Allow nodes with and without payload in forkchoice (#14288)

* Allow nodes with and without payload in forkchoice

    This PR takes care of adding nodes to forkchoice that may or may not
    have a corresponding payload. The rationale is as follows

    - The node structure is kept almost the same as today.
    - A zero payload hash is considered as if the node was empty (except for
      the tree root)
    - When inserting a node we check what the right parent node would be
      depending on whether the parent had a payload or not.
    - For pre-epbs forks all nodes are full, no logic changes except a new
      steps to gather the parent hash that is needed for block insertion.

    This PR had to change some core consensus types and interfaces.
    - It removed the ROBlockEPBS interface and added the corresponding ePBS
      fields to the ReadOnlyBeaconBlockBody
    - It moved the setters and getters to epbs dedicated files.

    It also added a checker for `IsParentFull` on forkchoice that simply
    checks for the parent hash of the parent node.

* review
This commit is contained in:
Potuz
2024-08-02 12:19:14 -03:00
parent ca858458c1
commit 63b72e1e82
16 changed files with 270 additions and 72 deletions

View File

@@ -287,12 +287,45 @@ func ProcessPayloadHeader(st state.BeaconState, header interfaces.ExecutionData)
// GetBlockPayloadHash returns the hash of the execution payload of the block
func GetBlockPayloadHash(blk interfaces.ReadOnlyBeaconBlock) ([32]byte, error) {
var payloadHash [32]byte
if IsPreBellatrixVersion(blk.Version()) {
return payloadHash, nil
if blk.Version() >= version.EPBS {
header, err := blk.Body().SignedExecutionPayloadHeader()
if err != nil {
return payloadHash, err
}
if header.Message == nil {
return payloadHash, errors.New("nil execution header")
}
return [32]byte(header.Message.BlockHash), nil
}
payload, err := blk.Body().Execution()
if err != nil {
return payloadHash, err
if blk.Version() >= version.Bellatrix {
payload, err := blk.Body().Execution()
if err != nil {
return payloadHash, err
}
return bytesutil.ToBytes32(payload.BlockHash()), nil
}
return bytesutil.ToBytes32(payload.BlockHash()), nil
return payloadHash, nil
}
// GetBlockParentHash returns the hash of the parent execution payload
func GetBlockParentHash(blk interfaces.ReadOnlyBeaconBlock) ([32]byte, error) {
var parentHash [32]byte
if blk.Version() >= version.EPBS {
header, err := blk.Body().SignedExecutionPayloadHeader()
if err != nil {
return parentHash, err
}
if header.Message == nil {
return parentHash, errors.New("nil execution header")
}
return [32]byte(header.Message.ParentBlockHash), nil
}
if blk.Version() >= version.Bellatrix {
payload, err := blk.Body().Execution()
if err != nil {
return parentHash, err
}
return bytesutil.ToBytes32(payload.ParentHash()), nil
}
return parentHash, nil
}

View File

@@ -4,6 +4,7 @@ go_library(
name = "go_default_library",
srcs = [
"doc.go",
"epbs.go",
"errors.go",
"forkchoice.go",
"last_root.go",
@@ -47,6 +48,7 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"epbs_test.go",
"ffg_update_test.go",
"forkchoice_test.go",
"last_root_test.go",

View File

@@ -0,0 +1,9 @@
package doublylinkedtree
func (n *Node) isParentFull() bool {
// Finalized checkpoint is considered full
if n.parent == nil || n.parent.parent == nil {
return true
}
return n.parent.payloadHash != [32]byte{}
}

View File

@@ -0,0 +1,79 @@
package doublylinkedtree
import (
"context"
"testing"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
func TestStore_Insert_PayloadContent(t *testing.T) {
ctx := context.Background()
f := setup(0, 0)
s := f.store
// The tree root is full
fr := [32]byte{}
n := s.nodeByRoot[fr]
require.Equal(t, true, n.isParentFull())
// Insert a child with a payload
cr := [32]byte{'a'}
cp := [32]byte{'p'}
n, err := s.insert(ctx, 1, cr, fr, cp, fr, 0, 0)
require.NoError(t, err)
require.Equal(t, true, n.isParentFull())
require.Equal(t, s.treeRootNode, n.parent)
require.Equal(t, s.nodeByRoot[cr], n)
// Insert a grandchild without a payload
gr := [32]byte{'b'}
gn, err := s.insert(ctx, 2, gr, cr, fr, cp, 0, 0)
require.NoError(t, err)
require.Equal(t, true, gn.isParentFull())
require.Equal(t, n, gn.parent)
// Insert the payload of the same grandchild
gp := [32]byte{'q'}
gfn, err := s.insert(ctx, 2, gr, cr, gp, cp, 0, 0)
require.NoError(t, err)
require.Equal(t, true, gfn.isParentFull())
require.Equal(t, n, gfn.parent)
// Insert an empty great grandchild based on empty
ggr := [32]byte{'c'}
ggn, err := s.insert(ctx, 3, ggr, gr, fr, cp, 0, 0)
require.NoError(t, err)
require.Equal(t, false, ggn.isParentFull())
require.Equal(t, gn, ggn.parent)
// Insert an empty great grandchild based on full
ggfr := [32]byte{'d'}
ggfn, err := s.insert(ctx, 3, ggfr, gr, fr, gp, 0, 0)
require.NoError(t, err)
require.Equal(t, gfn, ggfn.parent)
require.Equal(t, true, ggfn.isParentFull())
// Insert the payload for the great grandchild based on empty
ggp := [32]byte{'r'}
n, err = s.insert(ctx, 3, ggr, gr, ggp, cp, 0, 0)
require.NoError(t, err)
require.Equal(t, false, n.isParentFull())
require.Equal(t, gn, n.parent)
// Insert the payload for the great grandchild based on full
ggfp := [32]byte{'s'}
n, err = s.insert(ctx, 3, ggfr, gr, ggfp, gp, 0, 0)
require.NoError(t, err)
require.Equal(t, true, n.isParentFull())
require.Equal(t, gfn, n.parent)
// Reinsert an empty node
ggfn2, err := s.insert(ctx, 3, ggfr, gr, fr, gp, 0, 0)
require.NoError(t, err)
require.Equal(t, ggfn, ggfn2)
// Reinsert a full node
n2, err := s.insert(ctx, 3, ggfr, gr, ggfp, gp, 0, 0)
require.NoError(t, err)
require.Equal(t, n, n2)
}

View File

@@ -115,8 +115,27 @@ func (f *ForkChoice) InsertNode(ctx context.Context, state state.BeaconState, ro
return errNilBlockHeader
}
parentRoot := bytesutil.ToBytes32(bh.ParentRoot)
var payloadHash [32]byte
if state.Version() >= version.Bellatrix {
var payloadHash, parentHash [32]byte
if state.Version() >= version.EPBS {
slot, err := state.LatestFullSlot()
if err != nil {
return err
}
if slot == state.Slot() {
latestHeader, err := state.LatestExecutionPayloadHeaderEPBS()
if err != nil {
return err
}
copy(payloadHash[:], latestHeader.BlockHash)
copy(parentHash[:], latestHeader.ParentBlockHash)
} else {
latestHash, err := state.LatestBlockHash()
if err != nil {
return err
}
copy(parentHash[:], latestHash)
}
} else if state.Version() >= version.Bellatrix {
ph, err := state.LatestExecutionPayloadHeader()
if err != nil {
return err
@@ -135,7 +154,7 @@ func (f *ForkChoice) InsertNode(ctx context.Context, state state.BeaconState, ro
return errInvalidNilCheckpoint
}
finalizedEpoch := fc.Epoch
node, err := f.store.insert(ctx, slot, root, parentRoot, payloadHash, justifiedEpoch, finalizedEpoch)
node, err := f.store.insert(ctx, slot, root, parentRoot, payloadHash, parentHash, justifiedEpoch, finalizedEpoch)
if err != nil {
return err
}
@@ -490,8 +509,12 @@ func (f *ForkChoice) InsertChain(ctx context.Context, chain []*forkchoicetypes.B
if err != nil {
return err
}
parentHash, err := blocks.GetBlockParentHash(b)
if err != nil {
return err
}
if _, err := f.store.insert(ctx,
b.Slot(), r, parentRoot, payloadHash,
b.Slot(), r, parentRoot, payloadHash, parentHash,
chain[i].JustifiedCheckpoint.Epoch, chain[i].FinalizedCheckpoint.Epoch); err != nil {
return err
}

View File

@@ -22,7 +22,8 @@ import (
)
// prepareForkchoiceState prepares a beacon State with the given data to mock
// insert into forkchoice
// insert into forkchoice. This method prepares full states and blocks for
// bellatrix, it cannot be used for ePBS tests.
func prepareForkchoiceState(
_ context.Context,
slot primitives.Slot,

View File

@@ -62,20 +62,32 @@ func (s *Store) head(ctx context.Context) ([32]byte, error) {
// insert registers a new block node to the fork choice store's node list.
// It then updates the new node's parent with the best child and descendant node.
func (s *Store) insert(ctx context.Context,
func (s *Store) insert(
ctx context.Context,
slot primitives.Slot,
root, parentRoot, payloadHash [fieldparams.RootLength]byte,
justifiedEpoch, finalizedEpoch primitives.Epoch) (*Node, error) {
root, parentRoot, payloadHash, parentHash [fieldparams.RootLength]byte,
justifiedEpoch, finalizedEpoch primitives.Epoch,
) (*Node, error) {
ctx, span := trace.StartSpan(ctx, "doublyLinkedForkchoice.insert")
defer span.End()
// Return if the block has been inserted into Store before.
if n, ok := s.nodeByRoot[root]; ok {
return n, nil
n, rootPresent := s.nodeByRoot[root]
m, hashPresent := s.nodeByPayload[payloadHash]
if rootPresent {
if payloadHash == [32]byte{} {
return n, nil
}
if hashPresent {
return m, nil
}
}
parent := s.nodeByRoot[parentRoot]
n := &Node{
fullParent := s.nodeByPayload[parentHash]
if fullParent != nil && parent != nil && fullParent.root == parent.root {
parent = fullParent
}
n = &Node{
slot: slot,
root: root,
parent: parent,
@@ -99,17 +111,22 @@ func (s *Store) insert(ctx context.Context,
}
}
s.nodeByPayload[payloadHash] = n
s.nodeByRoot[root] = n
if parent == nil {
if s.treeRootNode == nil {
s.treeRootNode = n
s.headNode = n
s.highestReceivedNode = n
} else {
} else if s.treeRootNode.root != n.root {
return n, errInvalidParentRoot
}
} else {
}
if !rootPresent {
s.nodeByRoot[root] = n
}
if !hashPresent {
s.nodeByPayload[payloadHash] = n
}
if parent != nil {
parent.children = append(parent.children, n)
// Apply proposer boost
timeNow := uint64(time.Now().Unix())

View File

@@ -133,7 +133,7 @@ func TestStore_Insert(t *testing.T) {
fc := &forkchoicetypes.Checkpoint{Epoch: 0}
s := &Store{nodeByRoot: nodeByRoot, treeRootNode: treeRootNode, nodeByPayload: nodeByPayload, justifiedCheckpoint: jc, finalizedCheckpoint: fc, highestReceivedNode: &Node{}}
payloadHash := [32]byte{'a'}
_, err := s.insert(context.Background(), 100, indexToHash(100), indexToHash(0), payloadHash, 1, 1)
_, err := s.insert(context.Background(), 100, indexToHash(100), indexToHash(0), payloadHash, payloadHash, 1, 1)
require.NoError(t, err)
assert.Equal(t, 2, len(s.nodeByRoot), "Did not insert block")
assert.Equal(t, (*Node)(nil), treeRootNode.parent, "Incorrect parent")
@@ -327,7 +327,7 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
// Make sure it doesn't underflow
s.genesisTime = uint64(time.Now().Add(time.Duration(-1*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
_, err := s.insert(context.Background(), 1, [32]byte{'a'}, b, b, 1, 1)
_, err := s.insert(context.Background(), 1, [32]byte{'a'}, b, b, b, 1, 1)
require.NoError(t, err)
count, err := f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
@@ -337,7 +337,7 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
// 64
// Received block last epoch is 1
_, err = s.insert(context.Background(), 64, [32]byte{'A'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 64, [32]byte{'A'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration((-64*int64(params.BeaconConfig().SecondsPerSlot))-1) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
@@ -348,7 +348,7 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
// 64 65
// Received block last epoch is 2
_, err = s.insert(context.Background(), 65, [32]byte{'B'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 65, [32]byte{'B'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-66*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
@@ -359,7 +359,7 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
// 64 65 66
// Received block last epoch is 3
_, err = s.insert(context.Background(), 66, [32]byte{'C'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 66, [32]byte{'C'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-66*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
@@ -370,7 +370,7 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
// 64 65 66
// 98
// Received block last epoch is 1
_, err = s.insert(context.Background(), 98, [32]byte{'D'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 98, [32]byte{'D'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-98*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
@@ -382,7 +382,7 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
// 98
// 132
// Received block last epoch is 1
_, err = s.insert(context.Background(), 132, [32]byte{'E'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 132, [32]byte{'E'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-132*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
@@ -395,7 +395,7 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
// 132
// 99
// Received block last epoch is still 1. 99 is outside the window
_, err = s.insert(context.Background(), 99, [32]byte{'F'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 99, [32]byte{'F'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-132*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
@@ -408,7 +408,7 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
// 132
// 99 100
// Received block last epoch is still 1. 100 is at the same position as 132
_, err = s.insert(context.Background(), 100, [32]byte{'G'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 100, [32]byte{'G'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-132*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
@@ -421,7 +421,7 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
// 132
// 99 100 101
// Received block last epoch is 2. 101 is within the window
_, err = s.insert(context.Background(), 101, [32]byte{'H'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 101, [32]byte{'H'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-132*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()

View File

@@ -7,11 +7,13 @@ go_library(
"factory.go",
"get_payload.go",
"getters.go",
"getters_epbs.go",
"kzg.go",
"proto.go",
"roblob.go",
"roblock.go",
"setters.go",
"setters_epbs.go",
"types.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks",

View File

@@ -1165,16 +1165,6 @@ func (b *BeaconBlockBody) BlobKzgCommitments() ([][]byte, error) {
}
}
// PayloadAttestations returns the payload attestations in the block.
func (b *BeaconBlockBody) PayloadAttestations() []*eth.PayloadAttestation {
return b.payloadAttestations
}
// SignedExecutionPayloadHeader returns the signed execution payload header in the block.
func (b *BeaconBlockBody) SignedExecutionPayloadHeader() *enginev1.SignedExecutionPayloadHeader {
return b.signedExecutionPayloadHeader
}
// Version returns the version of the beacon block body
func (b *BeaconBlockBody) Version() int {
return b.version

View File

@@ -0,0 +1,24 @@
package blocks
import (
consensus_types "github.com/prysmaticlabs/prysm/v5/consensus-types"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
)
// PayloadAttestations returns the payload attestations in the block.
func (b *BeaconBlockBody) PayloadAttestations() ([]*ethpb.PayloadAttestation, error) {
if b.version < version.EPBS {
return nil, consensus_types.ErrNotSupported("PayloadAttestations", b.version)
}
return b.payloadAttestations, nil
}
// SignedExecutionPayloadHeader returns the signed execution payload header in the block.
func (b *BeaconBlockBody) SignedExecutionPayloadHeader() (*enginev1.SignedExecutionPayloadHeader, error) {
if b.version < version.EPBS {
return nil, consensus_types.ErrNotSupported("SignedExecutionPayloadHeader", b.version)
}
return b.signedExecutionPayloadHeader, nil
}

View File

@@ -63,11 +63,14 @@ func Test_EpbsBlock_Copy(t *testing.T) {
require.NoError(t, err)
copiedEpbsBlock, err := epbsBlock.Copy()
require.NoError(t, err)
copiedBody, ok := copiedEpbsBlock.Body().(interfaces.ROBlockBodyEpbs)
copiedBody, ok := copiedEpbsBlock.Body().(interfaces.ReadOnlyBeaconBlockBody)
require.Equal(t, true, ok)
require.DeepEqual(t, copiedBody.SignedExecutionPayloadHeader(), signedHeader)
copiedHeader, err := copiedBody.SignedExecutionPayloadHeader()
require.NoError(t, err)
require.DeepEqual(t, copiedHeader, signedHeader)
copiedPayloadAtts := copiedBody.PayloadAttestations()
copiedPayloadAtts, err := copiedBody.PayloadAttestations()
require.NoError(t, err)
require.DeepEqual(t, copiedPayloadAtts, payloadAttestations)
}
@@ -93,3 +96,11 @@ func Test_EpbsBlock_IsBlinded(t *testing.T) {
bd := &BeaconBlockBody{version: version.EPBS}
require.Equal(t, false, bd.IsBlinded())
}
func Test_PreEPBS_Versions(t *testing.T) {
bb := &BeaconBlockBody{version: version.Electra}
_, err := bb.PayloadAttestations()
require.ErrorContains(t, "PayloadAttestations", err)
_, err = bb.SignedExecutionPayloadHeader()
require.ErrorContains(t, "SignedExecutionPayloadHeader", err)
}

View File

@@ -6,7 +6,6 @@ import (
consensus_types "github.com/prysmaticlabs/prysm/v5/consensus-types"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
)
@@ -173,21 +172,3 @@ func (b *SignedBeaconBlock) SetBlobKzgCommitments(c [][]byte) error {
b.block.body.blobKzgCommitments = c
return nil
}
// SetPayloadAttestations sets the payload attestations in the block.
func (b *SignedBeaconBlock) SetPayloadAttestations(p []*eth.PayloadAttestation) error {
if b.version < version.EPBS {
return consensus_types.ErrNotSupported("PayloadAttestations", b.version)
}
b.block.body.payloadAttestations = p
return nil
}
// SetSignedExecutionPayloadHeader sets the signed execution payload header of the block body.
func (b *SignedBeaconBlock) SetSignedExecutionPayloadHeader(h *enginev1.SignedExecutionPayloadHeader) error {
if b.version < version.EPBS {
return consensus_types.ErrNotSupported("SetSignedExecutionPayloadHeader", b.version)
}
b.block.body.signedExecutionPayloadHeader = h
return nil
}

View File

@@ -0,0 +1,26 @@
package blocks
import (
consensus_types "github.com/prysmaticlabs/prysm/v5/consensus-types"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
)
// SetPayloadAttestations sets the payload attestations in the block.
func (b *SignedBeaconBlock) SetPayloadAttestations(p []*eth.PayloadAttestation) error {
if b.version < version.EPBS {
return consensus_types.ErrNotSupported("PayloadAttestations", b.version)
}
b.block.body.payloadAttestations = p
return nil
}
// SetSignedExecutionPayloadHeader sets the signed execution payload header of the block body.
func (b *SignedBeaconBlock) SetSignedExecutionPayloadHeader(h *enginev1.SignedExecutionPayloadHeader) error {
if b.version < version.EPBS {
return consensus_types.ErrNotSupported("SetSignedExecutionPayloadHeader", b.version)
}
b.block.body.signedExecutionPayloadHeader = h
return nil
}

View File

@@ -43,7 +43,9 @@ func Test_EpbsBlock_SetPayloadAttestations(t *testing.T) {
}
require.NoError(t, b.SetPayloadAttestations(payloadAttestation))
require.DeepEqual(t, b.block.body.PayloadAttestations(), payloadAttestation)
expectedPA, err := b.block.body.PayloadAttestations()
require.NoError(t, err)
require.DeepEqual(t, expectedPA, payloadAttestation)
}
func Test_EpbsBlock_SetSignedExecutionPayloadHeader(t *testing.T) {
@@ -67,5 +69,7 @@ func Test_EpbsBlock_SetSignedExecutionPayloadHeader(t *testing.T) {
Signature: []byte("signature"),
}
require.NoError(t, b.SetSignedExecutionPayloadHeader(signedExecutionPayloadHeader))
require.DeepEqual(t, b.block.body.SignedExecutionPayloadHeader(), signedExecutionPayloadHeader)
expectedHeader, err := b.block.body.SignedExecutionPayloadHeader()
require.NoError(t, err)
require.DeepEqual(t, expectedHeader, signedExecutionPayloadHeader)
}

View File

@@ -69,12 +69,8 @@ type ReadOnlyBeaconBlockBody interface {
Execution() (ExecutionData, error)
BLSToExecutionChanges() ([]*ethpb.SignedBLSToExecutionChange, error)
BlobKzgCommitments() ([][]byte, error)
}
type ROBlockBodyEpbs interface {
ReadOnlyBeaconBlockBody
PayloadAttestations() []*ethpb.PayloadAttestation
SignedExecutionPayloadHeader() *enginev1.SignedExecutionPayloadHeader
PayloadAttestations() ([]*ethpb.PayloadAttestation, error)
SignedExecutionPayloadHeader() (*enginev1.SignedExecutionPayloadHeader, error)
}
type SignedBeaconBlock interface {