mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-02-13 06:25:06 -05:00
Compare commits
19 Commits
payload-st
...
gloas-prop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ea57deff0 | ||
|
|
16e2b42990 | ||
|
|
9622ab90ea | ||
|
|
9a5c709e20 | ||
|
|
358a2537f0 | ||
|
|
c69c7a8c42 | ||
|
|
d7d8941a65 | ||
|
|
da5db1b4e5 | ||
|
|
367e4d49d3 | ||
|
|
afd5935c24 | ||
|
|
2b02648391 | ||
|
|
ec0c47396c | ||
|
|
513b699036 | ||
|
|
08dfeae0d9 | ||
|
|
5bc2653bc7 | ||
|
|
fdfbe3bfa8 | ||
|
|
6d4bc0e3bd | ||
|
|
6173c290d7 | ||
|
|
048ea521b6 |
4
beacon-chain/cache/BUILD.bazel
vendored
4
beacon-chain/cache/BUILD.bazel
vendored
@@ -15,6 +15,7 @@ go_library(
|
||||
"common.go",
|
||||
"doc.go",
|
||||
"error.go",
|
||||
"execution_payload_envelope.go",
|
||||
"interfaces.go",
|
||||
"log.go",
|
||||
"payload_attestation.go",
|
||||
@@ -52,6 +53,7 @@ go_library(
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//math:go_default_library",
|
||||
"//monitoring/tracing/trace:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/attestation:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
@@ -77,6 +79,7 @@ go_test(
|
||||
"checkpoint_state_test.go",
|
||||
"committee_fuzz_test.go",
|
||||
"committee_test.go",
|
||||
"execution_payload_envelope_test.go",
|
||||
"payload_attestation_test.go",
|
||||
"payload_id_test.go",
|
||||
"private_access_test.go",
|
||||
@@ -99,6 +102,7 @@ go_test(
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls/blst:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/attestation:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
|
||||
111
beacon-chain/cache/execution_payload_envelope.go
vendored
Normal file
111
beacon-chain/cache/execution_payload_envelope.go
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
// ExecutionPayloadEnvelopeKey uniquely identifies a cached execution payload envelope.
|
||||
type ExecutionPayloadEnvelopeKey struct {
|
||||
Slot primitives.Slot
|
||||
BuilderIndex primitives.BuilderIndex
|
||||
}
|
||||
|
||||
// executionPayloadEnvelopeCacheEntry holds an execution payload envelope and
|
||||
// the associated blobs bundle from the EL. The blobs bundle is needed later
|
||||
// when proposing the block to build and broadcast blob sidecars.
|
||||
type executionPayloadEnvelopeCacheEntry struct {
|
||||
envelope *ethpb.ExecutionPayloadEnvelope
|
||||
blobsBundle enginev1.BlobsBundler
|
||||
}
|
||||
|
||||
// ExecutionPayloadEnvelopeCache stores execution payload envelopes produced during
|
||||
// GLOAS block building for later retrieval by validators. When a beacon node
|
||||
// produces a GLOAS block, it caches the execution payload envelope so the validator
|
||||
// can retrieve it, sign it, and broadcast it separately from the beacon block.
|
||||
// The blobs bundle from the EL is also cached alongside, since blobs are only
|
||||
// persisted to the DB after they are broadcast as sidecars during block proposal.
|
||||
type ExecutionPayloadEnvelopeCache struct {
|
||||
cache map[ExecutionPayloadEnvelopeKey]*executionPayloadEnvelopeCacheEntry
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewExecutionPayloadEnvelopeCache creates a new execution payload envelope cache.
|
||||
func NewExecutionPayloadEnvelopeCache() *ExecutionPayloadEnvelopeCache {
|
||||
return &ExecutionPayloadEnvelopeCache{
|
||||
cache: make(map[ExecutionPayloadEnvelopeKey]*executionPayloadEnvelopeCacheEntry),
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves an execution payload envelope by slot and builder index.
|
||||
// Returns the envelope and true if found, nil and false otherwise.
|
||||
func (c *ExecutionPayloadEnvelopeCache) Get(slot primitives.Slot, builderIndex primitives.BuilderIndex) (*ethpb.ExecutionPayloadEnvelope, bool) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
key := ExecutionPayloadEnvelopeKey{
|
||||
Slot: slot,
|
||||
BuilderIndex: builderIndex,
|
||||
}
|
||||
entry, ok := c.cache[key]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return entry.envelope, true
|
||||
}
|
||||
|
||||
// GetBlobsBundle retrieves a cached blobs bundle by slot and builder index.
|
||||
// Returns the blobs bundle and true if found, nil and false otherwise.
|
||||
func (c *ExecutionPayloadEnvelopeCache) GetBlobsBundle(slot primitives.Slot, builderIndex primitives.BuilderIndex) (enginev1.BlobsBundler, bool) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
key := ExecutionPayloadEnvelopeKey{
|
||||
Slot: slot,
|
||||
BuilderIndex: builderIndex,
|
||||
}
|
||||
entry, ok := c.cache[key]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return entry.blobsBundle, true
|
||||
}
|
||||
|
||||
// Set stores an execution payload envelope and its associated blobs bundle in the cache.
|
||||
// The envelope's slot and builder_index fields are used as the cache key.
|
||||
// Old entries are automatically pruned to prevent unbounded growth.
|
||||
func (c *ExecutionPayloadEnvelopeCache) Set(envelope *ethpb.ExecutionPayloadEnvelope, blobsBundle enginev1.BlobsBundler) {
|
||||
if envelope == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
slot := envelope.Slot
|
||||
if slot > 2 {
|
||||
c.prune(slot - 2)
|
||||
}
|
||||
|
||||
key := ExecutionPayloadEnvelopeKey{
|
||||
Slot: slot,
|
||||
BuilderIndex: envelope.BuilderIndex,
|
||||
}
|
||||
c.cache[key] = &executionPayloadEnvelopeCacheEntry{
|
||||
envelope: envelope,
|
||||
blobsBundle: blobsBundle,
|
||||
}
|
||||
}
|
||||
|
||||
// prune removes all entries with slots older than the given slot.
|
||||
// Must be called with the lock held.
|
||||
func (c *ExecutionPayloadEnvelopeCache) prune(slot primitives.Slot) {
|
||||
for key := range c.cache {
|
||||
if key.Slot < slot {
|
||||
delete(c.cache, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
146
beacon-chain/cache/execution_payload_envelope_test.go
vendored
Normal file
146
beacon-chain/cache/execution_payload_envelope_test.go
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func TestExecutionPayloadEnvelopeCache_GetSet(t *testing.T) {
|
||||
cache := NewExecutionPayloadEnvelopeCache()
|
||||
|
||||
// Test empty cache returns false
|
||||
_, ok := cache.Get(1, 0)
|
||||
require.Equal(t, false, ok, "expected empty cache to return false")
|
||||
|
||||
// Create test envelope
|
||||
envelope := ðpb.ExecutionPayloadEnvelope{
|
||||
Slot: primitives.Slot(100),
|
||||
BuilderIndex: primitives.BuilderIndex(5),
|
||||
StateRoot: make([]byte, 32),
|
||||
}
|
||||
|
||||
// Set and retrieve
|
||||
cache.Set(envelope, nil)
|
||||
retrieved, ok := cache.Get(100, 5)
|
||||
require.Equal(t, true, ok, "expected to find cached envelope")
|
||||
require.Equal(t, envelope.Slot, retrieved.Slot)
|
||||
require.Equal(t, envelope.BuilderIndex, retrieved.BuilderIndex)
|
||||
|
||||
// Different builder index should not find it
|
||||
_, ok = cache.Get(100, 6)
|
||||
require.Equal(t, false, ok, "expected different builder index to return false")
|
||||
|
||||
// Different slot should not find it
|
||||
_, ok = cache.Get(101, 5)
|
||||
require.Equal(t, false, ok, "expected different slot to return false")
|
||||
}
|
||||
|
||||
func TestExecutionPayloadEnvelopeCache_Prune(t *testing.T) {
|
||||
cache := NewExecutionPayloadEnvelopeCache()
|
||||
|
||||
// Add envelopes at slots 10, 11, 12 (close enough that none get pruned).
|
||||
// Prune removes entries with slot < (new_slot - 2), so inserting 10, 11, 12
|
||||
// keeps all three: slot 12 prunes < 10, but 10 is not < 10.
|
||||
for i := primitives.Slot(10); i <= 12; i++ {
|
||||
envelope := ðpb.ExecutionPayloadEnvelope{
|
||||
Slot: i,
|
||||
BuilderIndex: 0,
|
||||
StateRoot: make([]byte, 32),
|
||||
}
|
||||
cache.Set(envelope, nil)
|
||||
}
|
||||
|
||||
// Verify all are present
|
||||
for i := primitives.Slot(10); i <= 12; i++ {
|
||||
_, ok := cache.Get(i, 0)
|
||||
require.Equal(t, true, ok, "expected envelope at slot %d", i)
|
||||
}
|
||||
|
||||
// Add envelope at slot 20, should prune slots < 18
|
||||
envelope := ðpb.ExecutionPayloadEnvelope{
|
||||
Slot: 20,
|
||||
BuilderIndex: 0,
|
||||
StateRoot: make([]byte, 32),
|
||||
}
|
||||
cache.Set(envelope, nil)
|
||||
|
||||
// Slots 10-12 should be pruned (all < 18)
|
||||
for i := primitives.Slot(10); i <= 12; i++ {
|
||||
_, ok := cache.Get(i, 0)
|
||||
require.Equal(t, false, ok, "expected envelope at slot %d to be pruned", i)
|
||||
}
|
||||
|
||||
// Slot 20 should still be present
|
||||
_, ok := cache.Get(20, 0)
|
||||
require.Equal(t, true, ok, "expected envelope at slot 20 to be present")
|
||||
}
|
||||
|
||||
func TestExecutionPayloadEnvelopeCache_NilEnvelope(t *testing.T) {
|
||||
cache := NewExecutionPayloadEnvelopeCache()
|
||||
|
||||
// Setting nil should not panic or add entry
|
||||
cache.Set(nil, nil)
|
||||
|
||||
// Cache should still be empty
|
||||
require.Equal(t, 0, len(cache.cache))
|
||||
}
|
||||
|
||||
func TestExecutionPayloadEnvelopeCache_MultipleBuilders(t *testing.T) {
|
||||
cache := NewExecutionPayloadEnvelopeCache()
|
||||
|
||||
slot := primitives.Slot(100)
|
||||
|
||||
// Add envelopes from multiple builders at same slot
|
||||
for i := range primitives.BuilderIndex(3) {
|
||||
envelope := ðpb.ExecutionPayloadEnvelope{
|
||||
Slot: slot,
|
||||
BuilderIndex: i,
|
||||
StateRoot: make([]byte, 32),
|
||||
}
|
||||
cache.Set(envelope, nil)
|
||||
}
|
||||
|
||||
// All should be retrievable
|
||||
for i := range primitives.BuilderIndex(3) {
|
||||
retrieved, ok := cache.Get(slot, i)
|
||||
require.Equal(t, true, ok, "expected to find envelope for builder %d", i)
|
||||
require.Equal(t, i, retrieved.BuilderIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutionPayloadEnvelopeCache_BlobsBundle(t *testing.T) {
|
||||
cache := NewExecutionPayloadEnvelopeCache()
|
||||
|
||||
slot := primitives.Slot(100)
|
||||
builderIndex := primitives.BuilderIndex(5)
|
||||
|
||||
envelope := ðpb.ExecutionPayloadEnvelope{
|
||||
Slot: slot,
|
||||
BuilderIndex: builderIndex,
|
||||
StateRoot: make([]byte, 32),
|
||||
}
|
||||
bundle := &enginev1.BlobsBundle{
|
||||
KzgCommitments: [][]byte{{1, 2, 3}},
|
||||
Proofs: [][]byte{{4, 5, 6}},
|
||||
Blobs: [][]byte{{7, 8, 9}},
|
||||
}
|
||||
|
||||
cache.Set(envelope, bundle)
|
||||
|
||||
// Retrieve blobs bundle
|
||||
retrieved, ok := cache.GetBlobsBundle(slot, builderIndex)
|
||||
require.Equal(t, true, ok, "expected to find cached blobs bundle")
|
||||
require.NotNil(t, retrieved)
|
||||
b, ok := retrieved.(*enginev1.BlobsBundle)
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, 1, len(b.KzgCommitments))
|
||||
require.DeepEqual(t, []byte{1, 2, 3}, b.KzgCommitments[0])
|
||||
|
||||
// Nil blobs bundle for missing key
|
||||
_, ok = cache.GetBlobsBundle(slot, 99)
|
||||
require.Equal(t, false, ok, "expected missing key to return false")
|
||||
}
|
||||
@@ -87,49 +87,50 @@ type serviceFlagOpts struct {
|
||||
// full PoS node. It handles the lifecycle of the entire system and registers
|
||||
// services to a service registry.
|
||||
type BeaconNode struct {
|
||||
cliCtx *cli.Context
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
services *runtime.ServiceRegistry
|
||||
lock sync.RWMutex
|
||||
stop chan struct{} // Channel to wait for termination notifications.
|
||||
db db.Database
|
||||
slasherDB db.SlasherDatabase
|
||||
attestationCache *cache.AttestationCache
|
||||
attestationPool attestations.Pool
|
||||
exitPool voluntaryexits.PoolManager
|
||||
slashingsPool slashings.PoolManager
|
||||
syncCommitteePool synccommittee.Pool
|
||||
blsToExecPool blstoexec.PoolManager
|
||||
depositCache cache.DepositCache
|
||||
trackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
payloadIDCache *cache.PayloadIDCache
|
||||
stateFeed *event.Feed
|
||||
blockFeed *event.Feed
|
||||
opFeed *event.Feed
|
||||
stateGen *stategen.State
|
||||
collector *bcnodeCollector
|
||||
slasherBlockHeadersFeed *event.Feed
|
||||
slasherAttestationsFeed *event.Feed
|
||||
finalizedStateAtStartUp state.BeaconState
|
||||
serviceFlagOpts *serviceFlagOpts
|
||||
GenesisProviders []genesis.Provider
|
||||
CheckpointInitializer checkpoint.Initializer
|
||||
forkChoicer forkchoice.ForkChoicer
|
||||
ClockWaiter startup.ClockWaiter
|
||||
BackfillOpts []backfill.ServiceOption
|
||||
initialSyncComplete chan struct{}
|
||||
BlobStorage *filesystem.BlobStorage
|
||||
BlobStorageOptions []filesystem.BlobStorageOption
|
||||
DataColumnStorage *filesystem.DataColumnStorage
|
||||
DataColumnStorageOptions []filesystem.DataColumnStorageOption
|
||||
verifyInitWaiter *verification.InitializerWaiter
|
||||
lhsp *verification.LazyHeadStateProvider
|
||||
syncChecker *initialsync.SyncChecker
|
||||
slasherEnabled bool
|
||||
lcStore *lightclient.Store
|
||||
ConfigOptions []params.Option
|
||||
SyncNeedsWaiter func() (das.SyncNeeds, error)
|
||||
cliCtx *cli.Context
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
services *runtime.ServiceRegistry
|
||||
lock sync.RWMutex
|
||||
stop chan struct{} // Channel to wait for termination notifications.
|
||||
db db.Database
|
||||
slasherDB db.SlasherDatabase
|
||||
attestationCache *cache.AttestationCache
|
||||
attestationPool attestations.Pool
|
||||
exitPool voluntaryexits.PoolManager
|
||||
slashingsPool slashings.PoolManager
|
||||
syncCommitteePool synccommittee.Pool
|
||||
blsToExecPool blstoexec.PoolManager
|
||||
depositCache cache.DepositCache
|
||||
trackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
payloadIDCache *cache.PayloadIDCache
|
||||
executionPayloadEnvelopeCache *cache.ExecutionPayloadEnvelopeCache
|
||||
stateFeed *event.Feed
|
||||
blockFeed *event.Feed
|
||||
opFeed *event.Feed
|
||||
stateGen *stategen.State
|
||||
collector *bcnodeCollector
|
||||
slasherBlockHeadersFeed *event.Feed
|
||||
slasherAttestationsFeed *event.Feed
|
||||
finalizedStateAtStartUp state.BeaconState
|
||||
serviceFlagOpts *serviceFlagOpts
|
||||
GenesisProviders []genesis.Provider
|
||||
CheckpointInitializer checkpoint.Initializer
|
||||
forkChoicer forkchoice.ForkChoicer
|
||||
ClockWaiter startup.ClockWaiter
|
||||
BackfillOpts []backfill.ServiceOption
|
||||
initialSyncComplete chan struct{}
|
||||
BlobStorage *filesystem.BlobStorage
|
||||
BlobStorageOptions []filesystem.BlobStorageOption
|
||||
DataColumnStorage *filesystem.DataColumnStorage
|
||||
DataColumnStorageOptions []filesystem.DataColumnStorageOption
|
||||
verifyInitWaiter *verification.InitializerWaiter
|
||||
lhsp *verification.LazyHeadStateProvider
|
||||
syncChecker *initialsync.SyncChecker
|
||||
slasherEnabled bool
|
||||
lcStore *lightclient.Store
|
||||
ConfigOptions []params.Option
|
||||
SyncNeedsWaiter func() (das.SyncNeeds, error)
|
||||
}
|
||||
|
||||
// New creates a new node instance, sets up configuration options, and registers
|
||||
@@ -151,28 +152,29 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, optFuncs []func(*cli.Co
|
||||
ctx := cliCtx.Context
|
||||
|
||||
beacon := &BeaconNode{
|
||||
cliCtx: cliCtx,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
services: runtime.NewServiceRegistry(),
|
||||
stop: make(chan struct{}),
|
||||
stateFeed: new(event.Feed),
|
||||
blockFeed: new(event.Feed),
|
||||
opFeed: new(event.Feed),
|
||||
attestationCache: cache.NewAttestationCache(),
|
||||
attestationPool: attestations.NewPool(),
|
||||
exitPool: voluntaryexits.NewPool(),
|
||||
slashingsPool: slashings.NewPool(),
|
||||
syncCommitteePool: synccommittee.NewPool(),
|
||||
blsToExecPool: blstoexec.NewPool(),
|
||||
trackedValidatorsCache: cache.NewTrackedValidatorsCache(),
|
||||
payloadIDCache: cache.NewPayloadIDCache(),
|
||||
slasherBlockHeadersFeed: new(event.Feed),
|
||||
slasherAttestationsFeed: new(event.Feed),
|
||||
serviceFlagOpts: &serviceFlagOpts{},
|
||||
initialSyncComplete: make(chan struct{}),
|
||||
syncChecker: &initialsync.SyncChecker{},
|
||||
slasherEnabled: cliCtx.Bool(flags.SlasherFlag.Name),
|
||||
cliCtx: cliCtx,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
services: runtime.NewServiceRegistry(),
|
||||
stop: make(chan struct{}),
|
||||
stateFeed: new(event.Feed),
|
||||
blockFeed: new(event.Feed),
|
||||
opFeed: new(event.Feed),
|
||||
attestationCache: cache.NewAttestationCache(),
|
||||
attestationPool: attestations.NewPool(),
|
||||
exitPool: voluntaryexits.NewPool(),
|
||||
slashingsPool: slashings.NewPool(),
|
||||
syncCommitteePool: synccommittee.NewPool(),
|
||||
blsToExecPool: blstoexec.NewPool(),
|
||||
trackedValidatorsCache: cache.NewTrackedValidatorsCache(),
|
||||
payloadIDCache: cache.NewPayloadIDCache(),
|
||||
executionPayloadEnvelopeCache: cache.NewExecutionPayloadEnvelopeCache(),
|
||||
slasherBlockHeadersFeed: new(event.Feed),
|
||||
slasherAttestationsFeed: new(event.Feed),
|
||||
serviceFlagOpts: &serviceFlagOpts{},
|
||||
initialSyncComplete: make(chan struct{}),
|
||||
syncChecker: &initialsync.SyncChecker{},
|
||||
slasherEnabled: cliCtx.Bool(flags.SlasherFlag.Name),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
@@ -954,60 +956,61 @@ func (b *BeaconNode) registerRPCService(router *http.ServeMux) error {
|
||||
|
||||
p2pService := b.fetchP2P()
|
||||
rpcService := rpc.NewService(b.ctx, &rpc.Config{
|
||||
ExecutionEngineCaller: web3Service,
|
||||
ExecutionReconstructor: web3Service,
|
||||
Host: host,
|
||||
Port: port,
|
||||
BeaconMonitoringHost: beaconMonitoringHost,
|
||||
BeaconMonitoringPort: beaconMonitoringPort,
|
||||
CertFlag: cert,
|
||||
KeyFlag: key,
|
||||
BeaconDB: b.db,
|
||||
Broadcaster: p2pService,
|
||||
PeersFetcher: p2pService,
|
||||
PeerManager: p2pService,
|
||||
MetadataProvider: p2pService,
|
||||
ChainInfoFetcher: chainService,
|
||||
HeadFetcher: chainService,
|
||||
CanonicalFetcher: chainService,
|
||||
ForkFetcher: chainService,
|
||||
ForkchoiceFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BlockReceiver: chainService,
|
||||
BlobReceiver: chainService,
|
||||
DataColumnReceiver: chainService,
|
||||
AttestationReceiver: chainService,
|
||||
GenesisTimeFetcher: chainService,
|
||||
GenesisFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
AttestationCache: b.attestationCache,
|
||||
AttestationsPool: b.attestationPool,
|
||||
ExitPool: b.exitPool,
|
||||
SlashingsPool: b.slashingsPool,
|
||||
BLSChangesPool: b.blsToExecPool,
|
||||
SyncCommitteeObjectPool: b.syncCommitteePool,
|
||||
ExecutionChainService: web3Service,
|
||||
ExecutionChainInfoFetcher: web3Service,
|
||||
ChainStartFetcher: chainStartFetcher,
|
||||
MockEth1Votes: mockEth1DataVotes,
|
||||
SyncService: syncService,
|
||||
DepositFetcher: depositFetcher,
|
||||
PendingDepositFetcher: b.depositCache,
|
||||
BlockNotifier: b,
|
||||
StateNotifier: b,
|
||||
OperationNotifier: b,
|
||||
StateGen: b.stateGen,
|
||||
EnableDebugRPCEndpoints: enableDebugRPCEndpoints,
|
||||
MaxMsgSize: maxMsgSize,
|
||||
BlockBuilder: b.fetchBuilderService(),
|
||||
Router: router,
|
||||
ClockWaiter: b.ClockWaiter,
|
||||
BlobStorage: b.BlobStorage,
|
||||
DataColumnStorage: b.DataColumnStorage,
|
||||
TrackedValidatorsCache: b.trackedValidatorsCache,
|
||||
PayloadIDCache: b.payloadIDCache,
|
||||
LCStore: b.lcStore,
|
||||
GraffitiInfo: web3Service.GraffitiInfo(),
|
||||
ExecutionEngineCaller: web3Service,
|
||||
ExecutionReconstructor: web3Service,
|
||||
Host: host,
|
||||
Port: port,
|
||||
BeaconMonitoringHost: beaconMonitoringHost,
|
||||
BeaconMonitoringPort: beaconMonitoringPort,
|
||||
CertFlag: cert,
|
||||
KeyFlag: key,
|
||||
BeaconDB: b.db,
|
||||
Broadcaster: p2pService,
|
||||
PeersFetcher: p2pService,
|
||||
PeerManager: p2pService,
|
||||
MetadataProvider: p2pService,
|
||||
ChainInfoFetcher: chainService,
|
||||
HeadFetcher: chainService,
|
||||
CanonicalFetcher: chainService,
|
||||
ForkFetcher: chainService,
|
||||
ForkchoiceFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BlockReceiver: chainService,
|
||||
BlobReceiver: chainService,
|
||||
DataColumnReceiver: chainService,
|
||||
AttestationReceiver: chainService,
|
||||
GenesisTimeFetcher: chainService,
|
||||
GenesisFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
AttestationCache: b.attestationCache,
|
||||
AttestationsPool: b.attestationPool,
|
||||
ExitPool: b.exitPool,
|
||||
SlashingsPool: b.slashingsPool,
|
||||
BLSChangesPool: b.blsToExecPool,
|
||||
SyncCommitteeObjectPool: b.syncCommitteePool,
|
||||
ExecutionChainService: web3Service,
|
||||
ExecutionChainInfoFetcher: web3Service,
|
||||
ChainStartFetcher: chainStartFetcher,
|
||||
MockEth1Votes: mockEth1DataVotes,
|
||||
SyncService: syncService,
|
||||
DepositFetcher: depositFetcher,
|
||||
PendingDepositFetcher: b.depositCache,
|
||||
BlockNotifier: b,
|
||||
StateNotifier: b,
|
||||
OperationNotifier: b,
|
||||
StateGen: b.stateGen,
|
||||
EnableDebugRPCEndpoints: enableDebugRPCEndpoints,
|
||||
MaxMsgSize: maxMsgSize,
|
||||
BlockBuilder: b.fetchBuilderService(),
|
||||
Router: router,
|
||||
ClockWaiter: b.ClockWaiter,
|
||||
BlobStorage: b.BlobStorage,
|
||||
DataColumnStorage: b.DataColumnStorage,
|
||||
TrackedValidatorsCache: b.trackedValidatorsCache,
|
||||
PayloadIDCache: b.payloadIDCache,
|
||||
ExecutionPayloadEnvelopeCache: b.executionPayloadEnvelopeCache,
|
||||
LCStore: b.lcStore,
|
||||
GraffitiInfo: web3Service.GraffitiInfo(),
|
||||
})
|
||||
|
||||
return b.services.RegisterService(rpcService)
|
||||
|
||||
@@ -5,6 +5,7 @@ go_library(
|
||||
srcs = [
|
||||
"handlers.go",
|
||||
"handlers_block.go",
|
||||
"handlers_gloas.go",
|
||||
"log.go",
|
||||
"server.go",
|
||||
],
|
||||
|
||||
31
beacon-chain/rpc/eth/validator/handlers_gloas.go
Normal file
31
beacon-chain/rpc/eth/validator/handlers_gloas.go
Normal file
@@ -0,0 +1,31 @@
|
||||
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)
|
||||
}
|
||||
|
||||
// GetExecutionPayloadEnvelope 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) GetExecutionPayloadEnvelope(w http.ResponseWriter, r *http.Request) {
|
||||
httputil.HandleError(w, "GetExecutionPayloadEnvelope 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)
|
||||
}
|
||||
@@ -26,6 +26,7 @@ go_library(
|
||||
"proposer_eth1data.go",
|
||||
"proposer_execution_payload.go",
|
||||
"proposer_exits.go",
|
||||
"proposer_gloas.go",
|
||||
"proposer_slashings.go",
|
||||
"proposer_sync_aggregate.go",
|
||||
"server.go",
|
||||
|
||||
@@ -57,6 +57,11 @@ func (vs *Server) constructGenericBeaconBlock(
|
||||
return nil, fmt.Errorf("expected *BlobsBundleV2, got %T", blobsBundler)
|
||||
}
|
||||
return vs.constructFuluBlock(blockProto, isBlinded, bidStr, bundle), nil
|
||||
case version.Gloas:
|
||||
// GLOAS blocks do not carry a separate payload value — the bid is part of the block body.
|
||||
return ðpb.GenericBeaconBlock{
|
||||
Block: ðpb.GenericBeaconBlock_Gloas{Gloas: blockProto.(*ethpb.BeaconBlockGloas)},
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown block version: %d", sBlk.Version())
|
||||
}
|
||||
|
||||
@@ -237,34 +237,50 @@ func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.Signed
|
||||
|
||||
// Set bls to execution change. New in Capella.
|
||||
vs.setBlsToExecData(sBlk, head)
|
||||
|
||||
// Set payload attestations. New in GLOAS.
|
||||
if sBlk.Version() >= version.Gloas {
|
||||
if err := sBlk.SetPayloadAttestations(vs.getPayloadAttestations(ctx, head, sBlk.Block().Slot())); err != nil {
|
||||
log.WithError(err).Error("Could not set payload attestations")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
winningBid := primitives.ZeroWei()
|
||||
var bundle enginev1.BlobsBundler
|
||||
var local *blocks.GetPayloadResponse
|
||||
if sBlk.Version() >= version.Bellatrix {
|
||||
local, err := vs.getLocalPayload(ctx, sBlk.Block(), head)
|
||||
var err error
|
||||
local, err = vs.getLocalPayload(ctx, sBlk.Block(), head)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get local payload: %v", err)
|
||||
}
|
||||
|
||||
// There's no reason to try to get a builder bid if local override is true.
|
||||
var builderBid builderapi.Bid
|
||||
if !(local.OverrideBuilder || skipMevBoost) {
|
||||
latestHeader, err := head.LatestExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get latest execution payload header: %v", err)
|
||||
switch {
|
||||
case sBlk.Version() >= version.Gloas:
|
||||
if err := vs.setGloasExecutionData(ctx, sBlk, local); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not set GLOAS execution data: %v", err)
|
||||
}
|
||||
parentGasLimit := latestHeader.GasLimit()
|
||||
builderBid, err = vs.getBuilderPayloadAndBlobs(ctx, sBlk.Block().Slot(), sBlk.Block().ProposerIndex(), parentGasLimit)
|
||||
if err != nil {
|
||||
builderGetPayloadMissCount.Inc()
|
||||
log.WithError(err).Error("Could not get builder payload")
|
||||
default:
|
||||
// There's no reason to try to get a builder bid if local override is true.
|
||||
var builderBid builderapi.Bid
|
||||
if !(local.OverrideBuilder || skipMevBoost) {
|
||||
latestHeader, err := head.LatestExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get latest execution payload header: %v", err)
|
||||
}
|
||||
parentGasLimit := latestHeader.GasLimit()
|
||||
builderBid, err = vs.getBuilderPayloadAndBlobs(ctx, sBlk.Block().Slot(), sBlk.Block().ProposerIndex(), parentGasLimit)
|
||||
if err != nil {
|
||||
builderGetPayloadMissCount.Inc()
|
||||
log.WithError(err).Error("Could not get builder payload")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
winningBid, bundle, err = setExecutionData(ctx, sBlk, local, builderBid, builderBoostFactor)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not set execution data: %v", err)
|
||||
winningBid, bundle, err = setExecutionData(ctx, sBlk, local, builderBid, builderBoostFactor)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not set execution data: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +292,15 @@ func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.Signed
|
||||
}
|
||||
sBlk.SetStateRoot(sr)
|
||||
|
||||
// For GLOAS, build and cache the execution payload envelope now that the block
|
||||
// is fully built (state root set). The envelope needs the final block HTR as
|
||||
// BeaconBlockRoot and the post-payload state root as StateRoot.
|
||||
if sBlk.Version() >= version.Gloas {
|
||||
if err := vs.buildExecutionPayloadEnvelope(ctx, sBlk, head, local); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not build execution payload envelope: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return vs.constructGenericBeaconBlock(sBlk, bundle, winningBid)
|
||||
}
|
||||
|
||||
@@ -283,11 +308,6 @@ func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.Signed
|
||||
//
|
||||
// ProposeBeaconBlock handles the proposal of beacon blocks.
|
||||
func (vs *Server) ProposeBeaconBlock(ctx context.Context, req *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) {
|
||||
var (
|
||||
blobSidecars []*ethpb.BlobSidecar
|
||||
dataColumnSidecars []blocks.RODataColumn
|
||||
)
|
||||
|
||||
ctx, span := trace.StartSpan(ctx, "ProposerServer.ProposeBeaconBlock")
|
||||
defer span.End()
|
||||
|
||||
@@ -304,15 +324,58 @@ func (vs *Server) ProposeBeaconBlock(ctx context.Context, req *ethpb.GenericSign
|
||||
return nil, status.Errorf(codes.Internal, "Could not hash tree root: %v", err)
|
||||
}
|
||||
|
||||
// For post-Fulu blinded blocks, submit to relay and return early
|
||||
if block.IsBlinded() && slots.ToEpoch(block.Block().Slot()) >= params.BeaconConfig().FuluForkEpoch {
|
||||
err := vs.BlockBuilder.SubmitBlindedBlockPostFulu(ctx, block)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not submit blinded block post-Fulu: %v", err)
|
||||
if block.Version() < version.Gloas {
|
||||
// For post-Fulu blinded blocks, submit to relay and return early.
|
||||
if block.IsBlinded() && slots.ToEpoch(block.Block().Slot()) >= params.BeaconConfig().FuluForkEpoch {
|
||||
err := vs.BlockBuilder.SubmitBlindedBlockPostFulu(ctx, block)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not submit blinded block post-Fulu: %v", err)
|
||||
}
|
||||
return ðpb.ProposeResponse{BlockRoot: root[:]}, nil
|
||||
}
|
||||
return ðpb.ProposeResponse{BlockRoot: root[:]}, nil
|
||||
return vs.proposeBlockWithSidecars(ctx, block, root, req)
|
||||
}
|
||||
|
||||
return vs.proposeBlock(ctx, block, root)
|
||||
}
|
||||
|
||||
// proposeBlock broadcasts and receives a beacon block without sidecars.
|
||||
// Used for GLOAS and beyond where execution data is delivered via a separate envelope.
|
||||
func (vs *Server) proposeBlock(
|
||||
ctx context.Context,
|
||||
block interfaces.SignedBeaconBlock,
|
||||
root [fieldparams.RootLength]byte,
|
||||
) (*ethpb.ProposeResponse, error) {
|
||||
protoBlock, err := block.Proto()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not convert block to proto: %v", err)
|
||||
}
|
||||
if err := vs.P2P.Broadcast(ctx, protoBlock); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not broadcast block: %v", err)
|
||||
}
|
||||
vs.BlockNotifier.BlockFeed().Send(&feed.Event{
|
||||
Type: blockfeed.ReceivedBlock,
|
||||
Data: &blockfeed.ReceivedBlockData{SignedBlock: block},
|
||||
})
|
||||
if err := vs.BlockReceiver.ReceiveBlock(ctx, block, root, nil); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not receive block: %v", err)
|
||||
}
|
||||
return ðpb.ProposeResponse{BlockRoot: root[:]}, nil
|
||||
}
|
||||
|
||||
// proposeBlockWithSidecars handles block proposal for forks that carry blob or
|
||||
// data column sidecars alongside the block (Bellatrix through Fulu).
|
||||
func (vs *Server) proposeBlockWithSidecars(
|
||||
ctx context.Context,
|
||||
block interfaces.SignedBeaconBlock,
|
||||
root [fieldparams.RootLength]byte,
|
||||
req *ethpb.GenericSignedBeaconBlock,
|
||||
) (*ethpb.ProposeResponse, error) {
|
||||
var (
|
||||
blobSidecars []*ethpb.BlobSidecar
|
||||
dataColumnSidecars []blocks.RODataColumn
|
||||
)
|
||||
|
||||
rob, err := blocks.NewROBlockWithRoot(block, root)
|
||||
if block.IsBlinded() {
|
||||
block, blobSidecars, err = vs.handleBlindedBlock(ctx, block)
|
||||
@@ -416,18 +479,10 @@ func (vs *Server) handleUnblindedBlock(
|
||||
}
|
||||
|
||||
if block.Version() >= version.Fulu {
|
||||
// Compute cells and proofs from the blobs and cell proofs.
|
||||
cellsPerBlob, proofsPerBlob, err := peerdas.ComputeCellsAndProofsFromFlat(rawBlobs, proofs)
|
||||
roDataColumnSidecars, err := buildDataColumnSidecars(rawBlobs, proofs, peerdas.PopulateFromBlock(block))
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "compute cells and proofs")
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Construct data column sidecars from the signed block and cells and proofs.
|
||||
roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(block))
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "data column sidcars")
|
||||
}
|
||||
|
||||
return nil, roDataColumnSidecars, nil
|
||||
}
|
||||
|
||||
@@ -439,6 +494,22 @@ func (vs *Server) handleUnblindedBlock(
|
||||
return blobSidecars, nil, nil
|
||||
}
|
||||
|
||||
// buildDataColumnSidecars computes cells and proofs from blobs and constructs
|
||||
// data column sidecars using the given ConstructionPopulator source.
|
||||
func buildDataColumnSidecars(blobs, proofs [][]byte, src peerdas.ConstructionPopulator) ([]blocks.RODataColumn, error) {
|
||||
cellsPerBlob, proofsPerBlob, err := peerdas.ComputeCellsAndProofsFromFlat(blobs, proofs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "compute cells and proofs")
|
||||
}
|
||||
|
||||
roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, src)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "data column sidecars")
|
||||
}
|
||||
|
||||
return roDataColumnSidecars, nil
|
||||
}
|
||||
|
||||
// broadcastReceiveBlock broadcasts a block and handles its reception.
|
||||
func (vs *Server) broadcastReceiveBlock(ctx context.Context, wg *sync.WaitGroup, block interfaces.SignedBeaconBlock, root [fieldparams.RootLength]byte) error {
|
||||
if err := vs.broadcastBlock(ctx, wg, block, root); err != nil {
|
||||
|
||||
@@ -16,6 +16,11 @@ func getEmptyBlock(slot primitives.Slot) (interfaces.SignedBeaconBlock, error) {
|
||||
var err error
|
||||
epoch := slots.ToEpoch(slot)
|
||||
switch {
|
||||
case epoch >= params.BeaconConfig().GloasForkEpoch:
|
||||
sBlk, err = blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockGloas{Block: ðpb.BeaconBlockGloas{Body: ðpb.BeaconBlockBodyGloas{}}})
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
|
||||
}
|
||||
case epoch >= params.BeaconConfig().FuluForkEpoch:
|
||||
sBlk, err = blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockFulu{Block: ðpb.BeaconBlockElectra{Body: ðpb.BeaconBlockBodyElectra{}}})
|
||||
if err != nil {
|
||||
|
||||
251
beacon-chain/rpc/prysm/v1alpha1/validator/proposer_gloas.go
Normal file
251
beacon-chain/rpc/prysm/v1alpha1/validator/proposer_gloas.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
"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"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// setGloasExecutionData creates an execution payload bid from the local payload
|
||||
// and sets it on the block body. The envelope is created and cached later by
|
||||
// buildExecutionPayloadEnvelope once the block is fully built.
|
||||
func (vs *Server) setGloasExecutionData(
|
||||
ctx context.Context,
|
||||
sBlk interfaces.SignedBeaconBlock,
|
||||
local *consensusblocks.GetPayloadResponse,
|
||||
) error {
|
||||
_, span := trace.StartSpan(ctx, "ProposerServer.setGloasExecutionData")
|
||||
defer span.End()
|
||||
|
||||
if local == nil || local.ExecutionData == nil {
|
||||
return errors.New("local execution payload is nil")
|
||||
}
|
||||
|
||||
// Create execution payload bid from the local payload.
|
||||
parentRoot := sBlk.Block().ParentRoot()
|
||||
bid, err := vs.createSelfBuildExecutionPayloadBid(
|
||||
local,
|
||||
primitives.BuilderIndex(sBlk.Block().ProposerIndex()),
|
||||
parentRoot[:],
|
||||
sBlk.Block().Slot(),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create execution payload bid")
|
||||
}
|
||||
|
||||
// Per spec, self-build bids must use G2 point-at-infinity as the signature.
|
||||
// Only the execution payload envelope requires a real signature from the proposer.
|
||||
signedBid := ðpb.SignedExecutionPayloadBid{
|
||||
Message: bid,
|
||||
Signature: common.InfiniteSignature[:],
|
||||
}
|
||||
if err := sBlk.SetSignedExecutionPayloadBid(signedBid); err != nil {
|
||||
return errors.Wrap(err, "could not set signed execution payload bid")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildExecutionPayloadEnvelope creates and caches the execution payload envelope
|
||||
// after the block is fully built (state root set). This allows setting the
|
||||
// BeaconBlockRoot from the final block HTR and computing the post-payload state root.
|
||||
func (vs *Server) buildExecutionPayloadEnvelope(
|
||||
ctx context.Context,
|
||||
sBlk interfaces.SignedBeaconBlock,
|
||||
head state.BeaconState,
|
||||
local *consensusblocks.GetPayloadResponse,
|
||||
) error {
|
||||
blockRoot, err := sBlk.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not compute block hash tree root")
|
||||
}
|
||||
|
||||
// Extract the underlying ExecutionPayloadDeneb proto.
|
||||
var payload *enginev1.ExecutionPayloadDeneb
|
||||
if local.ExecutionData != nil && !local.ExecutionData.IsNil() {
|
||||
if p, ok := local.ExecutionData.Proto().(*enginev1.ExecutionPayloadDeneb); ok {
|
||||
payload = p
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Compute post-payload state root. This requires:
|
||||
// 1. Run state transition on head state with the block to get post-block state
|
||||
// 2. Run ProcessPayloadStateTransition(ctx, postBlockState, envelope) to apply
|
||||
// execution payload effects (deposits, withdrawals, consolidations, etc.)
|
||||
// 3. Set stateRoot = postPayloadState.HashTreeRoot()
|
||||
// For now, the state root remains zeroed until ProcessPayloadStateTransition
|
||||
// is implemented in beacon-chain/core/gloas.
|
||||
|
||||
envelope := ðpb.ExecutionPayloadEnvelope{
|
||||
Payload: payload,
|
||||
ExecutionRequests: local.ExecutionRequests,
|
||||
BuilderIndex: primitives.BuilderIndex(sBlk.Block().ProposerIndex()),
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: sBlk.Block().Slot(),
|
||||
StateRoot: make([]byte, 32), // TODO: computed state root
|
||||
}
|
||||
|
||||
vs.cacheExecutionPayloadEnvelope(envelope, local.BlobsBundler)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPayloadAttestations returns payload attestations for inclusion in a GLOAS block.
|
||||
// PTC members broadcast PayloadAttestationMessages via P2P gossip during slot N.
|
||||
// All nodes collect these in a pool. The slot N+1 proposer retrieves and aggregates
|
||||
// them into PayloadAttestations for block inclusion.
|
||||
func (vs *Server) getPayloadAttestations(ctx context.Context, head state.BeaconState, slot primitives.Slot) []*ethpb.PayloadAttestation {
|
||||
// TODO: Retrieve and aggregate PayloadAttestationMessages from the pool
|
||||
// for the previous slot. Blocks are valid without payload attestations.
|
||||
return []*ethpb.PayloadAttestation{}
|
||||
}
|
||||
|
||||
// createSelfBuildExecutionPayloadBid creates an ExecutionPayloadBid for self-building,
|
||||
// where the proposer acts as its own builder. The value is the block value from the
|
||||
// execution layer, and execution payment is zero since the proposer doesn't pay themselves.
|
||||
func (vs *Server) createSelfBuildExecutionPayloadBid(
|
||||
local *consensusblocks.GetPayloadResponse,
|
||||
builderIndex primitives.BuilderIndex,
|
||||
parentBlockRoot []byte,
|
||||
slot primitives.Slot,
|
||||
) (*ethpb.ExecutionPayloadBid, error) {
|
||||
ed := local.ExecutionData
|
||||
if ed == nil || ed.IsNil() {
|
||||
return nil, errors.New("execution data is nil")
|
||||
}
|
||||
|
||||
return ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: ed.ParentHash(),
|
||||
ParentBlockRoot: bytesutil.SafeCopyBytes(parentBlockRoot),
|
||||
BlockHash: ed.BlockHash(),
|
||||
PrevRandao: ed.PrevRandao(),
|
||||
FeeRecipient: ed.FeeRecipient(),
|
||||
GasLimit: ed.GasLimit(),
|
||||
BuilderIndex: builderIndex,
|
||||
Slot: slot,
|
||||
Value: primitives.WeiToGwei(local.Bid),
|
||||
ExecutionPayment: 0, // Self-build: proposer doesn't pay themselves.
|
||||
BlobKzgCommitments: extractKzgCommitments(local.BlobsBundler),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// extractKzgCommitments pulls KZG commitments from a blobs bundler.
|
||||
func extractKzgCommitments(blobsBundler enginev1.BlobsBundler) [][]byte {
|
||||
if blobsBundler == nil {
|
||||
return nil
|
||||
}
|
||||
switch b := blobsBundler.(type) {
|
||||
case *enginev1.BlobsBundle:
|
||||
if b != nil {
|
||||
return b.KzgCommitments
|
||||
}
|
||||
case *enginev1.BlobsBundleV2:
|
||||
if b != nil {
|
||||
return b.KzgCommitments
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cacheExecutionPayloadEnvelope stores an envelope and its blobs bundle for later retrieval.
|
||||
// The blobs bundle is cached alongside the envelope because blobs from the EL are only
|
||||
// held in memory until they are broadcast as sidecars during block proposal.
|
||||
func (vs *Server) cacheExecutionPayloadEnvelope(envelope *ethpb.ExecutionPayloadEnvelope, blobsBundle enginev1.BlobsBundler) {
|
||||
if vs.ExecutionPayloadEnvelopeCache == nil {
|
||||
log.Warn("ExecutionPayloadEnvelopeCache is nil, envelope will not be cached")
|
||||
return
|
||||
}
|
||||
vs.ExecutionPayloadEnvelopeCache.Set(envelope, blobsBundle)
|
||||
}
|
||||
|
||||
// GetExecutionPayloadEnvelope retrieves a cached execution payload envelope.
|
||||
// This is called by validators after receiving a GLOAS block to get the envelope
|
||||
// they need to sign and broadcast.
|
||||
//
|
||||
// gRPC endpoint: /eth/v1alpha1/validator/execution_payload_envelope/{slot}/{builder_index}
|
||||
func (vs *Server) GetExecutionPayloadEnvelope(
|
||||
ctx context.Context,
|
||||
req *ethpb.ExecutionPayloadEnvelopeRequest,
|
||||
) (*ethpb.ExecutionPayloadEnvelopeResponse, error) {
|
||||
if req == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "request cannot be nil")
|
||||
}
|
||||
|
||||
if slots.ToEpoch(req.Slot) < params.BeaconConfig().GloasForkEpoch {
|
||||
return nil, status.Errorf(codes.InvalidArgument,
|
||||
"execution payload envelopes are not supported before GLOAS fork (slot %d)", req.Slot)
|
||||
}
|
||||
|
||||
envelope, found := vs.ExecutionPayloadEnvelopeCache.Get(req.Slot, req.BuilderIndex)
|
||||
if !found {
|
||||
return nil, status.Errorf(
|
||||
codes.NotFound,
|
||||
"execution payload envelope not found for slot %d builder %d",
|
||||
req.Slot,
|
||||
req.BuilderIndex,
|
||||
)
|
||||
}
|
||||
|
||||
return ðpb.ExecutionPayloadEnvelopeResponse{
|
||||
Envelope: envelope,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PublishExecutionPayloadEnvelope validates and broadcasts a signed execution payload envelope.
|
||||
// This is called by validators after signing the envelope retrieved from GetExecutionPayloadEnvelope.
|
||||
//
|
||||
// gRPC endpoint: POST /eth/v1alpha1/validator/execution_payload_envelope
|
||||
func (vs *Server) PublishExecutionPayloadEnvelope(
|
||||
ctx context.Context,
|
||||
req *ethpb.SignedExecutionPayloadEnvelope,
|
||||
) (*emptypb.Empty, error) {
|
||||
if req == nil || req.Message == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "signed envelope cannot be nil")
|
||||
}
|
||||
|
||||
if slots.ToEpoch(req.Message.Slot) < params.BeaconConfig().GloasForkEpoch {
|
||||
return nil, status.Errorf(codes.InvalidArgument,
|
||||
"execution payload envelopes are not supported before GLOAS fork (slot %d)", req.Message.Slot)
|
||||
}
|
||||
|
||||
beaconBlockRoot := bytesutil.ToBytes32(req.Message.BeaconBlockRoot)
|
||||
|
||||
log := log.WithFields(logrus.Fields{
|
||||
"slot": req.Message.Slot,
|
||||
"builderIndex": req.Message.BuilderIndex,
|
||||
"beaconBlockRoot": fmt.Sprintf("%#x", beaconBlockRoot[:8]),
|
||||
})
|
||||
log.Info("Publishing signed execution payload envelope")
|
||||
|
||||
// TODO: Validate envelope signature before broadcasting.
|
||||
|
||||
if err := vs.P2P.Broadcast(ctx, req); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to broadcast execution payload envelope: %v", err)
|
||||
}
|
||||
|
||||
// TODO: Receive the envelope locally following the broadcastReceiveBlock pattern.
|
||||
|
||||
// TODO: Build and broadcast data column sidecars from the cached blobs bundle.
|
||||
// In GLOAS, blob data is delivered alongside the execution payload envelope
|
||||
// rather than with the beacon block (which only carries the bid). Not needed
|
||||
// for devnet-0.
|
||||
|
||||
log.Info("Successfully published execution payload envelope")
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
consensusblocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls/common"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func TestSetGloasExecutionData(t *testing.T) {
|
||||
parentRoot := [32]byte{1, 2, 3}
|
||||
slot := primitives.Slot(100)
|
||||
proposerIndex := primitives.ValidatorIndex(42)
|
||||
|
||||
sBlk, err := consensusblocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: slot,
|
||||
ProposerIndex: proposerIndex,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
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)
|
||||
|
||||
// 5 Gwei = 5,000,000,000 Wei
|
||||
bidValue := big.NewInt(5_000_000_000)
|
||||
local := &consensusblocks.GetPayloadResponse{
|
||||
ExecutionData: ed,
|
||||
Bid: bidValue,
|
||||
BlobsBundler: nil,
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
}
|
||||
|
||||
vs := &Server{}
|
||||
|
||||
err = vs.setGloasExecutionData(t.Context(), sBlk, local)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the signed bid was set on the block.
|
||||
signedBid, err := sBlk.Block().Body().SignedExecutionPayloadBid()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, signedBid)
|
||||
require.NotNil(t, signedBid.Message)
|
||||
|
||||
// Per spec (process_execution_payload_bid): for self-builds,
|
||||
// signature must be G2 point-at-infinity.
|
||||
require.DeepEqual(t, common.InfiniteSignature[:], signedBid.Signature)
|
||||
|
||||
// Verify bid fields.
|
||||
bid := signedBid.Message
|
||||
require.Equal(t, slot, bid.Slot)
|
||||
require.Equal(t, primitives.BuilderIndex(proposerIndex), bid.BuilderIndex)
|
||||
require.DeepEqual(t, parentRoot[:], bid.ParentBlockRoot)
|
||||
require.Equal(t, primitives.Gwei(5), bid.Value)
|
||||
require.Equal(t, primitives.Gwei(0), bid.ExecutionPayment)
|
||||
}
|
||||
|
||||
func TestSetGloasExecutionData_NilPayload(t *testing.T) {
|
||||
sBlk, err := consensusblocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 1,
|
||||
ParentRoot: make([]byte, 32),
|
||||
Body: ðpb.BeaconBlockBodyGloas{},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
vs := &Server{}
|
||||
|
||||
err = vs.setGloasExecutionData(t.Context(), sBlk, nil)
|
||||
require.ErrorContains(t, "local execution payload is nil", err)
|
||||
|
||||
err = vs.setGloasExecutionData(t.Context(), sBlk, &consensusblocks.GetPayloadResponse{})
|
||||
require.ErrorContains(t, "local execution payload is nil", err)
|
||||
}
|
||||
@@ -44,46 +44,47 @@ import (
|
||||
// and committees in which particular validators need to perform their responsibilities,
|
||||
// and more.
|
||||
type Server struct {
|
||||
Ctx context.Context
|
||||
PayloadIDCache *cache.PayloadIDCache
|
||||
TrackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
ForkFetcher blockchain.ForkFetcher
|
||||
ForkchoiceFetcher blockchain.ForkchoiceFetcher
|
||||
GenesisFetcher blockchain.GenesisFetcher
|
||||
FinalizationFetcher blockchain.FinalizationFetcher
|
||||
TimeFetcher blockchain.TimeFetcher
|
||||
BlockFetcher execution.POWBlockFetcher
|
||||
DepositFetcher cache.DepositFetcher
|
||||
ChainStartFetcher execution.ChainStartFetcher
|
||||
Eth1InfoFetcher execution.ChainInfoFetcher
|
||||
OptimisticModeFetcher blockchain.OptimisticModeFetcher
|
||||
SyncChecker sync.Checker
|
||||
StateNotifier statefeed.Notifier
|
||||
BlockNotifier blockfeed.Notifier
|
||||
P2P p2p.Broadcaster
|
||||
AttestationCache *cache.AttestationCache
|
||||
AttPool attestations.Pool
|
||||
SlashingsPool slashings.PoolManager
|
||||
ExitPool voluntaryexits.PoolManager
|
||||
SyncCommitteePool synccommittee.Pool
|
||||
BlockReceiver blockchain.BlockReceiver
|
||||
BlobReceiver blockchain.BlobReceiver
|
||||
DataColumnReceiver blockchain.DataColumnReceiver
|
||||
MockEth1Votes bool
|
||||
Eth1BlockFetcher execution.POWBlockFetcher
|
||||
PendingDepositsFetcher depositsnapshot.PendingDepositsFetcher
|
||||
OperationNotifier opfeed.Notifier
|
||||
StateGen stategen.StateManager
|
||||
ReplayerBuilder stategen.ReplayerBuilder
|
||||
BeaconDB db.HeadAccessDatabase
|
||||
ExecutionEngineCaller execution.EngineCaller
|
||||
BlockBuilder builder.BlockBuilder
|
||||
BLSChangesPool blstoexec.PoolManager
|
||||
ClockWaiter startup.ClockWaiter
|
||||
CoreService *core.Service
|
||||
AttestationStateFetcher blockchain.AttestationStateFetcher
|
||||
GraffitiInfo *execution.GraffitiInfo
|
||||
Ctx context.Context
|
||||
PayloadIDCache *cache.PayloadIDCache
|
||||
TrackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
ExecutionPayloadEnvelopeCache *cache.ExecutionPayloadEnvelopeCache // GLOAS: Cache for execution payload envelopes
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
ForkFetcher blockchain.ForkFetcher
|
||||
ForkchoiceFetcher blockchain.ForkchoiceFetcher
|
||||
GenesisFetcher blockchain.GenesisFetcher
|
||||
FinalizationFetcher blockchain.FinalizationFetcher
|
||||
TimeFetcher blockchain.TimeFetcher
|
||||
BlockFetcher execution.POWBlockFetcher
|
||||
DepositFetcher cache.DepositFetcher
|
||||
ChainStartFetcher execution.ChainStartFetcher
|
||||
Eth1InfoFetcher execution.ChainInfoFetcher
|
||||
OptimisticModeFetcher blockchain.OptimisticModeFetcher
|
||||
SyncChecker sync.Checker
|
||||
StateNotifier statefeed.Notifier
|
||||
BlockNotifier blockfeed.Notifier
|
||||
P2P p2p.Broadcaster
|
||||
AttestationCache *cache.AttestationCache
|
||||
AttPool attestations.Pool
|
||||
SlashingsPool slashings.PoolManager
|
||||
ExitPool voluntaryexits.PoolManager
|
||||
SyncCommitteePool synccommittee.Pool
|
||||
BlockReceiver blockchain.BlockReceiver
|
||||
BlobReceiver blockchain.BlobReceiver
|
||||
DataColumnReceiver blockchain.DataColumnReceiver
|
||||
MockEth1Votes bool
|
||||
Eth1BlockFetcher execution.POWBlockFetcher
|
||||
PendingDepositsFetcher depositsnapshot.PendingDepositsFetcher
|
||||
OperationNotifier opfeed.Notifier
|
||||
StateGen stategen.StateManager
|
||||
ReplayerBuilder stategen.ReplayerBuilder
|
||||
BeaconDB db.HeadAccessDatabase
|
||||
ExecutionEngineCaller execution.EngineCaller
|
||||
BlockBuilder builder.BlockBuilder
|
||||
BLSChangesPool blstoexec.PoolManager
|
||||
ClockWaiter startup.ClockWaiter
|
||||
CoreService *core.Service
|
||||
AttestationStateFetcher blockchain.AttestationStateFetcher
|
||||
GraffitiInfo *execution.GraffitiInfo
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -72,60 +72,61 @@ type Service struct {
|
||||
|
||||
// Config options for the beacon node RPC server.
|
||||
type Config struct {
|
||||
ExecutionReconstructor execution.Reconstructor
|
||||
Host string
|
||||
Port string
|
||||
CertFlag string
|
||||
KeyFlag string
|
||||
BeaconMonitoringHost string
|
||||
BeaconMonitoringPort int
|
||||
BeaconDB db.HeadAccessDatabase
|
||||
ChainInfoFetcher blockchain.ChainInfoFetcher
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
CanonicalFetcher blockchain.CanonicalFetcher
|
||||
ForkFetcher blockchain.ForkFetcher
|
||||
ForkchoiceFetcher blockchain.ForkchoiceFetcher
|
||||
FinalizationFetcher blockchain.FinalizationFetcher
|
||||
AttestationReceiver blockchain.AttestationReceiver
|
||||
BlockReceiver blockchain.BlockReceiver
|
||||
BlobReceiver blockchain.BlobReceiver
|
||||
DataColumnReceiver blockchain.DataColumnReceiver
|
||||
ExecutionChainService execution.Chain
|
||||
ChainStartFetcher execution.ChainStartFetcher
|
||||
ExecutionChainInfoFetcher execution.ChainInfoFetcher
|
||||
GenesisTimeFetcher blockchain.TimeFetcher
|
||||
GenesisFetcher blockchain.GenesisFetcher
|
||||
MockEth1Votes bool
|
||||
EnableDebugRPCEndpoints bool
|
||||
AttestationCache *cache.AttestationCache
|
||||
AttestationsPool attestations.Pool
|
||||
ExitPool voluntaryexits.PoolManager
|
||||
SlashingsPool slashings.PoolManager
|
||||
SyncCommitteeObjectPool synccommittee.Pool
|
||||
BLSChangesPool blstoexec.PoolManager
|
||||
SyncService chainSync.Checker
|
||||
Broadcaster p2p.Broadcaster
|
||||
PeersFetcher p2p.PeersProvider
|
||||
PeerManager p2p.PeerManager
|
||||
MetadataProvider p2p.MetadataProvider
|
||||
DepositFetcher cache.DepositFetcher
|
||||
PendingDepositFetcher depositsnapshot.PendingDepositsFetcher
|
||||
StateNotifier statefeed.Notifier
|
||||
BlockNotifier blockfeed.Notifier
|
||||
OperationNotifier opfeed.Notifier
|
||||
StateGen *stategen.State
|
||||
MaxMsgSize int
|
||||
ExecutionEngineCaller execution.EngineCaller
|
||||
OptimisticModeFetcher blockchain.OptimisticModeFetcher
|
||||
BlockBuilder builder.BlockBuilder
|
||||
Router *http.ServeMux
|
||||
ClockWaiter startup.ClockWaiter
|
||||
BlobStorage *filesystem.BlobStorage
|
||||
DataColumnStorage *filesystem.DataColumnStorage
|
||||
TrackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
PayloadIDCache *cache.PayloadIDCache
|
||||
LCStore *lightClient.Store
|
||||
GraffitiInfo *execution.GraffitiInfo
|
||||
ExecutionReconstructor execution.Reconstructor
|
||||
Host string
|
||||
Port string
|
||||
CertFlag string
|
||||
KeyFlag string
|
||||
BeaconMonitoringHost string
|
||||
BeaconMonitoringPort int
|
||||
BeaconDB db.HeadAccessDatabase
|
||||
ChainInfoFetcher blockchain.ChainInfoFetcher
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
CanonicalFetcher blockchain.CanonicalFetcher
|
||||
ForkFetcher blockchain.ForkFetcher
|
||||
ForkchoiceFetcher blockchain.ForkchoiceFetcher
|
||||
FinalizationFetcher blockchain.FinalizationFetcher
|
||||
AttestationReceiver blockchain.AttestationReceiver
|
||||
BlockReceiver blockchain.BlockReceiver
|
||||
BlobReceiver blockchain.BlobReceiver
|
||||
DataColumnReceiver blockchain.DataColumnReceiver
|
||||
ExecutionChainService execution.Chain
|
||||
ChainStartFetcher execution.ChainStartFetcher
|
||||
ExecutionChainInfoFetcher execution.ChainInfoFetcher
|
||||
GenesisTimeFetcher blockchain.TimeFetcher
|
||||
GenesisFetcher blockchain.GenesisFetcher
|
||||
MockEth1Votes bool
|
||||
EnableDebugRPCEndpoints bool
|
||||
AttestationCache *cache.AttestationCache
|
||||
AttestationsPool attestations.Pool
|
||||
ExitPool voluntaryexits.PoolManager
|
||||
SlashingsPool slashings.PoolManager
|
||||
SyncCommitteeObjectPool synccommittee.Pool
|
||||
BLSChangesPool blstoexec.PoolManager
|
||||
SyncService chainSync.Checker
|
||||
Broadcaster p2p.Broadcaster
|
||||
PeersFetcher p2p.PeersProvider
|
||||
PeerManager p2p.PeerManager
|
||||
MetadataProvider p2p.MetadataProvider
|
||||
DepositFetcher cache.DepositFetcher
|
||||
PendingDepositFetcher depositsnapshot.PendingDepositsFetcher
|
||||
StateNotifier statefeed.Notifier
|
||||
BlockNotifier blockfeed.Notifier
|
||||
OperationNotifier opfeed.Notifier
|
||||
StateGen *stategen.State
|
||||
MaxMsgSize int
|
||||
ExecutionEngineCaller execution.EngineCaller
|
||||
OptimisticModeFetcher blockchain.OptimisticModeFetcher
|
||||
BlockBuilder builder.BlockBuilder
|
||||
Router *http.ServeMux
|
||||
ClockWaiter startup.ClockWaiter
|
||||
BlobStorage *filesystem.BlobStorage
|
||||
DataColumnStorage *filesystem.DataColumnStorage
|
||||
TrackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
PayloadIDCache *cache.PayloadIDCache
|
||||
ExecutionPayloadEnvelopeCache *cache.ExecutionPayloadEnvelopeCache
|
||||
LCStore *lightClient.Store
|
||||
GraffitiInfo *execution.GraffitiInfo
|
||||
}
|
||||
|
||||
// NewService instantiates a new RPC service instance that will
|
||||
@@ -218,46 +219,47 @@ func NewService(ctx context.Context, cfg *Config) *Service {
|
||||
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
|
||||
}
|
||||
validatorServer := &validatorv1alpha1.Server{
|
||||
Ctx: s.ctx,
|
||||
AttestationCache: s.cfg.AttestationCache,
|
||||
AttPool: s.cfg.AttestationsPool,
|
||||
ExitPool: s.cfg.ExitPool,
|
||||
HeadFetcher: s.cfg.HeadFetcher,
|
||||
ForkFetcher: s.cfg.ForkFetcher,
|
||||
ForkchoiceFetcher: s.cfg.ForkchoiceFetcher,
|
||||
GenesisFetcher: s.cfg.GenesisFetcher,
|
||||
FinalizationFetcher: s.cfg.FinalizationFetcher,
|
||||
TimeFetcher: s.cfg.GenesisTimeFetcher,
|
||||
BlockFetcher: s.cfg.ExecutionChainService,
|
||||
DepositFetcher: s.cfg.DepositFetcher,
|
||||
ChainStartFetcher: s.cfg.ChainStartFetcher,
|
||||
Eth1InfoFetcher: s.cfg.ExecutionChainService,
|
||||
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
|
||||
SyncChecker: s.cfg.SyncService,
|
||||
StateNotifier: s.cfg.StateNotifier,
|
||||
BlockNotifier: s.cfg.BlockNotifier,
|
||||
OperationNotifier: s.cfg.OperationNotifier,
|
||||
P2P: s.cfg.Broadcaster,
|
||||
BlockReceiver: s.cfg.BlockReceiver,
|
||||
BlobReceiver: s.cfg.BlobReceiver,
|
||||
DataColumnReceiver: s.cfg.DataColumnReceiver,
|
||||
MockEth1Votes: s.cfg.MockEth1Votes,
|
||||
Eth1BlockFetcher: s.cfg.ExecutionChainService,
|
||||
PendingDepositsFetcher: s.cfg.PendingDepositFetcher,
|
||||
SlashingsPool: s.cfg.SlashingsPool,
|
||||
StateGen: s.cfg.StateGen,
|
||||
SyncCommitteePool: s.cfg.SyncCommitteeObjectPool,
|
||||
ReplayerBuilder: ch,
|
||||
ExecutionEngineCaller: s.cfg.ExecutionEngineCaller,
|
||||
BeaconDB: s.cfg.BeaconDB,
|
||||
BlockBuilder: s.cfg.BlockBuilder,
|
||||
BLSChangesPool: s.cfg.BLSChangesPool,
|
||||
ClockWaiter: s.cfg.ClockWaiter,
|
||||
CoreService: coreService,
|
||||
TrackedValidatorsCache: s.cfg.TrackedValidatorsCache,
|
||||
PayloadIDCache: s.cfg.PayloadIDCache,
|
||||
AttestationStateFetcher: s.cfg.AttestationReceiver,
|
||||
GraffitiInfo: s.cfg.GraffitiInfo,
|
||||
Ctx: s.ctx,
|
||||
AttestationCache: s.cfg.AttestationCache,
|
||||
AttPool: s.cfg.AttestationsPool,
|
||||
ExitPool: s.cfg.ExitPool,
|
||||
HeadFetcher: s.cfg.HeadFetcher,
|
||||
ForkFetcher: s.cfg.ForkFetcher,
|
||||
ForkchoiceFetcher: s.cfg.ForkchoiceFetcher,
|
||||
GenesisFetcher: s.cfg.GenesisFetcher,
|
||||
FinalizationFetcher: s.cfg.FinalizationFetcher,
|
||||
TimeFetcher: s.cfg.GenesisTimeFetcher,
|
||||
BlockFetcher: s.cfg.ExecutionChainService,
|
||||
DepositFetcher: s.cfg.DepositFetcher,
|
||||
ChainStartFetcher: s.cfg.ChainStartFetcher,
|
||||
Eth1InfoFetcher: s.cfg.ExecutionChainService,
|
||||
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
|
||||
SyncChecker: s.cfg.SyncService,
|
||||
StateNotifier: s.cfg.StateNotifier,
|
||||
BlockNotifier: s.cfg.BlockNotifier,
|
||||
OperationNotifier: s.cfg.OperationNotifier,
|
||||
P2P: s.cfg.Broadcaster,
|
||||
BlockReceiver: s.cfg.BlockReceiver,
|
||||
BlobReceiver: s.cfg.BlobReceiver,
|
||||
DataColumnReceiver: s.cfg.DataColumnReceiver,
|
||||
MockEth1Votes: s.cfg.MockEth1Votes,
|
||||
Eth1BlockFetcher: s.cfg.ExecutionChainService,
|
||||
PendingDepositsFetcher: s.cfg.PendingDepositFetcher,
|
||||
SlashingsPool: s.cfg.SlashingsPool,
|
||||
StateGen: s.cfg.StateGen,
|
||||
SyncCommitteePool: s.cfg.SyncCommitteeObjectPool,
|
||||
ReplayerBuilder: ch,
|
||||
ExecutionEngineCaller: s.cfg.ExecutionEngineCaller,
|
||||
BeaconDB: s.cfg.BeaconDB,
|
||||
BlockBuilder: s.cfg.BlockBuilder,
|
||||
BLSChangesPool: s.cfg.BLSChangesPool,
|
||||
ClockWaiter: s.cfg.ClockWaiter,
|
||||
CoreService: coreService,
|
||||
TrackedValidatorsCache: s.cfg.TrackedValidatorsCache,
|
||||
PayloadIDCache: s.cfg.PayloadIDCache,
|
||||
AttestationStateFetcher: s.cfg.AttestationReceiver,
|
||||
ExecutionPayloadEnvelopeCache: s.cfg.ExecutionPayloadEnvelopeCache,
|
||||
GraffitiInfo: s.cfg.GraffitiInfo,
|
||||
}
|
||||
s.validatorServer = validatorServer
|
||||
nodeServer := &nodev1alpha1.Server{
|
||||
|
||||
4
changelog/james-prysm_gloas-proposer.md
Normal file
4
changelog/james-prysm_gloas-proposer.md
Normal file
@@ -0,0 +1,4 @@
|
||||
### Added
|
||||
|
||||
- gRPC endpoints for get block, propose block for gloas - excludes PTC attestations
|
||||
- gRPC endpoints for get payload envelope, and propose payload envelope - excludes blob propogation
|
||||
2416
proto/prysm/v1alpha1/validator.pb.go
generated
2416
proto/prysm/v1alpha1/validator.pb.go
generated
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@ import "proto/prysm/v1alpha1/beacon_block.proto";
|
||||
import "proto/prysm/v1alpha1/beacon_core_types.proto";
|
||||
import "proto/prysm/v1alpha1/sync_committee.proto";
|
||||
import "proto/prysm/v1alpha1/attestation.proto";
|
||||
import "proto/prysm/v1alpha1/gloas.proto";
|
||||
|
||||
option csharp_namespace = "Ethereum.Eth.V1";
|
||||
option go_package = "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1;eth";
|
||||
@@ -439,6 +440,39 @@ service BeaconNodeValidator {
|
||||
get : "/eth/v1alpha1/validator/blocks/aggregated_sig_and_aggregation_bits"
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// GLOAS Fork Endpoints
|
||||
// ==========================================================================
|
||||
|
||||
// GetExecutionPayloadEnvelope retrieves a cached execution payload envelope
|
||||
// for the given slot and builder index. This is called by validators after
|
||||
// receiving a GLOAS block to get the envelope they need to sign and broadcast.
|
||||
//
|
||||
// The envelope is cached by the beacon node during block production and
|
||||
// contains the full execution payload that corresponds to the bid in the block.
|
||||
rpc GetExecutionPayloadEnvelope(ExecutionPayloadEnvelopeRequest)
|
||||
returns (ExecutionPayloadEnvelopeResponse) {
|
||||
option deprecated = true;
|
||||
option (google.api.http) = {
|
||||
get : "/eth/v1alpha1/validator/execution_payload_envelope/{slot}/{builder_index}"
|
||||
};
|
||||
}
|
||||
|
||||
// PublishExecutionPayloadEnvelope broadcasts a signed execution payload envelope
|
||||
// to the P2P network. This is called by validators after signing the envelope
|
||||
// retrieved from GetExecutionPayloadEnvelope.
|
||||
//
|
||||
// The beacon node validates the envelope signature and broadcasts it to peers
|
||||
// via the execution_payload_envelope gossip topic.
|
||||
rpc PublishExecutionPayloadEnvelope(SignedExecutionPayloadEnvelope)
|
||||
returns (google.protobuf.Empty) {
|
||||
option deprecated = true;
|
||||
option (google.api.http) = {
|
||||
post : "/eth/v1alpha1/validator/execution_payload_envelope"
|
||||
body : "*"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// SyncMessageBlockRootResponse for beacon chain validator to retrieve and
|
||||
@@ -1134,3 +1168,34 @@ message AggregatedSigAndAggregationBitsResponse {
|
||||
bytes aggregated_sig = 1;
|
||||
bytes bits = 2;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// GLOAS Fork Messages
|
||||
// =============================================================================
|
||||
|
||||
// ExecutionPayloadEnvelopeRequest is the request for retrieving a cached
|
||||
// execution payload envelope from the beacon node.
|
||||
message ExecutionPayloadEnvelopeRequest {
|
||||
option deprecated = true;
|
||||
|
||||
// The slot for which to retrieve the execution payload envelope.
|
||||
uint64 slot = 1 [
|
||||
(ethereum.eth.ext.cast_type) =
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives.Slot"
|
||||
];
|
||||
|
||||
// The builder index that created the payload envelope.
|
||||
uint64 builder_index = 2 [
|
||||
(ethereum.eth.ext.cast_type) =
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives.BuilderIndex"
|
||||
];
|
||||
}
|
||||
|
||||
// ExecutionPayloadEnvelopeResponse is the response containing the cached
|
||||
// execution payload envelope.
|
||||
message ExecutionPayloadEnvelopeResponse {
|
||||
option deprecated = true;
|
||||
|
||||
// The execution payload envelope for the requested slot and builder.
|
||||
ExecutionPayloadEnvelope envelope = 1;
|
||||
}
|
||||
|
||||
40
testing/mock/beacon_validator_client_mock.go
generated
40
testing/mock/beacon_validator_client_mock.go
generated
@@ -683,6 +683,46 @@ func (mr *MockBeaconNodeValidatorClientMockRecorder) WaitForChainStart(arg0, arg
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForChainStart", reflect.TypeOf((*MockBeaconNodeValidatorClient)(nil).WaitForChainStart), varargs...)
|
||||
}
|
||||
|
||||
// GetExecutionPayloadEnvelope mocks base method.
|
||||
func (m *MockBeaconNodeValidatorClient) GetExecutionPayloadEnvelope(arg0 context.Context, arg1 *eth.ExecutionPayloadEnvelopeRequest, arg2 ...grpc.CallOption) (*eth.ExecutionPayloadEnvelopeResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "ExecutionPayloadEnvelope", varargs...)
|
||||
ret0, _ := ret[0].(*eth.ExecutionPayloadEnvelopeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetExecutionPayloadEnvelope indicates an expected call of GetExecutionPayloadEnvelope.
|
||||
func (mr *MockBeaconNodeValidatorClientMockRecorder) GetExecutionPayloadEnvelope(arg0, arg1 any, arg2 ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecutionPayloadEnvelope", reflect.TypeOf((*MockBeaconNodeValidatorClient)(nil).GetExecutionPayloadEnvelope), varargs...)
|
||||
}
|
||||
|
||||
// PublishExecutionPayloadEnvelope mocks base method.
|
||||
func (m *MockBeaconNodeValidatorClient) PublishExecutionPayloadEnvelope(arg0 context.Context, arg1 *eth.SignedExecutionPayloadEnvelope, arg2 ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "PublishExecutionPayloadEnvelope", varargs...)
|
||||
ret0, _ := ret[0].(*emptypb.Empty)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// PublishExecutionPayloadEnvelope indicates an expected call of PublishExecutionPayloadEnvelope.
|
||||
func (mr *MockBeaconNodeValidatorClientMockRecorder) PublishExecutionPayloadEnvelope(arg0, arg1 any, arg2 ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishExecutionPayloadEnvelope", reflect.TypeOf((*MockBeaconNodeValidatorClient)(nil).PublishExecutionPayloadEnvelope), varargs...)
|
||||
}
|
||||
|
||||
// MockBeaconNodeValidator_WaitForChainStartClient is a mock of BeaconNodeValidator_WaitForChainStartClient interface.
|
||||
type MockBeaconNodeValidator_WaitForChainStartClient struct {
|
||||
ctrl *gomock.Controller
|
||||
|
||||
30
testing/mock/beacon_validator_server_mock.go
generated
30
testing/mock/beacon_validator_server_mock.go
generated
@@ -518,6 +518,36 @@ func (mr *MockBeaconNodeValidatorServerMockRecorder) WaitForChainStart(arg0, arg
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForChainStart", reflect.TypeOf((*MockBeaconNodeValidatorServer)(nil).WaitForChainStart), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetExecutionPayloadEnvelope mocks base method.
|
||||
func (m *MockBeaconNodeValidatorServer) GetExecutionPayloadEnvelope(arg0 context.Context, arg1 *eth.ExecutionPayloadEnvelopeRequest) (*eth.ExecutionPayloadEnvelopeResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ExecutionPayloadEnvelope", arg0, arg1)
|
||||
ret0, _ := ret[0].(*eth.ExecutionPayloadEnvelopeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetExecutionPayloadEnvelope indicates an expected call of GetExecutionPayloadEnvelope.
|
||||
func (mr *MockBeaconNodeValidatorServerMockRecorder) GetExecutionPayloadEnvelope(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecutionPayloadEnvelope", reflect.TypeOf((*MockBeaconNodeValidatorServer)(nil).GetExecutionPayloadEnvelope), arg0, arg1)
|
||||
}
|
||||
|
||||
// PublishExecutionPayloadEnvelope mocks base method.
|
||||
func (m *MockBeaconNodeValidatorServer) PublishExecutionPayloadEnvelope(arg0 context.Context, arg1 *eth.SignedExecutionPayloadEnvelope) (*emptypb.Empty, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "PublishExecutionPayloadEnvelope", arg0, arg1)
|
||||
ret0, _ := ret[0].(*emptypb.Empty)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// PublishExecutionPayloadEnvelope indicates an expected call of PublishExecutionPayloadEnvelope.
|
||||
func (mr *MockBeaconNodeValidatorServerMockRecorder) PublishExecutionPayloadEnvelope(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishExecutionPayloadEnvelope", reflect.TypeOf((*MockBeaconNodeValidatorServer)(nil).PublishExecutionPayloadEnvelope), arg0, arg1)
|
||||
}
|
||||
|
||||
// MockBeaconNodeValidator_WaitForActivationServer is a mock of BeaconNodeValidator_WaitForActivationServer interface.
|
||||
type MockBeaconNodeValidator_WaitForActivationServer struct {
|
||||
ctrl *gomock.Controller
|
||||
|
||||
30
testing/validator-mock/validator_client_mock.go
generated
30
testing/validator-mock/validator_client_mock.go
generated
@@ -518,3 +518,33 @@ func (mr *MockValidatorClientMockRecorder) WaitForChainStart(ctx, in any) *gomoc
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForChainStart", reflect.TypeOf((*MockValidatorClient)(nil).WaitForChainStart), ctx, in)
|
||||
}
|
||||
|
||||
// GetExecutionPayloadEnvelope mocks base method.
|
||||
func (m *MockValidatorClient) ExecutionPayloadEnvelope(ctx context.Context, slot primitives.Slot, builderIndex primitives.BuilderIndex) (*eth.ExecutionPayloadEnvelope, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ExecutionPayloadEnvelope", ctx, slot, builderIndex)
|
||||
ret0, _ := ret[0].(*eth.ExecutionPayloadEnvelope)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetExecutionPayloadEnvelope indicates an expected call of GetExecutionPayloadEnvelope.
|
||||
func (mr *MockValidatorClientMockRecorder) GetExecutionPayloadEnvelope(ctx, slot, builderIndex any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecutionPayloadEnvelope", reflect.TypeOf((*MockValidatorClient)(nil).ExecutionPayloadEnvelope), ctx, slot, builderIndex)
|
||||
}
|
||||
|
||||
// PublishExecutionPayloadEnvelope mocks base method.
|
||||
func (m *MockValidatorClient) PublishExecutionPayloadEnvelope(ctx context.Context, in *eth.SignedExecutionPayloadEnvelope) (*emptypb.Empty, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "PublishExecutionPayloadEnvelope", ctx, in)
|
||||
ret0, _ := ret[0].(*emptypb.Empty)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// PublishExecutionPayloadEnvelope indicates an expected call of PublishExecutionPayloadEnvelope.
|
||||
func (mr *MockValidatorClientMockRecorder) PublishExecutionPayloadEnvelope(ctx, in any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishExecutionPayloadEnvelope", reflect.TypeOf((*MockValidatorClient)(nil).PublishExecutionPayloadEnvelope), ctx, in)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ go_library(
|
||||
"log_helpers.go",
|
||||
"metrics.go",
|
||||
"propose.go",
|
||||
"propose_gloas.go",
|
||||
"registration.go",
|
||||
"runner.go",
|
||||
"service.go",
|
||||
@@ -106,6 +107,7 @@ go_test(
|
||||
"key_reload_test.go",
|
||||
"log_test.go",
|
||||
"metrics_test.go",
|
||||
"propose_gloas_test.go",
|
||||
"propose_test.go",
|
||||
"registration_test.go",
|
||||
"runner_test.go",
|
||||
@@ -140,6 +142,7 @@ go_test(
|
||||
"//crypto/bls/common/mock:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//runtime:go_default_library",
|
||||
|
||||
@@ -15,6 +15,7 @@ go_library(
|
||||
"domain_data.go",
|
||||
"doppelganger.go",
|
||||
"duties.go",
|
||||
"execution_payload_envelope.go",
|
||||
"genesis.go",
|
||||
"get_beacon_block.go",
|
||||
"index.go",
|
||||
|
||||
@@ -342,3 +342,23 @@ func (c *beaconApiValidatorClient) Host() string {
|
||||
func (c *beaconApiValidatorClient) EnsureReady(ctx context.Context) bool {
|
||||
return fallback.EnsureReady(ctx, c.restProvider, c.nodeClient)
|
||||
}
|
||||
|
||||
// GLOAS Fork Methods
|
||||
|
||||
func (c *beaconApiValidatorClient) ExecutionPayloadEnvelope(ctx context.Context, slot primitives.Slot, builderIndex primitives.BuilderIndex) (*ethpb.ExecutionPayloadEnvelope, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon-api.ExecutionPayloadEnvelope")
|
||||
defer span.End()
|
||||
|
||||
return wrapInMetrics[*ethpb.ExecutionPayloadEnvelope]("ExecutionPayloadEnvelope", func() (*ethpb.ExecutionPayloadEnvelope, error) {
|
||||
return c.getExecutionPayloadEnvelope(ctx, slot, builderIndex)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) PublishExecutionPayloadEnvelope(ctx context.Context, in *ethpb.SignedExecutionPayloadEnvelope) (*empty.Empty, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon-api.PublishExecutionPayloadEnvelope")
|
||||
defer span.End()
|
||||
|
||||
return wrapInMetrics[*empty.Empty]("PublishExecutionPayloadEnvelope", func() (*empty.Empty, error) {
|
||||
return c.publishExecutionPayloadEnvelope(ctx, in)
|
||||
})
|
||||
}
|
||||
|
||||
29
validator/client/beacon-api/execution_payload_envelope.go
Normal file
29
validator/client/beacon-api/execution_payload_envelope.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// TODO: Implement GLOAS beacon API client methods.
|
||||
|
||||
// getExecutionPayloadEnvelope retrieves the execution payload envelope for the given slot and builder index.
|
||||
func (c *beaconApiValidatorClient) getExecutionPayloadEnvelope(
|
||||
ctx context.Context,
|
||||
slot primitives.Slot,
|
||||
builderIndex primitives.BuilderIndex,
|
||||
) (*ethpb.ExecutionPayloadEnvelope, error) {
|
||||
return nil, errors.New("getExecutionPayloadEnvelope not yet implemented")
|
||||
}
|
||||
|
||||
// publishExecutionPayloadEnvelope broadcasts a signed execution payload envelope.
|
||||
func (c *beaconApiValidatorClient) publishExecutionPayloadEnvelope(
|
||||
ctx context.Context,
|
||||
envelope *ethpb.SignedExecutionPayloadEnvelope,
|
||||
) (*empty.Empty, error) {
|
||||
return nil, errors.New("publishExecutionPayloadEnvelope not yet implemented")
|
||||
}
|
||||
@@ -391,3 +391,24 @@ func (c *grpcValidatorClient) EnsureReady(ctx context.Context) bool {
|
||||
provider := c.grpcClientManager.conn.GetGrpcConnectionProvider()
|
||||
return fallback.EnsureReady(ctx, provider, c.nodeClient)
|
||||
}
|
||||
|
||||
// GLOAS Fork Methods
|
||||
|
||||
func (c *grpcValidatorClient) ExecutionPayloadEnvelope(ctx context.Context, slot primitives.Slot, builderIndex primitives.BuilderIndex) (*ethpb.ExecutionPayloadEnvelope, error) {
|
||||
req := ðpb.ExecutionPayloadEnvelopeRequest{
|
||||
Slot: slot,
|
||||
BuilderIndex: builderIndex,
|
||||
}
|
||||
resp, err := c.getClient().GetExecutionPayloadEnvelope(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(
|
||||
client.ErrConnectionIssue,
|
||||
errors.Wrap(err, "ExecutionPayloadEnvelope").Error(),
|
||||
)
|
||||
}
|
||||
return resp.Envelope, nil
|
||||
}
|
||||
|
||||
func (c *grpcValidatorClient) PublishExecutionPayloadEnvelope(ctx context.Context, in *ethpb.SignedExecutionPayloadEnvelope) (*empty.Empty, error) {
|
||||
return c.getClient().PublishExecutionPayloadEnvelope(ctx, in)
|
||||
}
|
||||
|
||||
@@ -153,4 +153,6 @@ type ValidatorClient interface {
|
||||
AggregatedSyncSelections(ctx context.Context, selections []SyncCommitteeSelection) ([]SyncCommitteeSelection, error)
|
||||
Host() string
|
||||
EnsureReady(ctx context.Context) bool
|
||||
ExecutionPayloadEnvelope(ctx context.Context, slot primitives.Slot, builderIndex primitives.BuilderIndex) (*ethpb.ExecutionPayloadEnvelope, error)
|
||||
PublishExecutionPayloadEnvelope(ctx context.Context, in *ethpb.SignedExecutionPayloadEnvelope) (*empty.Empty, error)
|
||||
}
|
||||
|
||||
@@ -128,7 +128,8 @@ func (v *validator) ProposeBlock(ctx context.Context, slot primitives.Slot, pubK
|
||||
|
||||
var genericSignedBlock *ethpb.GenericSignedBeaconBlock
|
||||
// Special handling for Deneb blocks and later version because of blob side cars.
|
||||
if blk.Version() >= version.Deneb && !blk.IsBlinded() {
|
||||
// GLOAS blocks are handled differently - no blobs in block, execution payload is separate.
|
||||
if blk.Version() >= version.Deneb && blk.Version() < version.Gloas && !blk.IsBlinded() {
|
||||
pb, err := blk.Proto()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get deneb block")
|
||||
@@ -167,6 +168,29 @@ func (v *validator) ProposeBlock(ctx context.Context, slot primitives.Slot, pubK
|
||||
}
|
||||
}
|
||||
|
||||
// For GLOAS, retrieve and sign the execution payload envelope before
|
||||
// broadcasting the block. This ensures we can bail out early if signing
|
||||
// fails, rather than broadcasting a block with no valid envelope to back it.
|
||||
var signedEnvelope *ethpb.SignedExecutionPayloadEnvelope
|
||||
if blk.Version() >= version.Gloas {
|
||||
envelope, err := v.getExecutionPayloadEnvelope(ctx, slot, b)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get execution payload envelope")
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
|
||||
}
|
||||
return
|
||||
}
|
||||
signedEnvelope, err = v.signExecutionPayloadEnvelope(ctx, pubKey, slot, envelope)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to sign execution payload envelope")
|
||||
if v.emitAccountMetrics {
|
||||
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
blkResp, err := v.validatorClient.ProposeBeaconBlock(ctx, genericSignedBlock)
|
||||
if err != nil {
|
||||
log.WithField("slot", slot).WithError(err).Error("Failed to propose block")
|
||||
@@ -176,6 +200,13 @@ func (v *validator) ProposeBlock(ctx context.Context, slot primitives.Slot, pubK
|
||||
return
|
||||
}
|
||||
|
||||
// Publish the signed envelope after block broadcast.
|
||||
if signedEnvelope != nil {
|
||||
if _, err := v.validatorClient.PublishExecutionPayloadEnvelope(ctx, signedEnvelope); err != nil {
|
||||
log.WithError(err).Error("Failed to publish execution payload envelope")
|
||||
}
|
||||
}
|
||||
|
||||
span.SetAttributes(
|
||||
trace.StringAttribute("blockRoot", fmt.Sprintf("%#x", blkResp.BlockRoot)),
|
||||
trace.Int64Attribute("numDeposits", int64(len(blk.Block().Body().Deposits()))),
|
||||
|
||||
82
validator/client/propose_gloas.go
Normal file
82
validator/client/propose_gloas.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
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/monitoring/tracing/trace"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
validatorpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// getExecutionPayloadEnvelope retrieves the execution payload envelope from the
|
||||
// beacon node for the given block's builder index and slot.
|
||||
func (v *validator) getExecutionPayloadEnvelope(
|
||||
ctx context.Context,
|
||||
slot primitives.Slot,
|
||||
b *ethpb.GenericBeaconBlock,
|
||||
) (*ethpb.ExecutionPayloadEnvelope, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "validator.getExecutionPayloadEnvelope")
|
||||
defer span.End()
|
||||
|
||||
gloasBlock := b.GetGloas()
|
||||
if gloasBlock == nil {
|
||||
return nil, errors.New("expected GLOAS block but got nil")
|
||||
}
|
||||
if gloasBlock.Body == nil || gloasBlock.Body.SignedExecutionPayloadBid == nil || gloasBlock.Body.SignedExecutionPayloadBid.Message == nil {
|
||||
return nil, errors.New("block missing signed execution payload bid")
|
||||
}
|
||||
builderIndex := gloasBlock.Body.SignedExecutionPayloadBid.Message.BuilderIndex
|
||||
|
||||
return v.validatorClient.ExecutionPayloadEnvelope(ctx, slot, builderIndex)
|
||||
}
|
||||
|
||||
// signExecutionPayloadEnvelope signs the execution payload envelope using the
|
||||
// builder's key. The envelope is signed with DomainBeaconBuilder since it is
|
||||
// a builder artifact — even in the self-build case where the proposer acts as
|
||||
// their own builder.
|
||||
func (v *validator) signExecutionPayloadEnvelope(
|
||||
ctx context.Context,
|
||||
pubKey [fieldparams.BLSPubkeyLength]byte,
|
||||
slot primitives.Slot,
|
||||
envelope *ethpb.ExecutionPayloadEnvelope,
|
||||
) (*ethpb.SignedExecutionPayloadEnvelope, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "validator.signExecutionPayloadEnvelope")
|
||||
defer span.End()
|
||||
|
||||
epoch := slots.ToEpoch(slot)
|
||||
|
||||
domain, err := v.domainData(ctx, epoch, params.BeaconConfig().DomainBeaconBuilder[:])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get domain data")
|
||||
}
|
||||
if domain == nil {
|
||||
return nil, errors.New("nil domain data")
|
||||
}
|
||||
|
||||
signingRoot, err := signing.ComputeSigningRoot(envelope, domain.SignatureDomain)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not compute signing root")
|
||||
}
|
||||
|
||||
sig, err := v.km.Sign(ctx, &validatorpb.SignRequest{
|
||||
PublicKey: pubKey[:],
|
||||
SigningRoot: signingRoot[:],
|
||||
SignatureDomain: domain.SignatureDomain,
|
||||
Object: &validatorpb.SignRequest_Slot{Slot: slot},
|
||||
SigningSlot: slot,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not sign execution payload envelope")
|
||||
}
|
||||
|
||||
return ðpb.SignedExecutionPayloadEnvelope{
|
||||
Message: envelope,
|
||||
Signature: sig.Marshal(),
|
||||
}, nil
|
||||
}
|
||||
205
validator/client/propose_gloas_test.go
Normal file
205
validator/client/propose_gloas_test.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func testExecutionPayloadEnvelope(slot primitives.Slot, builderIndex primitives.BuilderIndex) *ethpb.ExecutionPayloadEnvelope {
|
||||
return ð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),
|
||||
ExtraData: make([]byte, 0),
|
||||
},
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
Slot: slot,
|
||||
BuilderIndex: builderIndex,
|
||||
BeaconBlockRoot: make([]byte, 32),
|
||||
StateRoot: make([]byte, 32),
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExecutionPayloadEnvelope(t *testing.T) {
|
||||
validator, m, _, finish := setup(t, false)
|
||||
defer finish()
|
||||
|
||||
slot := primitives.Slot(100)
|
||||
builderIndex := primitives.BuilderIndex(42)
|
||||
|
||||
expectedEnvelope := testExecutionPayloadEnvelope(slot, builderIndex)
|
||||
|
||||
m.validatorClient.EXPECT().
|
||||
GetExecutionPayloadEnvelope(gomock.Any(), slot, builderIndex).
|
||||
Return(expectedEnvelope, nil)
|
||||
|
||||
b := ðpb.GenericBeaconBlock{
|
||||
Block: ðpb.GenericBeaconBlock_Gloas{
|
||||
Gloas: ðpb.BeaconBlockGloas{
|
||||
Slot: slot,
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
SignedExecutionPayloadBid: ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BuilderIndex: builderIndex,
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
envelope, err := validator.getExecutionPayloadEnvelope(t.Context(), slot, b)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, expectedEnvelope, envelope)
|
||||
}
|
||||
|
||||
func TestGetExecutionPayloadEnvelope_NilBlock(t *testing.T) {
|
||||
validator, _, _, finish := setup(t, false)
|
||||
defer finish()
|
||||
|
||||
b := ðpb.GenericBeaconBlock{
|
||||
Block: ðpb.GenericBeaconBlock_Gloas{},
|
||||
}
|
||||
|
||||
_, err := validator.getExecutionPayloadEnvelope(t.Context(), 1, b)
|
||||
require.ErrorContains(t, "expected GLOAS block but got nil", err)
|
||||
}
|
||||
|
||||
func TestGetExecutionPayloadEnvelope_MissingBid(t *testing.T) {
|
||||
validator, _, _, finish := setup(t, false)
|
||||
defer finish()
|
||||
|
||||
b := ðpb.GenericBeaconBlock{
|
||||
Block: ðpb.GenericBeaconBlock_Gloas{
|
||||
Gloas: ðpb.BeaconBlockGloas{
|
||||
Slot: 1,
|
||||
Body: ðpb.BeaconBlockBodyGloas{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := validator.getExecutionPayloadEnvelope(t.Context(), 1, b)
|
||||
require.ErrorContains(t, "block missing signed execution payload bid", err)
|
||||
}
|
||||
|
||||
func TestGetExecutionPayloadEnvelope_ClientError(t *testing.T) {
|
||||
validator, m, _, finish := setup(t, false)
|
||||
defer finish()
|
||||
|
||||
m.validatorClient.EXPECT().
|
||||
GetExecutionPayloadEnvelope(gomock.Any(), gomock.Any(), gomock.Any()).
|
||||
Return(nil, errors.New("connection refused"))
|
||||
|
||||
b := ðpb.GenericBeaconBlock{
|
||||
Block: ðpb.GenericBeaconBlock_Gloas{
|
||||
Gloas: ðpb.BeaconBlockGloas{
|
||||
Slot: 1,
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
SignedExecutionPayloadBid: ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{BuilderIndex: 1},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := validator.getExecutionPayloadEnvelope(t.Context(), 1, b)
|
||||
require.ErrorContains(t, "connection refused", err)
|
||||
}
|
||||
|
||||
func TestSignExecutionPayloadEnvelope(t *testing.T) {
|
||||
validator, m, _, finish := setup(t, false)
|
||||
defer finish()
|
||||
|
||||
kp := testKeyFromBytes(t, []byte{1})
|
||||
validator.km = newMockKeymanager(t, kp)
|
||||
|
||||
builderDomain := make([]byte, 32)
|
||||
m.validatorClient.EXPECT().
|
||||
DomainData(gomock.Any(), gomock.Any()).
|
||||
Return(ðpb.DomainResponse{SignatureDomain: builderDomain}, nil)
|
||||
|
||||
envelope := testExecutionPayloadEnvelope(100, 42)
|
||||
|
||||
signed, err := validator.signExecutionPayloadEnvelope(t.Context(), kp.pub, 100, envelope)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, signed)
|
||||
require.DeepEqual(t, envelope, signed.Message)
|
||||
require.NotNil(t, signed.Signature)
|
||||
|
||||
// Verify the signature was computed with the builder domain.
|
||||
expectedRoot, err := signing.ComputeSigningRoot(envelope, builderDomain)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, [32]byte{}, expectedRoot)
|
||||
}
|
||||
|
||||
func TestSignExecutionPayloadEnvelope_DomainDataError(t *testing.T) {
|
||||
validator, m, _, finish := setup(t, false)
|
||||
defer finish()
|
||||
|
||||
kp := testKeyFromBytes(t, []byte{1})
|
||||
validator.km = newMockKeymanager(t, kp)
|
||||
|
||||
m.validatorClient.EXPECT().
|
||||
DomainData(gomock.Any(), gomock.Any()).
|
||||
Return(nil, errors.New("domain data unavailable"))
|
||||
|
||||
envelope := testExecutionPayloadEnvelope(100, 0)
|
||||
|
||||
_, err := validator.signExecutionPayloadEnvelope(t.Context(), kp.pub, 100, envelope)
|
||||
require.ErrorContains(t, "could not get domain data", err)
|
||||
}
|
||||
|
||||
func TestSignExecutionPayloadEnvelope_NilDomain(t *testing.T) {
|
||||
validator, m, _, finish := setup(t, false)
|
||||
defer finish()
|
||||
|
||||
kp := testKeyFromBytes(t, []byte{1})
|
||||
validator.km = newMockKeymanager(t, kp)
|
||||
|
||||
m.validatorClient.EXPECT().
|
||||
DomainData(gomock.Any(), gomock.Any()).
|
||||
Return(nil, nil)
|
||||
|
||||
envelope := testExecutionPayloadEnvelope(100, 0)
|
||||
|
||||
_, err := validator.signExecutionPayloadEnvelope(t.Context(), kp.pub, 100, envelope)
|
||||
require.ErrorContains(t, "nil domain data", err)
|
||||
}
|
||||
|
||||
func TestSignExecutionPayloadEnvelope_UsesDomainBeaconBuilder(t *testing.T) {
|
||||
validator, m, _, finish := setup(t, false)
|
||||
defer finish()
|
||||
|
||||
kp := testKeyFromBytes(t, []byte{1})
|
||||
validator.km = newMockKeymanager(t, kp)
|
||||
|
||||
// Verify the correct domain type is requested.
|
||||
m.validatorClient.EXPECT().
|
||||
DomainData(gomock.Any(), gomock.Any()).
|
||||
DoAndReturn(func(ctx any, req *ethpb.DomainRequest) (*ethpb.DomainResponse, error) {
|
||||
require.DeepEqual(t, params.BeaconConfig().DomainBeaconBuilder[:], req.Domain)
|
||||
return ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil
|
||||
})
|
||||
|
||||
envelope := testExecutionPayloadEnvelope(100, 0)
|
||||
|
||||
_, err := validator.signExecutionPayloadEnvelope(t.Context(), kp.pub, 100, envelope)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
Reference in New Issue
Block a user