Fork-specific consensus-types interfaces (#13948)

* fork-specific interface for electra

* add electra to wrapped payload switch

* use electra body in block factory

* deepsource

* rm pb getters from electra payload

---------

Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
This commit is contained in:
kasey
2024-05-03 12:12:27 -05:00
committed by GitHub
parent c312a88aa3
commit 80bff0dc2d
12 changed files with 158 additions and 143 deletions

View File

@@ -35,11 +35,15 @@ func NewWrappedExecutionData(v proto.Message, weiValue math.Wei) (interfaces.Exe
return WrappedExecutionPayloadCapella(pbStruct, weiValue)
case *enginev1.ExecutionPayloadDeneb:
return WrappedExecutionPayloadDeneb(pbStruct, weiValue)
case *enginev1.ExecutionPayloadElectra:
return WrappedExecutionPayloadElectra(pbStruct, weiValue)
default:
return nil, ErrUnsupportedVersion
}
}
var _ interfaces.ExecutionData = &executionPayload{}
// WrappedExecutionPayload is a constructor which wraps a protobuf execution payload into an interface.
func WrappedExecutionPayload(p *enginev1.ExecutionPayload) (interfaces.ExecutionData, error) {
w := executionPayload{p: p}
@@ -199,16 +203,6 @@ func (executionPayload) ValueInGwei() (uint64, error) {
return 0, consensus_types.ErrUnsupportedField
}
// DepositReceipts --
func (e executionPayload) DepositReceipts() ([]*enginev1.DepositReceipt, error) {
return nil, consensus_types.ErrUnsupportedField
}
// WithdrawalRequests --
func (e executionPayload) WithdrawalRequests() ([]*enginev1.ExecutionLayerWithdrawalRequest, error) {
return nil, consensus_types.ErrUnsupportedField
}
// executionPayloadHeader is a convenience wrapper around a blinded beacon block body's execution header data structure
// This wrapper allows us to conform to a common interface so that beacon
// blocks for future forks can also be applied across Prysm without issues.
@@ -216,6 +210,8 @@ type executionPayloadHeader struct {
p *enginev1.ExecutionPayloadHeader
}
var _ interfaces.ExecutionData = &executionPayloadHeader{}
// WrappedExecutionPayloadHeader is a constructor which wraps a protobuf execution header into an interface.
func WrappedExecutionPayloadHeader(p *enginev1.ExecutionPayloadHeader) (interfaces.ExecutionData, error) {
w := executionPayloadHeader{p: p}
@@ -375,16 +371,6 @@ func (executionPayloadHeader) ValueInGwei() (uint64, error) {
return 0, consensus_types.ErrUnsupportedField
}
// DepositReceipts --
func (e executionPayloadHeader) DepositReceipts() ([]*enginev1.DepositReceipt, error) {
return nil, consensus_types.ErrUnsupportedField
}
// WithdrawalRequests --
func (e executionPayloadHeader) WithdrawalRequests() ([]*enginev1.ExecutionLayerWithdrawalRequest, error) {
return nil, consensus_types.ErrUnsupportedField
}
// PayloadToHeader converts `payload` into execution payload header format.
func PayloadToHeader(payload interfaces.ExecutionData) (*enginev1.ExecutionPayloadHeader, error) {
txs, err := payload.Transactions()
@@ -422,6 +408,8 @@ type executionPayloadCapella struct {
gweiValue uint64
}
var _ interfaces.ExecutionData = &executionPayloadCapella{}
// WrappedExecutionPayloadCapella is a constructor which wraps a protobuf execution payload into an interface.
func WrappedExecutionPayloadCapella(p *enginev1.ExecutionPayloadCapella, value math.Wei) (interfaces.ExecutionData, error) {
w := executionPayloadCapella{p: p, weiValue: value, gweiValue: uint64(math.WeiToGwei(value))}
@@ -581,16 +569,6 @@ func (e executionPayloadCapella) ValueInGwei() (uint64, error) {
return e.gweiValue, nil
}
// DepositReceipts --
func (e executionPayloadCapella) DepositReceipts() ([]*enginev1.DepositReceipt, error) {
return nil, consensus_types.ErrUnsupportedField
}
// WithdrawalRequests --
func (e executionPayloadCapella) WithdrawalRequests() ([]*enginev1.ExecutionLayerWithdrawalRequest, error) {
return nil, consensus_types.ErrUnsupportedField
}
// executionPayloadHeaderCapella is a convenience wrapper around a blinded beacon block body's execution header data structure
// This wrapper allows us to conform to a common interface so that beacon
// blocks for future forks can also be applied across Prysm without issues.
@@ -600,6 +578,8 @@ type executionPayloadHeaderCapella struct {
gweiValue uint64
}
var _ interfaces.ExecutionData = &executionPayloadHeaderCapella{}
// WrappedExecutionPayloadHeaderCapella is a constructor which wraps a protobuf execution header into an interface.
func WrappedExecutionPayloadHeaderCapella(p *enginev1.ExecutionPayloadHeaderCapella, value math.Wei) (interfaces.ExecutionData, error) {
w := executionPayloadHeaderCapella{p: p, weiValue: value, gweiValue: uint64(math.WeiToGwei(value))}
@@ -759,16 +739,6 @@ func (e executionPayloadHeaderCapella) ValueInGwei() (uint64, error) {
return e.gweiValue, nil
}
// DepositReceipts --
func (e executionPayloadHeaderCapella) DepositReceipts() ([]*enginev1.DepositReceipt, error) {
return nil, consensus_types.ErrUnsupportedField
}
// WithdrawalRequests --
func (e executionPayloadHeaderCapella) WithdrawalRequests() ([]*enginev1.ExecutionLayerWithdrawalRequest, error) {
return nil, consensus_types.ErrUnsupportedField
}
// PayloadToHeaderCapella converts `payload` into execution payload header format.
func PayloadToHeaderCapella(payload interfaces.ExecutionData) (*enginev1.ExecutionPayloadHeaderCapella, error) {
txs, err := payload.Transactions()
@@ -856,7 +826,7 @@ func PayloadToHeaderDeneb(payload interfaces.ExecutionData) (*enginev1.Execution
}
// PayloadToHeaderElectra converts `payload` into execution payload header format.
func PayloadToHeaderElectra(payload interfaces.ExecutionData) (*enginev1.ExecutionPayloadHeaderElectra, error) {
func PayloadToHeaderElectra(payload interfaces.ExecutionDataElectra) (*enginev1.ExecutionPayloadHeaderElectra, error) {
txs, err := payload.Transactions()
if err != nil {
return nil, err
@@ -882,18 +852,13 @@ func PayloadToHeaderElectra(payload interfaces.ExecutionData) (*enginev1.Executi
return nil, err
}
depositReceipts, err := payload.DepositReceipts()
if err != nil {
return nil, err
}
depositReceipts := payload.DepositReceipts()
depositReceiptsRoot, err := ssz.DepositReceiptSliceRoot(depositReceipts, fieldparams.MaxDepositReceiptsPerPayload)
if err != nil {
return nil, err
}
withdrawalRequests, err := payload.WithdrawalRequests()
if err != nil {
return nil, err
}
withdrawalRequests := payload.WithdrawalRequests()
withdrawalRequestsRoot, err := ssz.WithdrawalRequestSliceRoot(withdrawalRequests, fieldparams.MaxWithdrawalRequestsPerPayload)
if err != nil {
return nil, err
@@ -980,23 +945,14 @@ func IsEmptyExecutionData(data interfaces.ExecutionData) (bool, error) {
return false, nil
}
drs, err := data.DepositReceipts()
switch {
case errors.Is(err, consensus_types.ErrUnsupportedField):
case err != nil:
return false, err
default:
epe, postElectra := data.(interfaces.ExecutionDataElectra)
if postElectra {
drs := epe.DepositReceipts()
if len(drs) != 0 {
return false, nil
}
}
wrs, err := data.WithdrawalRequests()
switch {
case errors.Is(err, consensus_types.ErrUnsupportedField):
case err != nil:
return false, err
default:
wrs := epe.WithdrawalRequests()
if len(wrs) != 0 {
return false, nil
}
@@ -1014,6 +970,8 @@ type executionPayloadHeaderDeneb struct {
gweiValue uint64
}
var _ interfaces.ExecutionData = &executionPayloadHeaderDeneb{}
// WrappedExecutionPayloadHeaderDeneb is a constructor which wraps a protobuf execution header into an interface.
func WrappedExecutionPayloadHeaderDeneb(p *enginev1.ExecutionPayloadHeaderDeneb, value math.Wei) (interfaces.ExecutionData, error) {
w := executionPayloadHeaderDeneb{p: p, weiValue: value, gweiValue: uint64(math.WeiToGwei(value))}
@@ -1168,16 +1126,6 @@ func (e executionPayloadHeaderDeneb) ValueInGwei() (uint64, error) {
return e.gweiValue, nil
}
// DepositReceipts --
func (e executionPayloadHeaderDeneb) DepositReceipts() ([]*enginev1.DepositReceipt, error) {
return nil, consensus_types.ErrUnsupportedField
}
// WithdrawalRequests --
func (e executionPayloadHeaderDeneb) WithdrawalRequests() ([]*enginev1.ExecutionLayerWithdrawalRequest, error) {
return nil, consensus_types.ErrUnsupportedField
}
// IsBlinded returns true if the underlying data is blinded.
func (e executionPayloadHeaderDeneb) IsBlinded() bool {
return true
@@ -1192,6 +1140,8 @@ type executionPayloadDeneb struct {
gweiValue uint64
}
var _ interfaces.ExecutionData = &executionPayloadDeneb{}
// WrappedExecutionPayloadDeneb is a constructor which wraps a protobuf execution payload into an interface.
func WrappedExecutionPayloadDeneb(p *enginev1.ExecutionPayloadDeneb, value math.Wei) (interfaces.ExecutionData, error) {
w := executionPayloadDeneb{p: p, weiValue: value, gweiValue: uint64(math.WeiToGwei(value))}
@@ -1344,16 +1294,6 @@ func (e executionPayloadDeneb) ValueInGwei() (uint64, error) {
return e.gweiValue, nil
}
// DepositReceipts --
func (e executionPayloadDeneb) DepositReceipts() ([]*enginev1.DepositReceipt, error) {
return nil, consensus_types.ErrUnsupportedField
}
// WithdrawalRequests --
func (e executionPayloadDeneb) WithdrawalRequests() ([]*enginev1.ExecutionLayerWithdrawalRequest, error) {
return nil, consensus_types.ErrUnsupportedField
}
// IsBlinded returns true if the underlying data is blinded.
func (e executionPayloadDeneb) IsBlinded() bool {
return false
@@ -1368,6 +1308,9 @@ type executionPayloadHeaderElectra struct {
gweiValue uint64
}
var _ interfaces.ExecutionData = &executionPayloadElectra{}
var _ interfaces.ExecutionDataElectra = &executionPayloadElectra{}
// WrappedExecutionPayloadHeaderElectra is a constructor which wraps a protobuf execution header into an interface.
func WrappedExecutionPayloadHeaderElectra(p *enginev1.ExecutionPayloadHeaderElectra, value math.Wei) (interfaces.ExecutionData, error) {
w := executionPayloadHeaderElectra{p: p, weiValue: value, gweiValue: uint64(math.WeiToGwei(value))}
@@ -1512,26 +1455,6 @@ func (e executionPayloadHeaderElectra) ExcessBlobGas() (uint64, error) {
return e.p.ExcessBlobGas, nil
}
// PbElectra --
func (e executionPayloadHeaderElectra) PbElectra() (*enginev1.ExecutionPayloadElectra, error) {
return nil, consensus_types.ErrUnsupportedField
}
// PbDeneb --
func (executionPayloadHeaderElectra) PbDeneb() (*enginev1.ExecutionPayloadDeneb, error) {
return nil, consensus_types.ErrUnsupportedField
}
// PbBellatrix --
func (executionPayloadHeaderElectra) PbBellatrix() (*enginev1.ExecutionPayload, error) {
return nil, consensus_types.ErrUnsupportedField
}
// PbCapella --
func (executionPayloadHeaderElectra) PbCapella() (*enginev1.ExecutionPayloadCapella, error) {
return nil, consensus_types.ErrUnsupportedField
}
// ValueInWei --
func (e executionPayloadHeaderElectra) ValueInWei() (math.Wei, error) {
return e.weiValue, nil
@@ -1575,6 +1498,9 @@ func WrappedExecutionPayloadElectra(p *enginev1.ExecutionPayloadElectra, value m
return w, nil
}
var _ interfaces.ExecutionData = &executionPayloadElectra{}
var _ interfaces.ExecutionDataElectra = &executionPayloadElectra{}
// IsNil checks if the underlying data is nil.
func (e executionPayloadElectra) IsNil() bool {
return e.p == nil
@@ -1708,26 +1634,6 @@ func (e executionPayloadElectra) ExcessBlobGas() (uint64, error) {
return e.p.ExcessBlobGas, nil
}
// PbBellatrix --
func (e executionPayloadElectra) PbBellatrix() (*enginev1.ExecutionPayload, error) {
return nil, consensus_types.ErrUnsupportedField
}
// PbCapella --
func (e executionPayloadElectra) PbCapella() (*enginev1.ExecutionPayloadCapella, error) {
return nil, consensus_types.ErrUnsupportedField
}
// PbDeneb --
func (e executionPayloadElectra) PbDeneb() (*enginev1.ExecutionPayloadDeneb, error) {
return nil, consensus_types.ErrUnsupportedField
}
// PbElectra --
func (e executionPayloadElectra) PbElectra() (*enginev1.ExecutionPayloadElectra, error) {
return e.p, nil
}
// ValueInWei --
func (e executionPayloadElectra) ValueInWei() (math.Wei, error) {
return e.weiValue, nil
@@ -1739,13 +1645,13 @@ func (e executionPayloadElectra) ValueInGwei() (uint64, error) {
}
// DepositReceipts --
func (e executionPayloadElectra) DepositReceipts() ([]*enginev1.DepositReceipt, error) {
return e.p.DepositReceipts, nil
func (e executionPayloadElectra) DepositReceipts() []*enginev1.DepositReceipt {
return e.p.DepositReceipts
}
// WithdrawalRequests --
func (e executionPayloadElectra) WithdrawalRequests() ([]*enginev1.ExecutionLayerWithdrawalRequest, error) {
return e.p.WithdrawalRequests, nil
func (e executionPayloadElectra) WithdrawalRequests() []*enginev1.ExecutionLayerWithdrawalRequest {
return e.p.WithdrawalRequests
}
// IsBlinded returns true if the underlying data is blinded.

View File

@@ -460,10 +460,11 @@ func BuildSignedBeaconBlockFromExecutionPayload(blk interfaces.ReadOnlySignedBea
if err != nil {
return nil, err
}
consolidations, err := b.Body().Consolidations()
if err != nil {
return nil, err
electraBody, ok := b.Body().(interfaces.ROBlockBodyElectra)
if !ok {
return nil, errors.Wrapf(interfaces.ErrInvalidCast, "%T does not support electra getters", b.Body())
}
consolidations := electraBody.Consolidations()
var atts []*eth.AttestationElectra
if b.Body().Attestations() != nil {
atts = make([]*eth.AttestationElectra, len(b.Body().Attestations()))

View File

@@ -7,6 +7,7 @@ import (
"testing"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
@@ -537,3 +538,26 @@ func TestBuildSignedBeaconBlockFromExecutionPayload(t *testing.T) {
require.DeepEqual(t, uint64(321), payload.BlobGasUsed)
})
}
func TestElectraBlockBodyCast(t *testing.T) {
t.Run("deneb cast fails", func(t *testing.T) {
pb := &eth.BeaconBlockBodyDeneb{}
i, err := NewBeaconBlockBody(pb)
require.NoError(t, err)
b, ok := i.(*BeaconBlockBody)
require.Equal(t, true, ok)
assert.Equal(t, version.Deneb, b.version)
_, err = interfaces.AsROBlockBodyElectra(b)
require.ErrorIs(t, err, interfaces.ErrInvalidCast)
})
t.Run("electra cast succeeds", func(t *testing.T) {
pb := &eth.BeaconBlockBodyElectra{}
i, err := NewBeaconBlockBody(pb)
require.NoError(t, err)
b, ok := i.(*BeaconBlockBody)
require.Equal(t, true, ok)
assert.Equal(t, version.Electra, b.version)
_, err = interfaces.AsROBlockBodyElectra(b)
require.NoError(t, err)
})
}

View File

@@ -252,7 +252,11 @@ func (b *SignedBeaconBlock) ToBlinded() (interfaces.ReadOnlySignedBeaconBlock, e
Signature: b.signature[:],
})
case *enginev1.ExecutionPayloadElectra:
header, err := PayloadToHeaderElectra(payload)
pe, ok := payload.(interfaces.ExecutionDataElectra)
if !ok {
return nil, interfaces.ErrIncompatibleFork
}
header, err := PayloadToHeaderElectra(pe)
if err != nil {
return nil, err
}
@@ -281,7 +285,6 @@ func (b *SignedBeaconBlock) ToBlinded() (interfaces.ReadOnlySignedBeaconBlock, e
},
Signature: b.signature[:],
})
default:
return nil, fmt.Errorf("%T is not an execution payload header", p)
}
@@ -1170,11 +1173,8 @@ func (b *BeaconBlockBody) BlobKzgCommitments() ([][]byte, error) {
}
}
func (b *BeaconBlockBody) Consolidations() ([]*eth.SignedConsolidation, error) {
if b.version < version.Electra {
return nil, consensus_types.ErrNotSupported("Consolidations", b.version)
}
return b.signedConsolidations, nil
func (b *BeaconBlockBody) Consolidations() []*eth.SignedConsolidation {
return b.signedConsolidations
}
// Version returns the version of the beacon block body

View File

@@ -490,3 +490,9 @@ func hydrateBeaconBlockBody() *eth.BeaconBlockBody {
},
}
}
func TestPreElectraFailsInterfaceAssertion(t *testing.T) {
var epd interfaces.ExecutionData = &executionPayloadDeneb{}
_, ok := epd.(interfaces.ExecutionDataElectra)
require.Equal(t, false, ok)
}

View File

@@ -57,6 +57,9 @@ type BeaconBlockBody struct {
signedConsolidations []*eth.SignedConsolidation
}
var _ interfaces.ReadOnlyBeaconBlockBody = &BeaconBlockBody{}
var _ interfaces.ROBlockBodyElectra = &BeaconBlockBody{}
// BeaconBlock is the main beacon block structure. It can represent any block type.
type BeaconBlock struct {
version int

View File

@@ -4,6 +4,8 @@ go_library(
name = "go_default_library",
srcs = [
"beacon_block.go",
"cast.go",
"error.go",
"utils.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces",
@@ -15,6 +17,7 @@ go_library(
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//runtime/version:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
@@ -24,14 +27,19 @@ go_library(
go_test(
name = "go_default_test",
srcs = ["utils_test.go"],
srcs = [
"error_test.go",
"utils_test.go",
],
embed = [":go_default_library"],
deps = [
":go_default_library",
"//config/fieldparams:go_default_library",
"//consensus-types/blocks:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"@com_github_pkg_errors//:go_default_library",
],
)

View File

@@ -1,6 +1,7 @@
package interfaces
import (
"github.com/pkg/errors"
ssz "github.com/prysmaticlabs/fastssz"
"github.com/prysmaticlabs/go-bitfield"
field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
@@ -12,6 +13,8 @@ import (
"google.golang.org/protobuf/proto"
)
var ErrIncompatibleFork = errors.New("Can't convert to fork-specific interface")
// ReadOnlySignedBeaconBlock is an interface describing the method set of
// a signed beacon block.
type ReadOnlySignedBeaconBlock interface {
@@ -70,7 +73,11 @@ type ReadOnlyBeaconBlockBody interface {
Execution() (ExecutionData, error)
BLSToExecutionChanges() ([]*ethpb.SignedBLSToExecutionChange, error)
BlobKzgCommitments() ([][]byte, error)
Consolidations() ([]*ethpb.SignedConsolidation, error)
}
type ROBlockBodyElectra interface {
ReadOnlyBeaconBlockBody
Consolidations() []*ethpb.SignedConsolidation
}
type SignedBeaconBlock interface {
@@ -125,8 +132,12 @@ type ExecutionData interface {
WithdrawalsRoot() ([]byte, error)
ValueInWei() (math.Wei, error)
ValueInGwei() (uint64, error)
DepositReceipts() ([]*enginev1.DepositReceipt, error)
WithdrawalRequests() ([]*enginev1.ExecutionLayerWithdrawalRequest, error)
}
type ExecutionDataElectra interface {
ExecutionData
DepositReceipts() []*enginev1.DepositReceipt
WithdrawalRequests() []*enginev1.ExecutionLayerWithdrawalRequest
}
type Attestation interface {

View File

@@ -0,0 +1,15 @@
package interfaces
import "github.com/prysmaticlabs/prysm/v5/runtime/version"
// AsROBlockBodyElectra safely asserts the ReadOnlyBeaconBlockBody to a ROBlockBodyElectra.
// This allows the caller to access methods on the block body which are only available on values after
// the Electra hard fork. If the value is for an earlier fork (based on comparing its Version() to the electra version)
// an error will be returned. Callers that want to conditionally process electra data can check for this condition
// and safely ignore it like `if err != nil && errors.Is(interfaces.ErrInvalidCast) {`
func AsROBlockBodyElectra(in ReadOnlyBeaconBlockBody) (ROBlockBodyElectra, error) {
if in.Version() >= version.Electra {
return in.(ROBlockBodyElectra), nil
}
return nil, NewInvalidCastError(in.Version(), version.Electra)
}

View File

@@ -0,0 +1,27 @@
package interfaces
import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
)
var ErrInvalidCast = errors.New("unable to cast between types")
type InvalidCastError struct {
from int
to int
}
func (e *InvalidCastError) Error() string {
return errors.Wrapf(ErrInvalidCast,
"from=%s(%d), to=%s(%d)", version.String(e.from), e.from, version.String(e.to), e.to).
Error()
}
func (*InvalidCastError) Is(err error) bool {
return errors.Is(err, ErrInvalidCast)
}
func NewInvalidCastError(from, to int) *InvalidCastError {
return &InvalidCastError{from: from, to: to}
}

View File

@@ -0,0 +1,14 @@
package interfaces
import (
"testing"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
func TestNewInvalidCastError(t *testing.T) {
err := NewInvalidCastError(version.Phase0, version.Electra)
require.Equal(t, true, errors.Is(err, ErrInvalidCast))
}

View File

@@ -284,8 +284,7 @@ func (b *BeaconBlockBody) BlobKzgCommitments() ([][]byte, error) {
func (b *BeaconBlockBody) Attestations() []interfaces.Attestation {
panic("implement me")
}
func (b *BeaconBlockBody) Consolidations() ([]*eth.SignedConsolidation, error) {
func (b *BeaconBlockBody) Consolidations() []*eth.SignedConsolidation {
panic("implement me")
}
@@ -296,3 +295,4 @@ func (b *BeaconBlockBody) Version() int {
var _ interfaces.ReadOnlySignedBeaconBlock = &SignedBeaconBlock{}
var _ interfaces.ReadOnlyBeaconBlock = &BeaconBlock{}
var _ interfaces.ReadOnlyBeaconBlockBody = &BeaconBlockBody{}
var _ interfaces.ROBlockBodyElectra = &BeaconBlockBody{}