mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-02-12 05:54:57 -05:00
Compare commits
2 Commits
gloas/fork
...
gossip-pay
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2295c62f8f | ||
|
|
94091e5ebd |
@@ -27,6 +27,7 @@ go_library(
|
||||
"receive_blob.go",
|
||||
"receive_block.go",
|
||||
"receive_data_column.go",
|
||||
"receive_execution_payload_envelope.go",
|
||||
"receive_payload_attestation_message.go",
|
||||
"service.go",
|
||||
"setup_forkchoice.go",
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
)
|
||||
|
||||
// ExecutionPayloadEnvelopeReceiver interface defines the methods of chain service for receiving
|
||||
// validated execution payload envelopes.
|
||||
type ExecutionPayloadEnvelopeReceiver interface {
|
||||
ReceiveExecutionPayloadEnvelope(context.Context, interfaces.ROSignedExecutionPayloadEnvelope) error
|
||||
}
|
||||
|
||||
// ReceiveExecutionPayloadEnvelope accepts a signed execution payload envelope.
|
||||
func (s *Service) ReceiveExecutionPayloadEnvelope(_ context.Context, _ interfaces.ROSignedExecutionPayloadEnvelope) error {
|
||||
// TODO: wire into execution payload envelope processing pipeline.
|
||||
return nil
|
||||
}
|
||||
@@ -762,6 +762,11 @@ func (c *ChainService) ReceivePayloadAttestationMessage(_ context.Context, _ *et
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReceiveExecutionPayloadEnvelope implements the same method in the chain service.
|
||||
func (c *ChainService) ReceiveExecutionPayloadEnvelope(_ context.Context, _ interfaces.ROSignedExecutionPayloadEnvelope) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DependentRootForEpoch mocks the same method in the chain service
|
||||
func (c *ChainService) DependentRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
|
||||
return c.TargetRoot, nil
|
||||
|
||||
@@ -26,6 +26,7 @@ var gossipTopicMappings = map[string]func() proto.Message{
|
||||
LightClientFinalityUpdateTopicFormat: func() proto.Message { return ðpb.LightClientFinalityUpdateAltair{} },
|
||||
DataColumnSubnetTopicFormat: func() proto.Message { return ðpb.DataColumnSidecar{} },
|
||||
PayloadAttestationMessageTopicFormat: func() proto.Message { return ðpb.PayloadAttestationMessage{} },
|
||||
ExecutionPayloadEnvelopeTopicFormat: func() proto.Message { return ðpb.SignedExecutionPayloadEnvelope{} },
|
||||
}
|
||||
|
||||
// GossipTopicMappings is a function to return the assigned data type
|
||||
|
||||
@@ -48,6 +48,8 @@ const (
|
||||
GossipDataColumnSidecarMessage = "data_column_sidecar"
|
||||
// GossipPayloadAttestationMessage is the name for the payload attestation message type.
|
||||
GossipPayloadAttestationMessage = "payload_attestation_message"
|
||||
// GossipExecutionPayloadEnvelopeMessage is the name for the execution payload envelope message type.
|
||||
GossipExecutionPayloadEnvelopeMessage = "execution_payload_envelope"
|
||||
|
||||
// Topic Formats
|
||||
//
|
||||
@@ -79,6 +81,8 @@ const (
|
||||
DataColumnSubnetTopicFormat = GossipProtocolAndDigest + GossipDataColumnSidecarMessage + "_%d"
|
||||
// PayloadAttestationMessageTopicFormat is the topic format for payload attestation messages.
|
||||
PayloadAttestationMessageTopicFormat = GossipProtocolAndDigest + GossipPayloadAttestationMessage
|
||||
// ExecutionPayloadEnvelopeTopicFormat is the topic format for execution payload envelopes.
|
||||
ExecutionPayloadEnvelopeTopicFormat = GossipProtocolAndDigest + GossipExecutionPayloadEnvelopeMessage
|
||||
)
|
||||
|
||||
// topic is a struct representing a single gossipsub topic.
|
||||
@@ -163,6 +167,7 @@ func (s *Service) allTopics() []topic {
|
||||
newTopic(altair, future, empty, GossipLightClientFinalityUpdateMessage),
|
||||
newTopic(capella, future, empty, GossipBlsToExecutionChangeMessage),
|
||||
newTopic(gloas, future, empty, GossipPayloadAttestationMessage),
|
||||
newTopic(gloas, future, empty, GossipExecutionPayloadEnvelopeMessage),
|
||||
}
|
||||
last := params.GetNetworkScheduleEntry(genesis)
|
||||
schedule := []params.NetworkScheduleEntry{last}
|
||||
|
||||
@@ -58,6 +58,7 @@ go_library(
|
||||
"validate_blob.go",
|
||||
"validate_bls_to_execution_change.go",
|
||||
"validate_data_column.go",
|
||||
"validate_execution_payload_envelope.go",
|
||||
"validate_light_client.go",
|
||||
"validate_payload_attestation.go",
|
||||
"validate_proposer_slashing.go",
|
||||
@@ -214,6 +215,7 @@ go_test(
|
||||
"validate_blob_test.go",
|
||||
"validate_bls_to_execution_change_test.go",
|
||||
"validate_data_column_test.go",
|
||||
"validate_execution_payload_envelope_test.go",
|
||||
"validate_light_client_test.go",
|
||||
"validate_payload_attestation_test.go",
|
||||
"validate_proposer_slashing_test.go",
|
||||
|
||||
@@ -62,6 +62,7 @@ var _ runtime.Service = (*Service)(nil)
|
||||
const (
|
||||
rangeLimit uint64 = 1024
|
||||
seenBlockSize = 1000
|
||||
seenPayloadEnvelopeSize = 1000
|
||||
seenDataColumnSize = seenBlockSize * 128 // Each block can have max 128 data columns.
|
||||
seenUnaggregatedAttSize = 20000
|
||||
seenAggregatedAttSize = 16384
|
||||
@@ -118,6 +119,7 @@ type blockchainService interface {
|
||||
blockchain.BlockReceiver
|
||||
blockchain.BlobReceiver
|
||||
blockchain.DataColumnReceiver
|
||||
blockchain.ExecutionPayloadEnvelopeReceiver
|
||||
blockchain.HeadFetcher
|
||||
blockchain.FinalizationFetcher
|
||||
blockchain.ForkFetcher
|
||||
@@ -134,60 +136,63 @@ type blockchainService interface {
|
||||
// Service is responsible for handling all run time p2p related operations as the
|
||||
// main entry point for network messages.
|
||||
type Service struct {
|
||||
cfg *config
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
slotToPendingBlocks *gcache.Cache
|
||||
seenPendingBlocks map[[32]byte]bool
|
||||
blkRootToPendingAtts map[[32]byte][]any
|
||||
subHandler *subTopicHandler
|
||||
pendingAttsLock sync.RWMutex
|
||||
pendingQueueLock sync.RWMutex
|
||||
chainStarted *abool.AtomicBool
|
||||
validateBlockLock sync.RWMutex
|
||||
rateLimiter *limiter
|
||||
seenBlockLock sync.RWMutex
|
||||
seenBlockCache *lru.Cache
|
||||
seenBlobLock sync.RWMutex
|
||||
seenBlobCache *lru.Cache
|
||||
seenDataColumnCache *slotAwareCache
|
||||
seenAggregatedAttestationLock sync.RWMutex
|
||||
seenAggregatedAttestationCache *lru.Cache
|
||||
seenUnAggregatedAttestationLock sync.RWMutex
|
||||
seenUnAggregatedAttestationCache *lru.Cache
|
||||
seenExitLock sync.RWMutex
|
||||
seenExitCache *lru.Cache
|
||||
seenProposerSlashingLock sync.RWMutex
|
||||
seenProposerSlashingCache *lru.Cache
|
||||
seenAttesterSlashingLock sync.RWMutex
|
||||
seenAttesterSlashingCache map[uint64]bool
|
||||
seenSyncMessageLock sync.RWMutex
|
||||
seenSyncMessageCache *lru.Cache
|
||||
seenSyncContributionLock sync.RWMutex
|
||||
seenSyncContributionCache *lru.Cache
|
||||
badBlockCache *lru.Cache
|
||||
badBlockLock sync.RWMutex
|
||||
syncContributionBitsOverlapLock sync.RWMutex
|
||||
syncContributionBitsOverlapCache *lru.Cache
|
||||
signatureChan chan *signatureVerifier
|
||||
clockWaiter startup.ClockWaiter
|
||||
initialSyncComplete chan struct{}
|
||||
verifierWaiter *verification.InitializerWaiter
|
||||
newBlobVerifier verification.NewBlobVerifier
|
||||
newColumnsVerifier verification.NewDataColumnsVerifier
|
||||
newPayloadAttestationVerifier verification.NewPayloadAttestationMsgVerifier
|
||||
columnSidecarsExecSingleFlight singleflight.Group
|
||||
reconstructionSingleFlight singleflight.Group
|
||||
availableBlocker coverage.AvailableBlocker
|
||||
reconstructionRandGen *rand.Rand
|
||||
trackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
ctxMap ContextByteVersions
|
||||
slasherEnabled bool
|
||||
lcStore *lightClient.Store
|
||||
dataColumnLogCh chan dataColumnLogEntry
|
||||
payloadAttestationCache *cache.PayloadAttestationCache
|
||||
digestActions perDigestSet
|
||||
subscriptionSpawner func(func()) // see Service.spawn for details
|
||||
cfg *config
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
slotToPendingBlocks *gcache.Cache
|
||||
seenPendingBlocks map[[32]byte]bool
|
||||
blkRootToPendingAtts map[[32]byte][]any
|
||||
subHandler *subTopicHandler
|
||||
pendingAttsLock sync.RWMutex
|
||||
pendingQueueLock sync.RWMutex
|
||||
chainStarted *abool.AtomicBool
|
||||
validateBlockLock sync.RWMutex
|
||||
rateLimiter *limiter
|
||||
seenBlockLock sync.RWMutex
|
||||
seenBlockCache *lru.Cache
|
||||
seenPayloadEnvelopeLock sync.RWMutex
|
||||
seenPayloadEnvelopeCache *lru.Cache
|
||||
seenBlobLock sync.RWMutex
|
||||
seenBlobCache *lru.Cache
|
||||
seenDataColumnCache *slotAwareCache
|
||||
seenAggregatedAttestationLock sync.RWMutex
|
||||
seenAggregatedAttestationCache *lru.Cache
|
||||
seenUnAggregatedAttestationLock sync.RWMutex
|
||||
seenUnAggregatedAttestationCache *lru.Cache
|
||||
seenExitLock sync.RWMutex
|
||||
seenExitCache *lru.Cache
|
||||
seenProposerSlashingLock sync.RWMutex
|
||||
seenProposerSlashingCache *lru.Cache
|
||||
seenAttesterSlashingLock sync.RWMutex
|
||||
seenAttesterSlashingCache map[uint64]bool
|
||||
seenSyncMessageLock sync.RWMutex
|
||||
seenSyncMessageCache *lru.Cache
|
||||
seenSyncContributionLock sync.RWMutex
|
||||
seenSyncContributionCache *lru.Cache
|
||||
badBlockCache *lru.Cache
|
||||
badBlockLock sync.RWMutex
|
||||
syncContributionBitsOverlapLock sync.RWMutex
|
||||
syncContributionBitsOverlapCache *lru.Cache
|
||||
signatureChan chan *signatureVerifier
|
||||
clockWaiter startup.ClockWaiter
|
||||
initialSyncComplete chan struct{}
|
||||
verifierWaiter *verification.InitializerWaiter
|
||||
newBlobVerifier verification.NewBlobVerifier
|
||||
newColumnsVerifier verification.NewDataColumnsVerifier
|
||||
newPayloadAttestationVerifier verification.NewPayloadAttestationMsgVerifier
|
||||
columnSidecarsExecSingleFlight singleflight.Group
|
||||
reconstructionSingleFlight singleflight.Group
|
||||
availableBlocker coverage.AvailableBlocker
|
||||
reconstructionRandGen *rand.Rand
|
||||
trackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
ctxMap ContextByteVersions
|
||||
slasherEnabled bool
|
||||
lcStore *lightClient.Store
|
||||
dataColumnLogCh chan dataColumnLogEntry
|
||||
payloadAttestationCache *cache.PayloadAttestationCache
|
||||
digestActions perDigestSet
|
||||
subscriptionSpawner func(func()) // see Service.spawn for details
|
||||
newExecutionPayloadEnvelopeVerifier verification.NewExecutionPayloadEnvelopeVerifier
|
||||
}
|
||||
|
||||
// NewService initializes new regular sync service.
|
||||
@@ -271,6 +276,7 @@ func (s *Service) Start() {
|
||||
s.newBlobVerifier = newBlobVerifierFromInitializer(v)
|
||||
s.newColumnsVerifier = newDataColumnsVerifierFromInitializer(v)
|
||||
s.newPayloadAttestationVerifier = newPayloadAttestationMessageFromInitializer(v)
|
||||
s.newExecutionPayloadEnvelopeVerifier = newPayloadVerifierFromInitializer(v)
|
||||
|
||||
go s.verifierRoutine()
|
||||
go s.startDiscoveryAndSubscriptions()
|
||||
@@ -358,6 +364,7 @@ func (s *Service) Status() error {
|
||||
// and prevent DoS.
|
||||
func (s *Service) initCaches() {
|
||||
s.seenBlockCache = lruwrpr.New(seenBlockSize)
|
||||
s.seenPayloadEnvelopeCache = lruwrpr.New(seenPayloadEnvelopeSize)
|
||||
s.seenBlobCache = lruwrpr.New(seenBlockSize * params.BeaconConfig().DeprecatedMaxBlobsPerBlockElectra)
|
||||
s.seenDataColumnCache = newSlotAwareCache(seenDataColumnSize)
|
||||
s.seenAggregatedAttestationCache = lruwrpr.New(seenAggregatedAttSize)
|
||||
@@ -558,3 +565,9 @@ type Checker interface {
|
||||
Status() error
|
||||
Resync() error
|
||||
}
|
||||
|
||||
func newPayloadVerifierFromInitializer(ini *verification.Initializer) verification.NewExecutionPayloadEnvelopeVerifier {
|
||||
return func(e interfaces.ROSignedExecutionPayloadEnvelope, reqs []verification.Requirement) verification.ExecutionPayloadEnvelopeVerifier {
|
||||
return ini.NewPayloadEnvelopeVerifier(e, reqs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,6 +341,15 @@ func (s *Service) registerSubscribers(nse params.NetworkScheduleEntry) bool {
|
||||
nse,
|
||||
)
|
||||
})
|
||||
|
||||
s.spawn(func() {
|
||||
s.subscribe(
|
||||
p2p.ExecutionPayloadEnvelopeTopicFormat,
|
||||
s.validateExecutionPayloadEnvelope,
|
||||
s.executionPayloadEnvelopeSubscriber,
|
||||
nse,
|
||||
)
|
||||
})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
160
beacon-chain/sync/validate_execution_payload_envelope.go
Normal file
160
beacon-chain/sync/validate_execution_payload_envelope.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/verification"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/OffchainLabs/prysm/v7/monitoring/tracing"
|
||||
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func (s *Service) validateExecutionPayloadEnvelope(ctx context.Context, pid peer.ID, msg *pubsub.Message) (pubsub.ValidationResult, error) {
|
||||
if pid == s.cfg.p2p.PeerID() {
|
||||
return pubsub.ValidationAccept, nil
|
||||
}
|
||||
if s.cfg.initialSync.Syncing() {
|
||||
return pubsub.ValidationIgnore, nil
|
||||
}
|
||||
|
||||
ctx, span := trace.StartSpan(ctx, "sync.validateExecutionPayloadEnvelope")
|
||||
defer span.End()
|
||||
|
||||
if msg.Topic == nil {
|
||||
return pubsub.ValidationReject, p2p.ErrInvalidTopic
|
||||
}
|
||||
|
||||
m, err := s.decodePubsubMessage(msg)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return pubsub.ValidationReject, err
|
||||
}
|
||||
|
||||
signedEnvelope, ok := m.(*ethpb.SignedExecutionPayloadEnvelope)
|
||||
if !ok {
|
||||
return pubsub.ValidationReject, errWrongMessage
|
||||
}
|
||||
e, err := blocks.WrappedROSignedExecutionPayloadEnvelope(signedEnvelope)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to create read only signed payload execution envelope")
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
v := s.newExecutionPayloadEnvelopeVerifier(e, verification.GossipExecutionPayloadEnvelopeRequirements)
|
||||
|
||||
env, err := e.Envelope()
|
||||
if err != nil {
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
|
||||
// [IGNORE] The envelope's block root envelope.block_root has been seen (via gossip or non-gossip sources)
|
||||
// (a client MAY queue payload for processing once the block is retrieved).
|
||||
if err := v.VerifyBlockRootSeen(func(root [32]byte) bool { return s.cfg.chain.HasBlock(ctx, root) }); err != nil {
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
root := env.BeaconBlockRoot()
|
||||
// [IGNORE] The node has not seen another valid SignedExecutionPayloadEnvelope for this block root from this builder.
|
||||
if s.hasSeenPayloadEnvelope(root, env.BuilderIndex()) {
|
||||
return pubsub.ValidationIgnore, nil
|
||||
}
|
||||
finalized := s.cfg.chain.FinalizedCheckpt()
|
||||
if finalized == nil {
|
||||
return pubsub.ValidationIgnore, errors.New("nil finalized checkpoint")
|
||||
}
|
||||
// [IGNORE] The envelope is from a slot greater than or equal to the latest finalized slot --
|
||||
// i.e. validate that envelope.slot >= compute_start_slot_at_epoch(store.finalized_checkpoint.epoch).
|
||||
if err := v.VerifySlotAboveFinalized(finalized.Epoch); err != nil {
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
// [REJECT] block passes validation.
|
||||
if err := v.VerifyBlockRootValid(s.hasBadBlock); err != nil {
|
||||
return pubsub.ValidationReject, err
|
||||
}
|
||||
|
||||
// Let block be the block with envelope.beacon_block_root.
|
||||
block, err := s.cfg.beaconDB.Block(ctx, root)
|
||||
if err != nil {
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
// [REJECT] block.slot equals envelope.slot.
|
||||
if err := v.VerifySlotMatchesBlock(block.Block().Slot()); err != nil {
|
||||
return pubsub.ValidationReject, err
|
||||
}
|
||||
|
||||
// Let bid alias block.body.signed_execution_payload_bid.message
|
||||
// (notice that this can be obtained from the state.latest_execution_payload_bid).
|
||||
signedBid, err := block.Block().Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
wrappedBid, err := blocks.WrappedROSignedExecutionPayloadBid(signedBid)
|
||||
if err != nil {
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
bid, err := wrappedBid.Bid()
|
||||
if err != nil {
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
// [REJECT] envelope.builder_index == bid.builder_index.
|
||||
if err := v.VerifyBuilderValid(bid); err != nil {
|
||||
return pubsub.ValidationReject, err
|
||||
}
|
||||
// [REJECT] payload.block_hash == bid.block_hash.
|
||||
if err := v.VerifyPayloadHash(bid); err != nil {
|
||||
return pubsub.ValidationReject, err
|
||||
}
|
||||
|
||||
// For self-build, the state is retrived via how we retrieve for beacon block optimization
|
||||
// For builder index, the state is retrived via head state read only
|
||||
st, err := s.blockVerifyingState(ctx, block)
|
||||
if err != nil {
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
|
||||
// [REJECT] signed_execution_payload_envelope.signature is valid with respect to the builder's public key.
|
||||
if err := v.VerifySignature(st); err != nil {
|
||||
return pubsub.ValidationReject, err
|
||||
}
|
||||
s.setSeenPayloadEnvelope(root, env.BuilderIndex())
|
||||
return pubsub.ValidationAccept, nil
|
||||
}
|
||||
|
||||
func (s *Service) executionPayloadEnvelopeSubscriber(ctx context.Context, msg proto.Message) error {
|
||||
e, ok := msg.(*ethpb.SignedExecutionPayloadEnvelope)
|
||||
if !ok {
|
||||
return errWrongMessage
|
||||
}
|
||||
env, err := blocks.WrappedROSignedExecutionPayloadEnvelope(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.cfg.chain.ReceiveExecutionPayloadEnvelope(ctx, env)
|
||||
}
|
||||
|
||||
func (s *Service) hasSeenPayloadEnvelope(root [32]byte, builderIdx primitives.BuilderIndex) bool {
|
||||
if s.seenPayloadEnvelopeCache == nil {
|
||||
return false
|
||||
}
|
||||
s.seenPayloadEnvelopeLock.RLock()
|
||||
defer s.seenPayloadEnvelopeLock.RUnlock()
|
||||
b := append(bytesutil.Bytes32(uint64(builderIdx)), root[:]...)
|
||||
_, seen := s.seenPayloadEnvelopeCache.Get(string(b))
|
||||
return seen
|
||||
}
|
||||
|
||||
func (s *Service) setSeenPayloadEnvelope(root [32]byte, builderIdx primitives.BuilderIndex) {
|
||||
if s.seenPayloadEnvelopeCache == nil {
|
||||
return
|
||||
}
|
||||
s.seenPayloadEnvelopeLock.Lock()
|
||||
defer s.seenPayloadEnvelopeLock.Unlock()
|
||||
b := append(bytesutil.Bytes32(uint64(builderIdx)), root[:]...)
|
||||
s.seenPayloadEnvelopeCache.Add(string(b), true)
|
||||
}
|
||||
275
beacon-chain/sync/validate_execution_payload_envelope_test.go
Normal file
275
beacon-chain/sync/validate_execution_payload_envelope_test.go
Normal file
@@ -0,0 +1,275 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
mock "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing"
|
||||
dbtest "github.com/OffchainLabs/prysm/v7/beacon-chain/db/testing"
|
||||
doublylinkedtree "github.com/OffchainLabs/prysm/v7/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p"
|
||||
p2ptest "github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/testing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/startup"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/stategen"
|
||||
mockSync "github.com/OffchainLabs/prysm/v7/beacon-chain/sync/initial-sync/testing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/verification"
|
||||
lruwrpr "github.com/OffchainLabs/prysm/v7/cache/lru"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
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/OffchainLabs/prysm/v7/testing/util"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
pb "github.com/libp2p/go-libp2p-pubsub/pb"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestValidateExecutionPayloadEnvelope_InvalidTopic(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := p2ptest.NewTestP2P(t)
|
||||
s := &Service{cfg: &config{p2p: p, initialSync: &mockSync.Sync{}}}
|
||||
|
||||
result, err := s.validateExecutionPayloadEnvelope(ctx, "", &pubsub.Message{
|
||||
Message: &pb.Message{},
|
||||
})
|
||||
require.ErrorIs(t, p2p.ErrInvalidTopic, err)
|
||||
require.Equal(t, result, pubsub.ValidationReject)
|
||||
}
|
||||
|
||||
func TestValidateExecutionPayloadEnvelope_AlreadySeen(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
s, msg, builderIdx, root := setupExecutionPayloadEnvelopeService(t, 1, 1)
|
||||
s.newExecutionPayloadEnvelopeVerifier = testNewExecutionPayloadEnvelopeVerifier(mockExecutionPayloadEnvelopeVerifier{})
|
||||
|
||||
s.setSeenPayloadEnvelope(root, builderIdx)
|
||||
result, err := s.validateExecutionPayloadEnvelope(ctx, "", msg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, result, pubsub.ValidationIgnore)
|
||||
}
|
||||
|
||||
func TestValidateExecutionPayloadEnvelope_ErrorPathsWithMock(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
tests := []struct {
|
||||
name string
|
||||
verifier mockExecutionPayloadEnvelopeVerifier
|
||||
result pubsub.ValidationResult
|
||||
}{
|
||||
{
|
||||
name: "block root not seen",
|
||||
verifier: mockExecutionPayloadEnvelopeVerifier{errBlockRootSeen: errors.New("not seen")},
|
||||
result: pubsub.ValidationIgnore,
|
||||
},
|
||||
{
|
||||
name: "slot below finalized",
|
||||
verifier: mockExecutionPayloadEnvelopeVerifier{errSlotAboveFinalized: errors.New("below finalized")},
|
||||
result: pubsub.ValidationIgnore,
|
||||
},
|
||||
{
|
||||
name: "block root invalid",
|
||||
verifier: mockExecutionPayloadEnvelopeVerifier{errBlockRootValid: errors.New("invalid block")},
|
||||
result: pubsub.ValidationReject,
|
||||
},
|
||||
{
|
||||
name: "slot mismatch",
|
||||
verifier: mockExecutionPayloadEnvelopeVerifier{errSlotMatchesBlock: errors.New("slot mismatch")},
|
||||
result: pubsub.ValidationReject,
|
||||
},
|
||||
{
|
||||
name: "builder mismatch",
|
||||
verifier: mockExecutionPayloadEnvelopeVerifier{errBuilderValid: errors.New("builder mismatch")},
|
||||
result: pubsub.ValidationReject,
|
||||
},
|
||||
{
|
||||
name: "payload hash mismatch",
|
||||
verifier: mockExecutionPayloadEnvelopeVerifier{errPayloadHash: errors.New("payload hash mismatch")},
|
||||
result: pubsub.ValidationReject,
|
||||
},
|
||||
{
|
||||
name: "signature invalid",
|
||||
verifier: mockExecutionPayloadEnvelopeVerifier{errSignature: errors.New("signature invalid")},
|
||||
result: pubsub.ValidationReject,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s, msg, _, _ := setupExecutionPayloadEnvelopeService(t, 1, 1)
|
||||
s.newExecutionPayloadEnvelopeVerifier = testNewExecutionPayloadEnvelopeVerifier(tc.verifier)
|
||||
|
||||
result, err := s.validateExecutionPayloadEnvelope(ctx, "", msg)
|
||||
require.NotNil(t, err)
|
||||
require.Equal(t, result, tc.result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateExecutionPayloadEnvelope_HappyPath(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
s, msg, builderIdx, root := setupExecutionPayloadEnvelopeService(t, 1, 1)
|
||||
s.newExecutionPayloadEnvelopeVerifier = testNewExecutionPayloadEnvelopeVerifier(mockExecutionPayloadEnvelopeVerifier{})
|
||||
|
||||
require.Equal(t, false, s.hasSeenPayloadEnvelope(root, builderIdx))
|
||||
result, err := s.validateExecutionPayloadEnvelope(ctx, "", msg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, result, pubsub.ValidationAccept)
|
||||
require.Equal(t, true, s.hasSeenPayloadEnvelope(root, builderIdx))
|
||||
}
|
||||
|
||||
type mockExecutionPayloadEnvelopeVerifier struct {
|
||||
errBlockRootSeen error
|
||||
errBlockRootValid error
|
||||
errSlotAboveFinalized error
|
||||
errSlotMatchesBlock error
|
||||
errBuilderValid error
|
||||
errPayloadHash error
|
||||
errSignature error
|
||||
}
|
||||
|
||||
var _ verification.ExecutionPayloadEnvelopeVerifier = &mockExecutionPayloadEnvelopeVerifier{}
|
||||
|
||||
func (m *mockExecutionPayloadEnvelopeVerifier) VerifyBlockRootSeen(_ func([32]byte) bool) error {
|
||||
return m.errBlockRootSeen
|
||||
}
|
||||
|
||||
func (m *mockExecutionPayloadEnvelopeVerifier) VerifyBlockRootValid(_ func([32]byte) bool) error {
|
||||
return m.errBlockRootValid
|
||||
}
|
||||
|
||||
func (m *mockExecutionPayloadEnvelopeVerifier) VerifySlotAboveFinalized(_ primitives.Epoch) error {
|
||||
return m.errSlotAboveFinalized
|
||||
}
|
||||
|
||||
func (m *mockExecutionPayloadEnvelopeVerifier) VerifySlotMatchesBlock(_ primitives.Slot) error {
|
||||
return m.errSlotMatchesBlock
|
||||
}
|
||||
|
||||
func (m *mockExecutionPayloadEnvelopeVerifier) VerifyBuilderValid(_ interfaces.ROExecutionPayloadBid) error {
|
||||
return m.errBuilderValid
|
||||
}
|
||||
|
||||
func (m *mockExecutionPayloadEnvelopeVerifier) VerifyPayloadHash(_ interfaces.ROExecutionPayloadBid) error {
|
||||
return m.errPayloadHash
|
||||
}
|
||||
|
||||
func (m *mockExecutionPayloadEnvelopeVerifier) VerifySignature(_ state.ReadOnlyBeaconState) error {
|
||||
return m.errSignature
|
||||
}
|
||||
|
||||
func (*mockExecutionPayloadEnvelopeVerifier) SatisfyRequirement(_ verification.Requirement) {}
|
||||
|
||||
func testNewExecutionPayloadEnvelopeVerifier(m mockExecutionPayloadEnvelopeVerifier) verification.NewExecutionPayloadEnvelopeVerifier {
|
||||
return func(_ interfaces.ROSignedExecutionPayloadEnvelope, _ []verification.Requirement) verification.ExecutionPayloadEnvelopeVerifier {
|
||||
clone := m
|
||||
return &clone
|
||||
}
|
||||
}
|
||||
|
||||
func setupExecutionPayloadEnvelopeService(t *testing.T, envelopeSlot, blockSlot primitives.Slot) (*Service, *pubsub.Message, primitives.BuilderIndex, [32]byte) {
|
||||
t.Helper()
|
||||
|
||||
ctx := context.Background()
|
||||
db := dbtest.SetupDB(t)
|
||||
p := p2ptest.NewTestP2P(t)
|
||||
chainService := &mock.ChainService{
|
||||
Genesis: time.Unix(time.Now().Unix()-int64(params.BeaconConfig().SecondsPerSlot), 0),
|
||||
FinalizedCheckPoint: ðpb.Checkpoint{},
|
||||
DB: db,
|
||||
}
|
||||
stateGen := stategen.New(db, doublylinkedtree.New())
|
||||
s := &Service{
|
||||
seenPayloadEnvelopeCache: lruwrpr.New(10),
|
||||
cfg: &config{
|
||||
p2p: p,
|
||||
initialSync: &mockSync.Sync{},
|
||||
chain: chainService,
|
||||
beaconDB: db,
|
||||
stateGen: stateGen,
|
||||
clock: startup.NewClock(chainService.Genesis, chainService.ValidatorsRoot),
|
||||
},
|
||||
}
|
||||
|
||||
bid := util.GenerateTestSignedExecutionPayloadBid(blockSlot)
|
||||
sb := util.NewBeaconBlockGloas()
|
||||
sb.Block.Slot = blockSlot
|
||||
sb.Block.Body.SignedExecutionPayloadBid = bid
|
||||
signedBlock, err := blocks.NewSignedBeaconBlock(sb)
|
||||
require.NoError(t, err)
|
||||
root, err := signedBlock.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.SaveBlock(ctx, signedBlock))
|
||||
|
||||
state, err := util.NewBeaconStateFulu()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.SaveState(ctx, state, root))
|
||||
|
||||
blockHash := bytesutil.ToBytes32(bid.Message.BlockHash)
|
||||
env := testSignedExecutionPayloadEnvelope(t, envelopeSlot, primitives.BuilderIndex(bid.Message.BuilderIndex), root, blockHash)
|
||||
msg := envelopeToPubsub(t, s, p, env)
|
||||
|
||||
return s, msg, primitives.BuilderIndex(bid.Message.BuilderIndex), root
|
||||
}
|
||||
|
||||
func envelopeToPubsub(t *testing.T, s *Service, p p2p.P2P, env *ethpb.SignedExecutionPayloadEnvelope) *pubsub.Message {
|
||||
t.Helper()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err := p.Encoding().EncodeGossip(buf, env)
|
||||
require.NoError(t, err)
|
||||
|
||||
topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedExecutionPayloadEnvelope]()]
|
||||
digest, err := s.currentForkDigest()
|
||||
require.NoError(t, err)
|
||||
topic = s.addDigestToTopic(topic, digest)
|
||||
|
||||
return &pubsub.Message{
|
||||
Message: &pb.Message{
|
||||
Data: buf.Bytes(),
|
||||
Topic: &topic,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testSignedExecutionPayloadEnvelope(t *testing.T, slot primitives.Slot, builderIdx primitives.BuilderIndex, root, blockHash [32]byte) *ethpb.SignedExecutionPayloadEnvelope {
|
||||
t.Helper()
|
||||
|
||||
payload := &enginev1.ExecutionPayloadDeneb{
|
||||
ParentHash: bytes.Repeat([]byte{0x01}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x02}, 20),
|
||||
StateRoot: bytes.Repeat([]byte{0x03}, 32),
|
||||
ReceiptsRoot: bytes.Repeat([]byte{0x04}, 32),
|
||||
LogsBloom: bytes.Repeat([]byte{0x05}, 256),
|
||||
PrevRandao: bytes.Repeat([]byte{0x06}, 32),
|
||||
BlockNumber: 1,
|
||||
GasLimit: 2,
|
||||
GasUsed: 3,
|
||||
Timestamp: 4,
|
||||
BaseFeePerGas: bytes.Repeat([]byte{0x07}, 32),
|
||||
BlockHash: blockHash[:],
|
||||
Transactions: [][]byte{},
|
||||
Withdrawals: []*enginev1.Withdrawal{},
|
||||
BlobGasUsed: 0,
|
||||
ExcessBlobGas: 0,
|
||||
}
|
||||
|
||||
return ðpb.SignedExecutionPayloadEnvelope{
|
||||
Message: ðpb.ExecutionPayloadEnvelope{
|
||||
Payload: payload,
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{
|
||||
Deposits: []*enginev1.DepositRequest{},
|
||||
},
|
||||
BuilderIndex: builderIdx,
|
||||
BeaconBlockRoot: root[:],
|
||||
Slot: slot,
|
||||
StateRoot: bytes.Repeat([]byte{0xBB}, 32),
|
||||
},
|
||||
Signature: bytes.Repeat([]byte{0xAA}, 96),
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ go_library(
|
||||
"cache.go",
|
||||
"data_column.go",
|
||||
"error.go",
|
||||
"execution_payload_envelope.go",
|
||||
"fake.go",
|
||||
"filesystem.go",
|
||||
"initializer.go",
|
||||
@@ -36,6 +37,7 @@ go_library(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/payload-attestation:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
@@ -60,6 +62,7 @@ go_test(
|
||||
"blob_test.go",
|
||||
"cache_test.go",
|
||||
"data_column_test.go",
|
||||
"execution_payload_envelope_test.go",
|
||||
"filesystem_test.go",
|
||||
"initializer_test.go",
|
||||
"payload_attestation_test.go",
|
||||
@@ -80,11 +83,13 @@ go_test(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/payload-attestation:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/bls/common:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/interop:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
|
||||
229
beacon-chain/verification/execution_payload_envelope.go
Normal file
229
beacon-chain/verification/execution_payload_envelope.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package verification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ExecutionPayloadEnvelopeVerifier defines the methods implemented by the ROSignedExecutionPayloadEnvelope.
|
||||
type ExecutionPayloadEnvelopeVerifier interface {
|
||||
VerifyBlockRootSeen(func([32]byte) bool) error
|
||||
VerifyBlockRootValid(func([32]byte) bool) error
|
||||
VerifySlotAboveFinalized(primitives.Epoch) error
|
||||
VerifySlotMatchesBlock(primitives.Slot) error
|
||||
VerifyBuilderValid(interfaces.ROExecutionPayloadBid) error
|
||||
VerifyPayloadHash(interfaces.ROExecutionPayloadBid) error
|
||||
VerifySignature(state.ReadOnlyBeaconState) error
|
||||
SatisfyRequirement(Requirement)
|
||||
}
|
||||
|
||||
// NewExecutionPayloadEnvelopeVerifier is a function signature that can be used by code that needs to be
|
||||
// able to mock Initializer.NewExecutionPayloadEnvelopeVerifier without complex setup.
|
||||
type NewExecutionPayloadEnvelopeVerifier func(e interfaces.ROSignedExecutionPayloadEnvelope, reqs []Requirement) ExecutionPayloadEnvelopeVerifier
|
||||
|
||||
// ExecutionPayloadEnvelopeGossipRequirements defines the list of requirements for gossip
|
||||
// execution payload envelopes.
|
||||
var ExecutionPayloadEnvelopeGossipRequirements = []Requirement{
|
||||
RequireBlockRootSeen,
|
||||
RequireBlockRootValid,
|
||||
RequireEnvelopeSlotAboveFinalized,
|
||||
RequireEnvelopeSlotMatchesBlock,
|
||||
RequireBuilderValid,
|
||||
RequirePayloadHashValid,
|
||||
RequireBuilderSignatureValid,
|
||||
}
|
||||
|
||||
// GossipExecutionPayloadEnvelopeRequirements is a requirement list for gossip execution payload envelopes.
|
||||
var GossipExecutionPayloadEnvelopeRequirements = requirementList(ExecutionPayloadEnvelopeGossipRequirements)
|
||||
|
||||
var (
|
||||
ErrEnvelopeBlockRootNotSeen = errors.New("block root not seen")
|
||||
ErrEnvelopeBlockRootInvalid = errors.New("block root invalid")
|
||||
ErrEnvelopeSlotBeforeFinalized = errors.New("envelope slot is before finalized checkpoint")
|
||||
ErrEnvelopeSlotMismatch = errors.New("envelope slot does not match block slot")
|
||||
ErrIncorrectEnvelopeBuilder = errors.New("builder index does not match committed header")
|
||||
ErrIncorrectEnvelopeBlockHash = errors.New("block hash does not match committed header")
|
||||
)
|
||||
|
||||
var _ ExecutionPayloadEnvelopeVerifier = &EnvelopeVerifier{}
|
||||
|
||||
// EnvelopeVerifier is a read-only verifier for execution payload envelopes.
|
||||
type EnvelopeVerifier struct {
|
||||
results *results
|
||||
e interfaces.ROSignedExecutionPayloadEnvelope
|
||||
}
|
||||
|
||||
// VerifyBlockRootSeen verifies if the block root has been seen before.
|
||||
func (v *EnvelopeVerifier) VerifyBlockRootSeen(parentSeen func([32]byte) bool) (err error) {
|
||||
defer v.record(RequireBlockRootSeen, &err)
|
||||
env, err := v.e.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get envelope")
|
||||
}
|
||||
if parentSeen != nil && parentSeen(env.BeaconBlockRoot()) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%w: root=%#x slot=%d builder=%d", ErrEnvelopeBlockRootNotSeen, env.BeaconBlockRoot(), env.Slot(), env.BuilderIndex())
|
||||
}
|
||||
|
||||
// VerifyBlockRootValid verifies if the block root is valid.
|
||||
func (v *EnvelopeVerifier) VerifyBlockRootValid(badBlock func([32]byte) bool) (err error) {
|
||||
defer v.record(RequireBlockRootValid, &err)
|
||||
env, err := v.e.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get envelope")
|
||||
}
|
||||
if badBlock != nil && badBlock(env.BeaconBlockRoot()) {
|
||||
return fmt.Errorf("%w: root=%#x slot=%d builder=%d", ErrEnvelopeBlockRootInvalid, env.BeaconBlockRoot(), env.Slot(), env.BuilderIndex())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifySlotAboveFinalized ensures the envelope slot is not before the latest finalized epoch start.
|
||||
func (v *EnvelopeVerifier) VerifySlotAboveFinalized(finalizedEpoch primitives.Epoch) (err error) {
|
||||
defer v.record(RequireEnvelopeSlotAboveFinalized, &err)
|
||||
env, err := v.e.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get envelope")
|
||||
}
|
||||
startSlot, err := slots.EpochStart(finalizedEpoch)
|
||||
if err != nil {
|
||||
return errors.Wrapf(ErrEnvelopeSlotBeforeFinalized, "error computing epoch start slot for finalized checkpoint (%d) %s", finalizedEpoch, err.Error())
|
||||
}
|
||||
if env.Slot() < startSlot {
|
||||
return fmt.Errorf("%w: slot=%d start=%d", ErrEnvelopeSlotBeforeFinalized, env.Slot(), startSlot)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifySlotMatchesBlock ensures the envelope slot matches the block slot.
|
||||
func (v *EnvelopeVerifier) VerifySlotMatchesBlock(blockSlot primitives.Slot) (err error) {
|
||||
defer v.record(RequireEnvelopeSlotMatchesBlock, &err)
|
||||
env, err := v.e.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get envelope")
|
||||
}
|
||||
if env.Slot() != blockSlot {
|
||||
return fmt.Errorf("%w: envelope=%d block=%d", ErrEnvelopeSlotMismatch, env.Slot(), blockSlot)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyBuilderValid checks that the builder index matches the one in the bid.
|
||||
func (v *EnvelopeVerifier) VerifyBuilderValid(bid interfaces.ROExecutionPayloadBid) (err error) {
|
||||
defer v.record(RequireBuilderValid, &err)
|
||||
env, err := v.e.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get envelope")
|
||||
}
|
||||
if bid.BuilderIndex() != env.BuilderIndex() {
|
||||
return fmt.Errorf("%w: envelope=%d bid=%d", ErrIncorrectEnvelopeBuilder, env.BuilderIndex(), bid.BuilderIndex())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyPayloadHash checks that the payload blockhash matches the one in the bid.
|
||||
func (v *EnvelopeVerifier) VerifyPayloadHash(bid interfaces.ROExecutionPayloadBid) (err error) {
|
||||
defer v.record(RequirePayloadHashValid, &err)
|
||||
env, err := v.e.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get envelope")
|
||||
}
|
||||
if env.IsBlinded() {
|
||||
return nil
|
||||
}
|
||||
payload, err := env.Execution()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get payload execution")
|
||||
}
|
||||
if bid.BlockHash() != [32]byte(payload.BlockHash()) {
|
||||
return fmt.Errorf("%w: payload=%#x bid=%#x", ErrIncorrectEnvelopeBlockHash, payload.BlockHash(), bid.BlockHash())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifySignature verifies the signature of the execution payload envelope.
|
||||
func (v *EnvelopeVerifier) VerifySignature(st state.ReadOnlyBeaconState) (err error) {
|
||||
defer v.record(RequireBuilderSignatureValid, &err)
|
||||
|
||||
err = validatePayloadEnvelopeSignature(st, v.e)
|
||||
if err != nil {
|
||||
env, envErr := v.e.Envelope()
|
||||
if envErr != nil {
|
||||
return errors.Wrap(err, "failed to get envelope for signature validation")
|
||||
}
|
||||
return errors.Wrapf(err, "signature validation failed: root=%#x slot=%d builder=%d", env.BeaconBlockRoot(), env.Slot(), env.BuilderIndex())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SatisfyRequirement allows the caller to manually mark a requirement as satisfied.
|
||||
func (v *EnvelopeVerifier) SatisfyRequirement(req Requirement) {
|
||||
v.record(req, nil)
|
||||
}
|
||||
|
||||
// record records the result of a requirement verification.
|
||||
func (v *EnvelopeVerifier) record(req Requirement, err *error) {
|
||||
if err == nil || *err == nil {
|
||||
v.results.record(req, nil)
|
||||
return
|
||||
}
|
||||
|
||||
v.results.record(req, *err)
|
||||
}
|
||||
|
||||
// validatePayloadEnvelopeSignature verifies the signature of a signed execution payload envelope
|
||||
func validatePayloadEnvelopeSignature(st state.ReadOnlyBeaconState, e interfaces.ROSignedExecutionPayloadEnvelope) error {
|
||||
env, err := e.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get envelope")
|
||||
}
|
||||
var pubkey []byte
|
||||
if env.BuilderIndex() == params.BeaconConfig().BuilderIndexSelfBuild {
|
||||
header := st.LatestBlockHeader()
|
||||
if header == nil {
|
||||
return errors.New("latest block header is nil")
|
||||
}
|
||||
val, err := st.ValidatorAtIndex(primitives.ValidatorIndex(header.ProposerIndex))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get proposer validator")
|
||||
}
|
||||
pubkey = val.PublicKey
|
||||
} else {
|
||||
builderPubkey, err := st.BuilderPubkey(env.BuilderIndex())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get builder pubkey")
|
||||
}
|
||||
pubkey = builderPubkey[:]
|
||||
}
|
||||
pub, err := bls.PublicKeyFromBytes(pubkey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid public key")
|
||||
}
|
||||
s := e.Signature()
|
||||
sig, err := bls.SignatureFromBytes(s[:])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid signature format")
|
||||
}
|
||||
currentEpoch := slots.ToEpoch(st.Slot())
|
||||
domain, err := signing.Domain(st.Fork(), currentEpoch, params.BeaconConfig().DomainBeaconBuilder, st.GenesisValidatorsRoot())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to compute signing domain")
|
||||
}
|
||||
root, err := e.SigningRoot(domain)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to compute signing root")
|
||||
}
|
||||
if !sig.Verify(pub, root[:]) {
|
||||
return signing.ErrSigFailedToVerify
|
||||
}
|
||||
return nil
|
||||
}
|
||||
258
beacon-chain/verification/execution_payload_envelope_test.go
Normal file
258
beacon-chain/verification/execution_payload_envelope_test.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package verification
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
)
|
||||
|
||||
func TestEnvelopeVerifier_VerifySlotAboveFinalized(t *testing.T) {
|
||||
root := bytesutil.ToBytes32(bytes.Repeat([]byte{0xAA}, 32))
|
||||
blockHash := bytesutil.ToBytes32(bytes.Repeat([]byte{0xBB}, 32))
|
||||
env := testSignedExecutionPayloadEnvelope(t, 1, 1, root, blockHash)
|
||||
wrapped, err := blocks.WrappedROSignedExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
verifier := &EnvelopeVerifier{results: newResults(RequireEnvelopeSlotAboveFinalized), e: wrapped}
|
||||
require.ErrorIs(t, verifier.VerifySlotAboveFinalized(1), ErrEnvelopeSlotBeforeFinalized)
|
||||
|
||||
verifier = &EnvelopeVerifier{results: newResults(RequireEnvelopeSlotAboveFinalized), e: wrapped}
|
||||
require.NoError(t, verifier.VerifySlotAboveFinalized(0))
|
||||
}
|
||||
|
||||
func TestEnvelopeVerifier_VerifySlotMatchesBlock(t *testing.T) {
|
||||
root := bytesutil.ToBytes32(bytes.Repeat([]byte{0xAA}, 32))
|
||||
blockHash := bytesutil.ToBytes32(bytes.Repeat([]byte{0xBB}, 32))
|
||||
env := testSignedExecutionPayloadEnvelope(t, 2, 1, root, blockHash)
|
||||
wrapped, err := blocks.WrappedROSignedExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
verifier := &EnvelopeVerifier{results: newResults(RequireEnvelopeSlotMatchesBlock), e: wrapped}
|
||||
require.ErrorIs(t, verifier.VerifySlotMatchesBlock(3), ErrEnvelopeSlotMismatch)
|
||||
|
||||
verifier = &EnvelopeVerifier{results: newResults(RequireEnvelopeSlotMatchesBlock), e: wrapped}
|
||||
require.NoError(t, verifier.VerifySlotMatchesBlock(2))
|
||||
}
|
||||
|
||||
func TestEnvelopeVerifier_VerifyBlockRootSeen(t *testing.T) {
|
||||
root := bytesutil.ToBytes32(bytes.Repeat([]byte{0xAA}, 32))
|
||||
blockHash := bytesutil.ToBytes32(bytes.Repeat([]byte{0xBB}, 32))
|
||||
env := testSignedExecutionPayloadEnvelope(t, 1, 1, root, blockHash)
|
||||
wrapped, err := blocks.WrappedROSignedExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
verifier := &EnvelopeVerifier{results: newResults(RequireBlockRootSeen), e: wrapped}
|
||||
require.ErrorIs(t, verifier.VerifyBlockRootSeen(func([32]byte) bool { return false }), ErrEnvelopeBlockRootNotSeen)
|
||||
|
||||
verifier = &EnvelopeVerifier{results: newResults(RequireBlockRootSeen), e: wrapped}
|
||||
require.NoError(t, verifier.VerifyBlockRootSeen(func([32]byte) bool { return true }))
|
||||
}
|
||||
|
||||
func TestEnvelopeVerifier_VerifyBlockRootValid(t *testing.T) {
|
||||
root := bytesutil.ToBytes32(bytes.Repeat([]byte{0xAA}, 32))
|
||||
blockHash := bytesutil.ToBytes32(bytes.Repeat([]byte{0xBB}, 32))
|
||||
env := testSignedExecutionPayloadEnvelope(t, 1, 1, root, blockHash)
|
||||
wrapped, err := blocks.WrappedROSignedExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
verifier := &EnvelopeVerifier{results: newResults(RequireBlockRootValid), e: wrapped}
|
||||
require.ErrorIs(t, verifier.VerifyBlockRootValid(func([32]byte) bool { return true }), ErrEnvelopeBlockRootInvalid)
|
||||
|
||||
verifier = &EnvelopeVerifier{results: newResults(RequireBlockRootValid), e: wrapped}
|
||||
require.NoError(t, verifier.VerifyBlockRootValid(func([32]byte) bool { return false }))
|
||||
}
|
||||
|
||||
func TestEnvelopeVerifier_VerifyBuilderValid(t *testing.T) {
|
||||
root := bytesutil.ToBytes32(bytes.Repeat([]byte{0xAA}, 32))
|
||||
blockHash := bytesutil.ToBytes32(bytes.Repeat([]byte{0xBB}, 32))
|
||||
env := testSignedExecutionPayloadEnvelope(t, 1, 1, root, blockHash)
|
||||
wrapped, err := blocks.WrappedROSignedExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
badBid := testExecutionPayloadBid(t, 1, 2, blockHash)
|
||||
verifier := &EnvelopeVerifier{results: newResults(RequireBuilderValid), e: wrapped}
|
||||
require.ErrorIs(t, verifier.VerifyBuilderValid(badBid), ErrIncorrectEnvelopeBuilder)
|
||||
|
||||
okBid := testExecutionPayloadBid(t, 1, 1, blockHash)
|
||||
verifier = &EnvelopeVerifier{results: newResults(RequireBuilderValid), e: wrapped}
|
||||
require.NoError(t, verifier.VerifyBuilderValid(okBid))
|
||||
}
|
||||
|
||||
func TestEnvelopeVerifier_VerifyPayloadHash(t *testing.T) {
|
||||
root := bytesutil.ToBytes32(bytes.Repeat([]byte{0xAA}, 32))
|
||||
blockHash := bytesutil.ToBytes32(bytes.Repeat([]byte{0xBB}, 32))
|
||||
env := testSignedExecutionPayloadEnvelope(t, 1, 1, root, blockHash)
|
||||
wrapped, err := blocks.WrappedROSignedExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
badHash := bytesutil.ToBytes32(bytes.Repeat([]byte{0xCC}, 32))
|
||||
badBid := testExecutionPayloadBid(t, 1, 1, badHash)
|
||||
verifier := &EnvelopeVerifier{results: newResults(RequirePayloadHashValid), e: wrapped}
|
||||
require.ErrorIs(t, verifier.VerifyPayloadHash(badBid), ErrIncorrectEnvelopeBlockHash)
|
||||
|
||||
okBid := testExecutionPayloadBid(t, 1, 1, blockHash)
|
||||
verifier = &EnvelopeVerifier{results: newResults(RequirePayloadHashValid), e: wrapped}
|
||||
require.NoError(t, verifier.VerifyPayloadHash(okBid))
|
||||
}
|
||||
|
||||
func TestEnvelopeVerifier_VerifySignature_Builder(t *testing.T) {
|
||||
slot := primitives.Slot(1)
|
||||
root := bytesutil.ToBytes32(bytes.Repeat([]byte{0xAA}, 32))
|
||||
blockHash := bytesutil.ToBytes32(bytes.Repeat([]byte{0xBB}, 32))
|
||||
env := testSignedExecutionPayloadEnvelope(t, slot, 0, root, blockHash)
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
builderPubkey := sk.PublicKey().Marshal()
|
||||
|
||||
st := newGloasState(t, slot, nil, nil, []*ethpb.Builder{{Pubkey: builderPubkey}})
|
||||
|
||||
sig := signEnvelope(t, sk, env.Message, st.Fork(), st.GenesisValidatorsRoot(), slot)
|
||||
env.Signature = sig[:]
|
||||
wrapped, err := blocks.WrappedROSignedExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
verifier := &EnvelopeVerifier{results: newResults(RequireBuilderSignatureValid), e: wrapped}
|
||||
require.NoError(t, verifier.VerifySignature(st))
|
||||
|
||||
sk2, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
badSig := signEnvelope(t, sk2, env.Message, st.Fork(), st.GenesisValidatorsRoot(), slot)
|
||||
env.Signature = badSig[:]
|
||||
wrapped, err = blocks.WrappedROSignedExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
verifier = &EnvelopeVerifier{results: newResults(RequireBuilderSignatureValid), e: wrapped}
|
||||
require.ErrorIs(t, verifier.VerifySignature(st), signing.ErrSigFailedToVerify)
|
||||
}
|
||||
|
||||
func TestEnvelopeVerifier_VerifySignature_SelfBuild(t *testing.T) {
|
||||
slot := primitives.Slot(2)
|
||||
root := bytesutil.ToBytes32(bytes.Repeat([]byte{0xAA}, 32))
|
||||
blockHash := bytesutil.ToBytes32(bytes.Repeat([]byte{0xBB}, 32))
|
||||
env := testSignedExecutionPayloadEnvelope(t, slot, params.BeaconConfig().BuilderIndexSelfBuild, root, blockHash)
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
validatorPubkey := sk.PublicKey().Marshal()
|
||||
|
||||
validators := []*ethpb.Validator{{PublicKey: validatorPubkey}}
|
||||
balances := []uint64{0}
|
||||
st := newGloasState(t, slot, validators, balances, nil)
|
||||
|
||||
sig := signEnvelope(t, sk, env.Message, st.Fork(), st.GenesisValidatorsRoot(), slot)
|
||||
env.Signature = sig[:]
|
||||
wrapped, err := blocks.WrappedROSignedExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
verifier := &EnvelopeVerifier{results: newResults(RequireBuilderSignatureValid), e: wrapped}
|
||||
require.NoError(t, verifier.VerifySignature(st))
|
||||
}
|
||||
|
||||
func testSignedExecutionPayloadEnvelope(t *testing.T, slot primitives.Slot, builderIdx primitives.BuilderIndex, root, blockHash [32]byte) *ethpb.SignedExecutionPayloadEnvelope {
|
||||
t.Helper()
|
||||
|
||||
payload := &enginev1.ExecutionPayloadDeneb{
|
||||
ParentHash: bytes.Repeat([]byte{0x01}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x02}, 20),
|
||||
StateRoot: bytes.Repeat([]byte{0x03}, 32),
|
||||
ReceiptsRoot: bytes.Repeat([]byte{0x04}, 32),
|
||||
LogsBloom: bytes.Repeat([]byte{0x05}, 256),
|
||||
PrevRandao: bytes.Repeat([]byte{0x06}, 32),
|
||||
BlockNumber: 1,
|
||||
GasLimit: 2,
|
||||
GasUsed: 3,
|
||||
Timestamp: 4,
|
||||
BaseFeePerGas: bytes.Repeat([]byte{0x07}, 32),
|
||||
BlockHash: blockHash[:],
|
||||
Transactions: [][]byte{},
|
||||
Withdrawals: []*enginev1.Withdrawal{},
|
||||
BlobGasUsed: 0,
|
||||
ExcessBlobGas: 0,
|
||||
}
|
||||
|
||||
return ðpb.SignedExecutionPayloadEnvelope{
|
||||
Message: ðpb.ExecutionPayloadEnvelope{
|
||||
Payload: payload,
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{
|
||||
Deposits: []*enginev1.DepositRequest{},
|
||||
},
|
||||
BuilderIndex: builderIdx,
|
||||
BeaconBlockRoot: root[:],
|
||||
Slot: slot,
|
||||
StateRoot: bytes.Repeat([]byte{0xBB}, 32),
|
||||
},
|
||||
Signature: bytes.Repeat([]byte{0xCC}, 96),
|
||||
}
|
||||
}
|
||||
|
||||
func testExecutionPayloadBid(t *testing.T, slot primitives.Slot, builderIdx primitives.BuilderIndex, blockHash [32]byte) interfaces.ROExecutionPayloadBid {
|
||||
t.Helper()
|
||||
|
||||
signed := util.GenerateTestSignedExecutionPayloadBid(slot)
|
||||
signed.Message.BuilderIndex = builderIdx
|
||||
copy(signed.Message.BlockHash, blockHash[:])
|
||||
|
||||
wrapped, err := blocks.WrappedROSignedExecutionPayloadBid(signed)
|
||||
require.NoError(t, err)
|
||||
bid, err := wrapped.Bid()
|
||||
require.NoError(t, err)
|
||||
return bid
|
||||
}
|
||||
|
||||
func newGloasState(
|
||||
t *testing.T,
|
||||
slot primitives.Slot,
|
||||
validators []*ethpb.Validator,
|
||||
balances []uint64,
|
||||
builders []*ethpb.Builder,
|
||||
) state.BeaconState {
|
||||
t.Helper()
|
||||
|
||||
genesisRoot := bytes.Repeat([]byte{0x11}, 32)
|
||||
st, err := util.NewBeaconStateGloas(func(s *ethpb.BeaconStateGloas) error {
|
||||
s.Slot = slot
|
||||
s.GenesisValidatorsRoot = genesisRoot
|
||||
if validators != nil {
|
||||
s.Validators = validators
|
||||
}
|
||||
if balances != nil {
|
||||
s.Balances = balances
|
||||
}
|
||||
if s.LatestBlockHeader != nil {
|
||||
s.LatestBlockHeader.ProposerIndex = 0
|
||||
}
|
||||
if builders != nil {
|
||||
s.Builders = builders
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return st
|
||||
}
|
||||
|
||||
func signEnvelope(t *testing.T, sk bls.SecretKey, env *ethpb.ExecutionPayloadEnvelope, fork *ethpb.Fork, genesisRoot []byte, slot primitives.Slot) [96]byte {
|
||||
t.Helper()
|
||||
|
||||
epoch := slots.ToEpoch(slot)
|
||||
domain, err := signing.Domain(fork, epoch, params.BeaconConfig().DomainBeaconBuilder, genesisRoot)
|
||||
require.NoError(t, err)
|
||||
root, err := signing.ComputeSigningRoot(env, domain)
|
||||
require.NoError(t, err)
|
||||
sig := sk.Sign(root[:]).Marshal()
|
||||
var out [96]byte
|
||||
copy(out[:], sig)
|
||||
return out
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
payloadattestation "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attestation"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
@@ -97,6 +98,14 @@ func (ini *Initializer) NewPayloadAttestationMsgVerifier(pa payloadattestation.R
|
||||
}
|
||||
}
|
||||
|
||||
// NewPayloadEnvelopeVerifier creates a SignedExecutionPayloadEnvelopeVerifier for a single signed execution payload envelope with the given set of requirements.
|
||||
func (ini *Initializer) NewPayloadEnvelopeVerifier(ee interfaces.ROSignedExecutionPayloadEnvelope, reqs []Requirement) *EnvelopeVerifier {
|
||||
return &EnvelopeVerifier{
|
||||
results: newResults(reqs...),
|
||||
e: ee,
|
||||
}
|
||||
}
|
||||
|
||||
// InitializerWaiter provides an Initializer once all dependent resources are ready
|
||||
// via the WaitForInitializer method.
|
||||
type InitializerWaiter struct {
|
||||
|
||||
@@ -24,4 +24,11 @@ const (
|
||||
RequireBlockRootSeen
|
||||
RequireBlockRootValid
|
||||
RequireSignatureValid
|
||||
|
||||
// Execution payload envelope specific.
|
||||
RequireBuilderValid
|
||||
RequirePayloadHashValid
|
||||
RequireEnvelopeSlotAboveFinalized
|
||||
RequireEnvelopeSlotMatchesBlock
|
||||
RequireBuilderSignatureValid
|
||||
)
|
||||
|
||||
@@ -45,6 +45,16 @@ func (r Requirement) String() string {
|
||||
return "RequireBlockRootValid"
|
||||
case RequireSignatureValid:
|
||||
return "RequireSignatureValid"
|
||||
case RequireBuilderValid:
|
||||
return "RequireBuilderValid"
|
||||
case RequirePayloadHashValid:
|
||||
return "RequirePayloadHashValid"
|
||||
case RequireEnvelopeSlotAboveFinalized:
|
||||
return "RequireEnvelopeSlotAboveFinalized"
|
||||
case RequireEnvelopeSlotMatchesBlock:
|
||||
return "RequireEnvelopeSlotMatchesBlock"
|
||||
case RequireBuilderSignatureValid:
|
||||
return "RequireBuilderSignatureValid"
|
||||
default:
|
||||
return unknownRequirementName
|
||||
}
|
||||
|
||||
3
changelog/codex_add-gloas-execution-payload-envelope.md
Normal file
3
changelog/codex_add-gloas-execution-payload-envelope.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Add Gloas execution payload envelope gossip validation
|
||||
Reference in New Issue
Block a user