Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion beacon-chain/blockchain/chain_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ type ChainInfoFetcher interface {
ForkFetcher
HeadDomainFetcher
ForkchoiceFetcher
PayloadAvailabilityFetcher
}

// PayloadAvailabilityFetcher provides the payload arrival and blob data availability status
// used to build payload attestations.
type PayloadAvailabilityFetcher interface {
PayloadEarly([32]byte) (bool, bool)
DataAvailable(context.Context, [32]byte, primitives.Slot) (bool, error)
}

// ForkchoiceFetcher defines a common interface for methods that access directly
Expand All @@ -41,6 +49,7 @@ type ChainInfoFetcher interface {
type ForkchoiceFetcher interface {
Ancestor(context.Context, []byte, primitives.Slot) ([]byte, error)
BlockHash(root [32]byte) ([32]byte, error)
HasPayloadBlockHash(root, blockHash [32]byte) bool
GasLimit(root [32]byte) (uint64, error)
CachedHeadRoot() [32]byte
GetProposerHead() [32]byte
Expand All @@ -50,7 +59,6 @@ type ForkchoiceFetcher interface {
HighestReceivedBlockRoot() [32]byte
HasNode([32]byte) bool
HasFullNode([32]byte) bool
PayloadEarly([32]byte) (bool, bool)
FullBeatsEmpty([32]byte) bool
ReceivedBlocksLastEpoch() (uint64, error)
InsertNode(context.Context, state.BeaconState, consensus_blocks.ROBlock) error
Expand Down
7 changes: 7 additions & 0 deletions beacon-chain/blockchain/chain_info_forkchoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ func (s *Service) BlockHash(root [32]byte) ([32]byte, error) {
return s.cfg.ForkChoiceStore.BlockHash(root)
}

// HasPayloadBlockHash reports whether blockHash is an available payload parent at root.
func (s *Service) HasPayloadBlockHash(root, blockHash [32]byte) bool {
s.cfg.ForkChoiceStore.RLock()
defer s.cfg.ForkChoiceStore.RUnlock()
return s.cfg.ForkChoiceStore.HasPayloadBlockHash(root, blockHash)
}

// GasLimit returns the gas limit of the latest full payload at or before the given beacon block root from forkchoice.
func (s *Service) GasLimit(root [32]byte) (uint64, error) {
s.cfg.ForkChoiceStore.RLock()
Expand Down
32 changes: 32 additions & 0 deletions beacon-chain/blockchain/receive_execution_payload_envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,16 +275,19 @@ func (s *Service) callNewPayload(
) (bool, error) {
_, err := s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, &parentRoot, requests)
if err == nil {
newPayloadValidNodeCount.Inc()
return true, nil
}
if errors.Is(err, execution.ErrAcceptedSyncingPayloadStatus) {
newPayloadOptimisticNodeCount.Inc()
log.WithFields(logrus.Fields{
"slot": slot,
"payloadBlockHash": fmt.Sprintf("%#x", bytesutil.Trunc(payload.BlockHash())),
}).Info("Called new payload with optimistic envelope")
return false, nil
}
if errors.Is(err, execution.ErrInvalidPayloadStatus) {
newPayloadInvalidNodeCount.Inc()
return false, invalidBlock{error: ErrInvalidPayload}
}
return false, errors.WithMessage(ErrUndefinedExecutionEngineError, err.Error())
Expand Down Expand Up @@ -378,6 +381,35 @@ func (s *Service) PayloadEarly(root [32]byte) (bool, bool) {
return s.payloadArrivals.isEarly(root)
}

// DataAvailable reports whether all blob data committed to by the block at root is available now.
func (s *Service) DataAvailable(ctx context.Context, root [32]byte, slot primitives.Slot) (bool, error) {
available, err := s.dataColumnsAvailableNow(ctx, root, slot)
if err != nil {
return false, errors.Wrap(err, "data columns available now")
}
if available {
return true, nil
}

s.headLock.RLock()
var b interfaces.ReadOnlySignedBeaconBlock
if s.head != nil && s.head.root == root {
b = s.head.block
}
s.headLock.RUnlock()
if b == nil {
b, err = s.getBlock(ctx, root)
if err != nil {
return false, errors.Wrap(err, "could not get block")
}
}
sbid, err := b.Block().Body().SignedExecutionPayloadBid()
if err != nil {
return false, errors.Wrap(err, "could not get signed execution payload bid from block")
}
return len(sbid.GetMessage().GetBlobKzgCommitments()) == 0, nil
}

// notifyForkchoiceUpdateGloas takes the block hash directly because Gloas
// blocks don't carry an execution payload in the body.
func (s *Service) notifyForkchoiceUpdateGloas(ctx context.Context, blockHash [32]byte, attributes payloadattribute.Attributer) (*enginev1.PayloadIDBytes, error) {
Expand Down
35 changes: 35 additions & 0 deletions beacon-chain/blockchain/receive_execution_payload_envelope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/feed"
statefeed "github.com/OffchainLabs/prysm/v7/beacon-chain/core/feed/state"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/db/filesystem"
"github.com/OffchainLabs/prysm/v7/beacon-chain/execution"
mockExecution "github.com/OffchainLabs/prysm/v7/beacon-chain/execution/testing"
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
Expand All @@ -20,6 +21,7 @@ import (
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/testing/util"
"github.com/OffchainLabs/prysm/v7/time/slots"
)

Expand Down Expand Up @@ -248,6 +250,39 @@ func TestReceiveExecutionPayloadEnvelope_EmitsHeadV2Event(t *testing.T) {
})
}

func TestDataAvailable(t *testing.T) {
saveGloasBlock := func(t *testing.T, service *Service, commitments [][]byte) [32]byte {
b := util.NewBeaconBlockGloas()
b.Block.Body.SignedExecutionPayloadBid.Message.BlobKzgCommitments = commitments
sb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
root, err := sb.Block().HashTreeRoot()
require.NoError(t, err)
require.NoError(t, service.cfg.BeaconDB.SaveBlock(t.Context(), sb))
return root
}

t.Run("unknown block returns error", func(t *testing.T) {
service, _ := minimalTestService(t, WithDataColumnStorage(filesystem.NewEphemeralDataColumnStorage(t)))
_, err := service.DataAvailable(t.Context(), [32]byte{'a'}, 0)
require.NotNil(t, err)
})
t.Run("no blob commitments", func(t *testing.T) {
service, _ := minimalTestService(t, WithDataColumnStorage(filesystem.NewEphemeralDataColumnStorage(t)))
root := saveGloasBlock(t, service, nil)
available, err := service.DataAvailable(t.Context(), root, 0)
require.NoError(t, err)
require.Equal(t, true, available)
})
t.Run("commitments with no columns stored", func(t *testing.T) {
service, _ := minimalTestService(t, WithDataColumnStorage(filesystem.NewEphemeralDataColumnStorage(t)))
root := saveGloasBlock(t, service, [][]byte{bytesutil.PadTo([]byte{0x01}, 48)})
available, err := service.DataAvailable(t.Context(), root, 0)
require.NoError(t, err)
require.Equal(t, false, available)
})
}

// countStateEventsByType is a helper function for counting the number of events
// of each type received on a channel.
func countStateEventsByType(ch chan *feed.Event) map[feed.EventType]int {
Expand Down
20 changes: 20 additions & 0 deletions beacon-chain/blockchain/testing/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ type ChainService struct {
MockCanonicalRoots map[primitives.Slot][32]byte
InitSyncBlockRoots map[[32]byte]bool
MockPayloadEarly map[[32]byte]bool
MockDataAvailable map[[32]byte]bool
MockDataAvailableErr error
ParentPayloadReadyVal *bool
BlockSlot primitives.Slot
OptimisticRoots map[[32]byte]bool
Expand Down Expand Up @@ -635,6 +637,16 @@ func (s *ChainService) BlockHash(root [32]byte) ([32]byte, error) {
return [32]byte{}, errors.New("block hash not found")
}

// HasPayloadBlockHash mocks the same method in the chain service.
func (s *ChainService) HasPayloadBlockHash(root, blockHash [32]byte) bool {
if s.ForkChoiceStore == nil {
return false
}
s.ForkChoiceStore.RLock()
defer s.ForkChoiceStore.RUnlock()
return s.ForkChoiceStore.HasPayloadBlockHash(root, blockHash)
}

// IsOptimisticForRoot mocks the same method in the chain service.
func (s *ChainService) IsOptimisticForRoot(_ context.Context, root [32]byte) (bool, error) {
s.OptimisticCheckRootReceived = root
Expand Down Expand Up @@ -809,6 +821,14 @@ func (s *ChainService) PayloadEarly(root [32]byte) (bool, bool) {
return early, ok
}

// DataAvailable mocks the same method in the chain service.
func (s *ChainService) DataAvailable(_ context.Context, root [32]byte, _ primitives.Slot) (bool, error) {
if s.MockDataAvailableErr != nil {
return false, s.MockDataAvailableErr
}
return s.MockDataAvailable[root], nil
}

// FullBeatsEmpty mocks the same method in the chain service.
func (s *ChainService) FullBeatsEmpty(root [32]byte) bool {
if s.ForkChoiceStore != nil {
Expand Down
17 changes: 16 additions & 1 deletion beacon-chain/forkchoice/doubly-linked-tree/gloas.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,9 @@ func (f *ForkChoice) SetPTCVote(root [32]byte, ptcIdx uint64, payloadPresent, bl
if n == nil {
return
}
ptcVoteCount.Inc()
if !n.node.payloadAttesters.BitAt(ptcIdx) {
ptcVoteCount.Inc()
}
n.node.payloadAttesters.SetBitAt(ptcIdx, true)
n.node.payloadAvailabilityVote.SetBitAt(ptcIdx, payloadPresent)
n.node.payloadDataAvailabilityVote.SetBitAt(ptcIdx, blobDataAvailable)
Expand Down Expand Up @@ -575,6 +577,19 @@ func (f *ForkChoice) HasFullNode(root [32]byte) bool {
return ok
}

// HasPayloadBlockHash reports whether blockHash is an available payload parent at root.
func (f *ForkChoice) HasPayloadBlockHash(root, blockHash [32]byte) bool {
en := f.store.emptyNodeByRoot[root]
if en == nil || en.node == nil {
return false
}
if blockHash == en.node.blockHash {
_, ok := f.store.fullNodeByRoot[root]
return ok
}
return blockHash == f.store.parentHash(en)
}

// FullBeatsEmpty returns whether fork choice would select the full payload variant
// for the given beacon block root. The caller MUST hold the forkchoice lock.
func (f *ForkChoice) FullBeatsEmpty(root [32]byte) bool {
Expand Down
22 changes: 22 additions & 0 deletions beacon-chain/forkchoice/doubly-linked-tree/gloas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,28 @@ func TestParentHash_UnknownRoot(t *testing.T) {
assert.Equal(t, [32]byte{}, f.ParentHash(indexToHash(999)))
}

func TestHasPayloadBlockHash(t *testing.T) {
f := setupGloas(t, 0, 0)
ctx := t.Context()

root := indexToHash(1)
fullHash := indexToHash(100)
emptyHash := params.BeaconConfig().ZeroHash
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, root, emptyHash, fullHash, emptyHash, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, roblock))

assert.Equal(t, true, f.HasPayloadBlockHash(root, emptyHash))
assert.Equal(t, false, f.HasPayloadBlockHash(root, fullHash))
assert.Equal(t, false, f.HasPayloadBlockHash(root, indexToHash(999)))

pe, err := prepareGloasForkchoicePayload(root)
require.NoError(t, err)
require.NoError(t, f.InsertPayload(pe))
assert.Equal(t, true, f.HasPayloadBlockHash(root, fullHash))
assert.Equal(t, false, f.HasPayloadBlockHash(indexToHash(999), fullHash))
}

func TestGloasBlock_ChildBuildsOnFull(t *testing.T) {
f := setupGloas(t, 0, 0)
ctx := t.Context()
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/forkchoice/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ type FastGetter interface {
Weight(root [32]byte) (uint64, error)
ConsensusNodeWeight(root [32]byte) (uint64, error)
PayloadWeights(root [32]byte) (emptyWeight, fullWeight uint64, err error)
HasPayloadBlockHash(root, blockHash [32]byte) bool
PTCVotedEarlyAndAvailable(root [32]byte) bool
PTCVotedLate(root [32]byte) bool
ParentRoot(root [32]byte) ([32]byte, error)
Expand Down
7 changes: 7 additions & 0 deletions beacon-chain/forkchoice/ro.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,13 @@ func (ro *ROForkChoice) PayloadWeights(root [32]byte) (uint64, uint64, error) {
return ro.getter.PayloadWeights(root)
}

// HasPayloadBlockHash delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) HasPayloadBlockHash(root, blockHash [32]byte) bool {
ro.l.RLock()
defer ro.l.RUnlock()
return ro.getter.HasPayloadBlockHash(root, blockHash)
}

// IsOptimistic delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) IsOptimistic(root [32]byte) (bool, error) {
ro.l.RLock()
Expand Down
11 changes: 11 additions & 0 deletions beacon-chain/forkchoice/ro_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const (
dependentRootForEpochCalled
canonicalNodeAtSlotCalled
payloadWeightsCalled
hasPayloadBlockHashCalled
)

func _discard(t *testing.T, e error) {
Expand Down Expand Up @@ -197,6 +198,11 @@ func TestROLocking(t *testing.T) {
call: gasLimitCalled,
cb: func(g FastGetter) { _, err := g.GasLimit([32]byte{}); _discard(t, err) },
},
{
name: "hasPayloadBlockHashCalled",
call: hasPayloadBlockHashCalled,
cb: func(g FastGetter) { g.HasPayloadBlockHash([32]byte{}, [32]byte{}) },
},
{
name: "parentHashCalled",
call: parentHashCalled,
Expand Down Expand Up @@ -344,6 +350,11 @@ func (ro *mockROForkchoice) PayloadWeights(_ [32]byte) (uint64, uint64, error) {
return 0, 0, nil
}

func (ro *mockROForkchoice) HasPayloadBlockHash(_, _ [32]byte) bool {
ro.calls = append(ro.calls, hasPayloadBlockHashCalled)
return false
}

func (ro *mockROForkchoice) IsOptimistic(_ [32]byte) (bool, error) {
ro.calls = append(ro.calls, isOptimisticCalled)
return false, nil
Expand Down
11 changes: 11 additions & 0 deletions beacon-chain/node/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ func configureBuilderCircuitBreaker(cliCtx *cli.Context) error {
return nil
}

func configureBuilderGetHeaderTimeout(cliCtx *cli.Context) error {
if cliCtx.IsSet(flags.BuilderGetHeaderTimeout.Name) {
c := params.BeaconConfig().Copy()
c.BuilderGetHeaderTimeout = cliCtx.Duration(flags.BuilderGetHeaderTimeout.Name)
if err := params.SetActive(c); err != nil {
return err
}
}
return nil
}

func configureSlotsPerArchivedPoint(cliCtx *cli.Context) error {
if cliCtx.IsSet(flags.SlotsPerArchivedPoint.Name) {
c := params.BeaconConfig().Copy()
Expand Down
15 changes: 15 additions & 0 deletions beacon-chain/node/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"testing"
"time"

"github.com/OffchainLabs/prysm/v7/cmd"
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
Expand Down Expand Up @@ -39,6 +40,20 @@ func TestConfigureHistoricalSlasher(t *testing.T) {
)
}

func TestConfigureBuilderGetHeaderTimeout(t *testing.T) {
params.SetupTestConfigCleanup(t)

app := cli.App{}
set := flag.NewFlagSet("test", 0)
set.Duration(flags.BuilderGetHeaderTimeout.Name, 0, "")
require.NoError(t, set.Set(flags.BuilderGetHeaderTimeout.Name, "950ms"))
cliCtx := cli.NewContext(&app, set, nil)

require.NoError(t, configureBuilderGetHeaderTimeout(cliCtx))

assert.Equal(t, 950*time.Millisecond, params.BeaconConfig().BuilderGetHeaderTimeout)
}

func TestConfigureSlotsPerArchivedPoint(t *testing.T) {
params.SetupTestConfigCleanup(t)

Expand Down
4 changes: 4 additions & 0 deletions beacon-chain/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ func configureBeacon(cliCtx *cli.Context) error {
return errors.Wrap(err, "could not configure builder circuit breaker")
}

if err := configureBuilderGetHeaderTimeout(cliCtx); err != nil {
return errors.Wrap(err, "could not configure builder getHeader timeout")
}

if err := configureSlotsPerArchivedPoint(cliCtx); err != nil {
return errors.Wrap(err, "could not configure slots per archived point")
}
Expand Down
Loading