mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-04-19 03:01:06 -04:00
Compare commits
19 Commits
defer-db-v
...
defer-payl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e716037f31 | ||
|
|
d8c2a1f610 | ||
|
|
ae65871466 | ||
|
|
f7c9fa6041 | ||
|
|
c338473fa1 | ||
|
|
2059e19083 | ||
|
|
134a64adc3 | ||
|
|
eea5870cba | ||
|
|
1aced9e85d | ||
|
|
4c207aa068 | ||
|
|
fbb65f6700 | ||
|
|
f0c7633c87 | ||
|
|
321828e775 | ||
|
|
e2ffb42abe | ||
|
|
d1bb9018d3 | ||
|
|
8c70e4bbb1 | ||
|
|
c004abc89d | ||
|
|
85316c5d16 | ||
|
|
9069afc6d0 |
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ð.GenericSignedBeaconBlock{
|
||||
Block: ð.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 ð.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 ð.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 ð.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 ð.SignedExecutionPayloadEnvelope{
|
||||
Message: msg,
|
||||
Signature: sig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
65
api/server/structs/conversions_block_gloas_test.go
Normal file
65
api/server/structs/conversions_block_gloas_test.go
Normal 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 ð.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))
|
||||
}
|
||||
@@ -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[:]),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 := ð.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 := ð.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 = ð.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{
|
||||
{
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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: ðpb.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: ðpb.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 := ðpb.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(ðpb.SignedBeaconBlockDeneb{
|
||||
Block: ðpb.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(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.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(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentNodeBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.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(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentNodeBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: parentSlot + 1,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.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(ðpb.SignedBeaconBlockGloas{})
|
||||
oldSigned, err2 := blocks.NewSignedBeaconBlock(oldBlk)
|
||||
require.NoError(t, err2)
|
||||
oldSt, err2 := state_native.InitializeFromProtoUnsafeGloas(ðpb.BeaconStateGloas{
|
||||
Slot: 0,
|
||||
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
|
||||
CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
LatestBlockHeader: ðpb.BeaconBlockHeader{ParentRoot: make([]byte, 32), StateRoot: make([]byte, 32), BodyRoot: make([]byte, 32)},
|
||||
Eth1Data: ðpb.Eth1Data{DepositRoot: make([]byte, 32), BlockHash: make([]byte, 32)},
|
||||
LatestExecutionPayloadBid: ðpb.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] = ðpb.BuilderPendingPayment{Withdrawal: ðpb.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(ðpb.SignedBeaconBlockGloas{})
|
||||
oldSigned2, err2 := blocks.NewSignedBeaconBlock(oldBlk2)
|
||||
require.NoError(t, err2)
|
||||
oldSt2, err2 := state_native.InitializeFromProtoUnsafeGloas(ðpb.BeaconStateGloas{
|
||||
Slot: 0,
|
||||
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
|
||||
CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
LatestBlockHeader: ðpb.BeaconBlockHeader{ParentRoot: make([]byte, 32), StateRoot: make([]byte, 32), BodyRoot: make([]byte, 32)},
|
||||
Eth1Data: ðpb.Eth1Data{DepositRoot: make([]byte, 32), BlockHash: make([]byte, 32)},
|
||||
LatestExecutionPayloadBid: ðpb.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] = ðpb.BuilderPendingPayment{Withdrawal: ðpb.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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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, ðpb.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")
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -89,16 +89,17 @@ func testSignedExecutionPayloadBid(
|
||||
) *ethpb.SignedExecutionPayloadBid {
|
||||
return ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.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),
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ go_library(
|
||||
"deposit_request.go",
|
||||
"log.go",
|
||||
"metrics.go",
|
||||
"parent_payload.go",
|
||||
"payload.go",
|
||||
"payload_attestation.go",
|
||||
"pending_payment.go",
|
||||
|
||||
@@ -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 := ðpb.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 := ðpb.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 := ðpb.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 := ðpb.SignedExecutionPayloadBid{
|
||||
Message: bid,
|
||||
@@ -302,17 +307,18 @@ func TestProcessExecutionPayloadBid_PendingPaymentAndCacheBid(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, balance, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.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 := ðpb.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 := ðpb.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 := ðpb.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 := ðpb.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 := ðpb.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 := ðpb.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 := ðpb.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 := ðpb.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 := ðpb.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)
|
||||
|
||||
112
beacon-chain/core/gloas/parent_payload.go
Normal file
112
beacon-chain/core/gloas/parent_payload.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 := ðpb.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 := ðpb.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 := ðpb.SignedBlindedExecutionPayloadEnvelope{
|
||||
Message: ðpb.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 := ðpb.SignedBlindedExecutionPayloadEnvelope{
|
||||
Message: ðpb.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 := ðpb.SignedBlindedExecutionPayloadEnvelope{
|
||||
Message: ðpb.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 := ðpb.SignedBlindedExecutionPayloadEnvelope{
|
||||
Message: ðpb.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)
|
||||
|
||||
@@ -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 := ðpb.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: ðpb.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,
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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: ðpb.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: ðpb.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 := ðpb.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: ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: make([]byte, 32),
|
||||
ParentBlockRoot: parentBlockRoot[:],
|
||||
BlockHash: blockHash[:],
|
||||
PrevRandao: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
BlobKzgCommitments: [][]byte{make([]byte, 48)},
|
||||
},
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
PreviousEpochParticipation: []byte{},
|
||||
CurrentEpochParticipation: []byte{},
|
||||
JustificationBits: []byte{0},
|
||||
PreviousJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
FinalizedCheckpoint: ðpb.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] = ðpb.BuilderPendingPayment{
|
||||
Withdrawal: ðpb.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 = ðpb.SyncCommittee{Pubkeys: pubkeys, AggregatePubkey: aggPubkey}
|
||||
protoState.NextSyncCommittee = ðpb.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 := ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: slot,
|
||||
ParentRoot: parentRoot[:],
|
||||
StateRoot: make([]byte, 32),
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
RandaoReveal: make([]byte, fieldparams.BLSSignatureLength),
|
||||
Graffiti: make([]byte, 32),
|
||||
Eth1Data: ðpb.Eth1Data{DepositRoot: make([]byte, 32), BlockHash: make([]byte, 32)},
|
||||
SyncAggregate: ðpb.SyncAggregate{
|
||||
SyncCommitteeBits: make([]byte, fieldparams.SyncAggregateSyncCommitteeBytesLength),
|
||||
SyncCommitteeSignature: make([]byte, fieldparams.BLSSignatureLength),
|
||||
},
|
||||
SignedExecutionPayloadBid: ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.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())
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -67,12 +67,13 @@ func prepareGloasForkchoiceState(
|
||||
FinalizedCheckpoint: finalizedCheckpoint,
|
||||
LatestBlockHeader: blockHeader,
|
||||
LatestExecutionPayloadBid: ðpb.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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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 := ð.SignedBeaconBlockGloas{}
|
||||
if err := gloasBlock.UnmarshalSSZ(body); err != nil {
|
||||
return nil, decodingError(
|
||||
version.String(version.Gloas), err,
|
||||
)
|
||||
}
|
||||
return ð.GenericSignedBeaconBlock{
|
||||
Block: ð.GenericSignedBeaconBlock_Gloas{Gloas: gloasBlock},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeFuluSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) {
|
||||
fuluBlock := ð.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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 := ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.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),
|
||||
}
|
||||
|
||||
@@ -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 ðpb.SignedExecutionPayloadEnvelope{
|
||||
Message: ðpb.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)
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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: ð.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: ð.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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
238
beacon-chain/rpc/eth/validator/handlers_block_gloas.go
Normal file
238
beacon-chain/rpc/eth/validator/handlers_block_gloas.go
Normal 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, ð.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, ð.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 := (ð.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, ð.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,
|
||||
})
|
||||
}
|
||||
234
beacon-chain/rpc/eth/validator/handlers_block_gloas_test.go
Normal file
234
beacon-chain/rpc/eth/validator/handlers_block_gloas_test.go
Normal 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 ð.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 ð.GenericBeaconBlock{
|
||||
Block: ð.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(
|
||||
ð.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(
|
||||
ð.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"))
|
||||
}
|
||||
@@ -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{}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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: ðpb.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: ðpb.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: ¤tSlot},
|
||||
}
|
||||
|
||||
_, 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: ¤tSlot},
|
||||
}
|
||||
|
||||
_, 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: ¤tSlot}
|
||||
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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 ðpb.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
|
||||
}
|
||||
|
||||
@@ -192,17 +192,18 @@ func TestSetExecutionPayloadBid_PrefersP2PBid(t *testing.T) {
|
||||
// Populate the highest bid cache with a P2P bid.
|
||||
p2pBid := ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.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 := ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.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 := ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.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),
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 ðpb.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)
|
||||
}
|
||||
|
||||
@@ -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 := ðpb.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 := ðpb.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(), ðpb.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),
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 := ðpb.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: ¤tSlot}
|
||||
p2p := &p2pmock.MockBroadcaster{}
|
||||
cache := cache.NewProposerPreferencesCache()
|
||||
vs := &Server{
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
TimeFetcher: chain,
|
||||
P2P: p2p,
|
||||
ProposerPreferencesCache: cache,
|
||||
}
|
||||
|
||||
req := ðpb.SubmitSignedProposerPreferencesRequest{
|
||||
SignedProposerPreferences: []*ethpb.SignedProposerPreferences{
|
||||
{
|
||||
Message: ðpb.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) {
|
||||
|
||||
@@ -26,15 +26,16 @@ func TestSubmitSignedExecutionPayloadBid_OK(t *testing.T) {
|
||||
|
||||
req := ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.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),
|
||||
}
|
||||
|
||||
@@ -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[:],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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 = ðpb.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))
|
||||
}
|
||||
|
||||
@@ -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] = ðpb.BuilderPendingPayment{
|
||||
Weight: 1,
|
||||
Withdrawal: ðpb.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] = ðpb.BuilderPendingPayment{
|
||||
Weight: 1,
|
||||
Withdrawal: ðpb.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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ func makeEnvelope(t *testing.T, slot primitives.Slot, blockHash [32]byte, parent
|
||||
Message: ðpb.ExecutionPayloadEnvelope{
|
||||
Slot: slot,
|
||||
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
|
||||
StateRoot: make([]byte, fieldparams.RootLength),
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{
|
||||
ParentHash: parentHash[:],
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user