Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proposer gRPC suppot for Electra #13984

Merged
merged 2 commits into from
May 11, 2024
Merged
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
2 changes: 1 addition & 1 deletion beacon-chain/core/transition/transition_no_verify_sig.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ func ProcessOperationsNoVerifyAttsSigs(
if err != nil {
return nil, err
}
case version.Altair, version.Bellatrix, version.Capella, version.Deneb:
case version.Altair, version.Bellatrix, version.Capella, version.Deneb, version.Electra:
state, err = altairOperations(ctx, state, beaconBlock)
if err != nil {
return nil, err
Expand Down
8 changes: 8 additions & 0 deletions beacon-chain/execution/testing/mock_engine_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type EngineClient struct {
ExecutionPayload *pb.ExecutionPayload
ExecutionPayloadCapella *pb.ExecutionPayloadCapella
ExecutionPayloadDeneb *pb.ExecutionPayloadDeneb
ExecutionPayloadElectra *pb.ExecutionPayloadElectra
ExecutionBlock *pb.ExecutionBlock
Err error
ErrLatestExecBlock error
Expand Down Expand Up @@ -61,6 +62,13 @@ func (e *EngineClient) ForkchoiceUpdated(

// GetPayload --
func (e *EngineClient) GetPayload(_ context.Context, _ [8]byte, s primitives.Slot) (interfaces.ExecutionData, *pb.BlobsBundle, bool, error) {
if slots.ToEpoch(s) >= params.BeaconConfig().ElectraForkEpoch {
ed, err := blocks.WrappedExecutionPayloadElectra(e.ExecutionPayloadElectra, big.NewInt(int64(e.BlockValue)))
if err != nil {
return nil, nil, false, err
}
return ed, e.BlobsBundle, e.BuilderOverride, nil
}
if slots.ToEpoch(s) >= params.BeaconConfig().DenebForkEpoch {
ed, err := blocks.WrappedExecutionPayloadDeneb(e.ExecutionPayloadDeneb, big.NewInt(int64(e.BlockValue)))
if err != nil {
Expand Down
13 changes: 13 additions & 0 deletions beacon-chain/rpc/prysm/v1alpha1/validator/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,17 @@ func sendVerifiedBlocks(stream ethpb.BeaconNodeValidator_StreamBlocksAltairServe
return nil
}
b.Block = &ethpb.StreamBlocksResponse_DenebBlock{DenebBlock: phBlk}
case version.Electra:
pb, err := data.SignedBlock.Proto()
if err != nil {
return errors.Wrap(err, "could not get protobuf block")
}
phBlk, ok := pb.(*ethpb.SignedBeaconBlockElectra)
if !ok {
log.Warn("Mismatch between version and block type, was expecting SignedBeaconBlockElectra")
return nil
}
b.Block = &ethpb.StreamBlocksResponse_ElectraBlock{ElectraBlock: phBlk}
}

if err := stream.Send(b); err != nil {
Expand Down Expand Up @@ -210,6 +221,8 @@ func (vs *Server) sendBlocks(stream ethpb.BeaconNodeValidator_StreamBlocksAltair
b.Block = &ethpb.StreamBlocksResponse_CapellaBlock{CapellaBlock: p}
case *ethpb.SignedBeaconBlockDeneb:
b.Block = &ethpb.StreamBlocksResponse_DenebBlock{DenebBlock: p}
case *ethpb.SignedBeaconBlockElectra:
b.Block = &ethpb.StreamBlocksResponse_ElectraBlock{ElectraBlock: p}
default:
log.Errorf("Unknown block type %T", p)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func (vs *Server) constructGenericBeaconBlock(sBlk interfaces.SignedBeaconBlock,
payloadValue := sBlk.ValueInWei()

switch sBlk.Version() {
case version.Electra:
return vs.constructElectraBlock(blockProto, isBlinded, payloadValue, blobsBundle), nil
case version.Deneb:
return vs.constructDenebBlock(blockProto, isBlinded, payloadValue, blobsBundle), nil
case version.Capella:
Expand All @@ -42,6 +44,18 @@ func (vs *Server) constructGenericBeaconBlock(sBlk interfaces.SignedBeaconBlock,
}

// Helper functions for constructing blocks for each version
func (vs *Server) constructElectraBlock(blockProto proto.Message, isBlinded bool, payloadValue math.Wei, bundle *enginev1.BlobsBundle) *ethpb.GenericBeaconBlock {
if isBlinded {
return &ethpb.GenericBeaconBlock{Block: &ethpb.GenericBeaconBlock_BlindedElectra{BlindedElectra: blockProto.(*ethpb.BlindedBeaconBlockElectra)}, IsBlinded: true, PayloadValue: (*payloadValue).String()}
}
electraContents := &ethpb.BeaconBlockContentsElectra{Block: blockProto.(*ethpb.BeaconBlockElectra)}
if bundle != nil {
electraContents.KzgProofs = bundle.Proofs
electraContents.Blobs = bundle.Blobs
}
return &ethpb.GenericBeaconBlock{Block: &ethpb.GenericBeaconBlock_Electra{Electra: electraContents}, IsBlinded: false, PayloadValue: (*payloadValue).String()}
}

func (vs *Server) constructDenebBlock(blockProto proto.Message, isBlinded bool, payloadValue math.Wei, bundle *enginev1.BlobsBundle) *ethpb.GenericBeaconBlock {
if isBlinded {
return &ethpb.GenericBeaconBlock{Block: &ethpb.GenericBeaconBlock_BlindedDeneb{BlindedDeneb: blockProto.(*ethpb.BlindedBeaconBlockDeneb)}, IsBlinded: true, PayloadValue: (*payloadValue).String()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,31 @@ func TestConstructGenericBeaconBlock(t *testing.T) {
require.ErrorContains(t, "block cannot be nil", err)
})

// Test for Electra version
t.Run("electra block", func(t *testing.T) {
eb := util.NewBeaconBlockElectra()
eb.Block.Body.Consolidations = []*eth.SignedConsolidation{
{
Signature: make([]byte, 96),
Message: &eth.Consolidation{
SourceIndex: 1,
TargetIndex: 2,
Epoch: 3,
},
},
}
b, err := blocks.NewSignedBeaconBlock(eb)
require.NoError(t, err)
r1, err := eb.Block.HashTreeRoot()
require.NoError(t, err)
result, err := vs.constructGenericBeaconBlock(b, nil)
require.NoError(t, err)
r2, err := result.GetElectra().Block.HashTreeRoot()
require.NoError(t, err)
require.Equal(t, r1, r2)
require.Equal(t, result.IsBlinded, false)
})

// Test for Deneb version
t.Run("deneb block", func(t *testing.T) {
eb := util.NewBeaconBlockDeneb()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,35 @@ import (
func getEmptyBlock(slot primitives.Slot) (interfaces.SignedBeaconBlock, error) {
var sBlk interfaces.SignedBeaconBlock
var err error
epoch := slots.ToEpoch(slot)
switch {
case slots.ToEpoch(slot) < params.BeaconConfig().AltairForkEpoch:
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlock{Block: &ethpb.BeaconBlock{Body: &ethpb.BeaconBlockBody{}}})
case epoch >= params.BeaconConfig().ElectraForkEpoch:
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockElectra{Block: &ethpb.BeaconBlockElectra{Body: &ethpb.BeaconBlockBodyElectra{}}})
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
case slots.ToEpoch(slot) < params.BeaconConfig().BellatrixForkEpoch:
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockAltair{Block: &ethpb.BeaconBlockAltair{Body: &ethpb.BeaconBlockBodyAltair{}}})
case epoch >= params.BeaconConfig().DenebForkEpoch:
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockDeneb{Block: &ethpb.BeaconBlockDeneb{Body: &ethpb.BeaconBlockBodyDeneb{}}})
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
case slots.ToEpoch(slot) < params.BeaconConfig().CapellaForkEpoch:
case epoch >= params.BeaconConfig().CapellaForkEpoch:
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockCapella{Block: &ethpb.BeaconBlockCapella{Body: &ethpb.BeaconBlockBodyCapella{}}})
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
case epoch >= params.BeaconConfig().BellatrixForkEpoch:
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockBellatrix{Block: &ethpb.BeaconBlockBellatrix{Body: &ethpb.BeaconBlockBodyBellatrix{}}})
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
case slots.ToEpoch(slot) < params.BeaconConfig().DenebForkEpoch:
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockCapella{Block: &ethpb.BeaconBlockCapella{Body: &ethpb.BeaconBlockBodyCapella{}}})
case epoch >= params.BeaconConfig().AltairForkEpoch:
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockAltair{Block: &ethpb.BeaconBlockAltair{Body: &ethpb.BeaconBlockBodyAltair{}}})
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
default:
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockDeneb{Block: &ethpb.BeaconBlockDeneb{Body: &ethpb.BeaconBlockBodyDeneb{}}})
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlock{Block: &ethpb.BeaconBlock{Body: &ethpb.BeaconBlockBody{}}})
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is just repeating the existing pattern, but this switch feels upside down. Typically we write this kind of type switch where the inequality is the other way:

bEpoch := slots.ToEpoch(slot) 
if bEpoch >= params.BeaconConfig().ElectraForkEpoch {
... // do electra thing
}
if bEpoch >= params.BeaconConfig().DenebForkEpoch {
... // do deneb thing
}
// do phase 0 thing

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func Test_getEmptyBlock(t *testing.T) {
config.BellatrixForkEpoch = 2
config.CapellaForkEpoch = 3
config.DenebForkEpoch = 4
config.ElectraForkEpoch = 5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you document the effect of setting these fork epoch values? I assume the point here is just to set the highest epoch to something that won't overflow (when it is set to max uint) and making sure these epochs don't conflict with each other.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just so we can test the conditions. Here is how it's used in the test case:
slot: primitives.Slot(params.BeaconConfig().ElectraForkEpoch) * params.BeaconConfig().SlotsPerEpoch,
which gets passed to getEmptyBlock(tt.slot), then reached under case epoch >= params.BeaconConfig().ElectraForkEpoch:

params.OverrideBeaconConfig(config)

tests := []struct {
Expand Down Expand Up @@ -61,6 +62,15 @@ func Test_getEmptyBlock(t *testing.T) {
return b
},
},
{
name: "electra",
slot: primitives.Slot(params.BeaconConfig().ElectraForkEpoch) * params.BeaconConfig().SlotsPerEpoch,
want: func() interfaces.ReadOnlySignedBeaconBlock {
b, err := blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockElectra{Block: &ethpb.BeaconBlockElectra{Body: &ethpb.BeaconBlockBodyElectra{}}})
require.NoError(t, err)
return b
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func (vs *Server) getLocalPayload(ctx context.Context, blk interfaces.ReadOnlyBe
}
var attr payloadattribute.Attributer
switch st.Version() {
case version.Deneb:
case version.Deneb, version.Electra:
withdrawals, _, err := st.ExpectedWithdrawals()
if err != nil {
return nil, false, err
Expand Down
153 changes: 153 additions & 0 deletions beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,132 @@ func TestServer_GetBeaconBlock_Deneb(t *testing.T) {
require.DeepEqual(t, got.GetDeneb().Block.Body.BlobKzgCommitments, kc)
}

func TestServer_GetBeaconBlock_Electra(t *testing.T) {
db := dbutil.SetupDB(t)
ctx := context.Background()
transition.SkipSlotCache.Disable()

params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.ElectraForkEpoch = 5
cfg.DenebForkEpoch = 4
cfg.CapellaForkEpoch = 3
cfg.BellatrixForkEpoch = 2
cfg.AltairForkEpoch = 1
params.OverrideBeaconConfig(cfg)
beaconState, privKeys := util.DeterministicGenesisState(t, 64)

stateRoot, err := beaconState.HashTreeRoot(ctx)
require.NoError(t, err, "Could not hash genesis state")

genesis := b.NewGenesisBlock(stateRoot[:])
util.SaveBlock(t, ctx, db, genesis)

parentRoot, err := genesis.Block.HashTreeRoot()
require.NoError(t, err, "Could not get signing root")
require.NoError(t, db.SaveState(ctx, beaconState, parentRoot), "Could not save genesis state")
require.NoError(t, db.SaveHeadBlockRoot(ctx, parentRoot), "Could not save genesis state")

electraSlot, err := slots.EpochStart(params.BeaconConfig().ElectraForkEpoch)
require.NoError(t, err)

var scBits [fieldparams.SyncAggregateSyncCommitteeBytesLength]byte
blk := &ethpb.SignedBeaconBlockElectra{
Block: &ethpb.BeaconBlockElectra{
Slot: electraSlot + 1,
ParentRoot: parentRoot[:],
StateRoot: genesis.Block.StateRoot,
Body: &ethpb.BeaconBlockBodyElectra{
Consolidations: []*ethpb.SignedConsolidation{
{
Message: &ethpb.Consolidation{
SourceIndex: 1,
TargetIndex: 2,
Epoch: 3,
},
Signature: bytesutil.PadTo([]byte("sig"), 96),
},
},
RandaoReveal: genesis.Block.Body.RandaoReveal,
Graffiti: genesis.Block.Body.Graffiti,
Eth1Data: genesis.Block.Body.Eth1Data,
SyncAggregate: &ethpb.SyncAggregate{SyncCommitteeBits: scBits[:], SyncCommitteeSignature: make([]byte, 96)},
ExecutionPayload: &enginev1.ExecutionPayloadElectra{
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: make([]byte, fieldparams.RootLength),
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
},
},
},
}

blkRoot, err := blk.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, err, "Could not get signing root")
require.NoError(t, db.SaveState(ctx, beaconState, blkRoot), "Could not save genesis state")
require.NoError(t, db.SaveHeadBlockRoot(ctx, blkRoot), "Could not save genesis state")

random, err := helpers.RandaoMix(beaconState, slots.ToEpoch(beaconState.Slot()))
require.NoError(t, err)
timeStamp, err := slots.ToTime(beaconState.GenesisTime(), electraSlot+1)
require.NoError(t, err)
dr := []*enginev1.DepositReceipt{{
Pubkey: bytesutil.PadTo(privKeys[0].PublicKey().Marshal(), 48),
WithdrawalCredentials: bytesutil.PadTo([]byte("wc"), 32),
Amount: 123,
Signature: bytesutil.PadTo([]byte("sig"), 96),
Index: 456,
}}
wr := []*enginev1.ExecutionLayerWithdrawalRequest{
{
SourceAddress: bytesutil.PadTo([]byte("sa"), 20),
ValidatorPubkey: bytesutil.PadTo(privKeys[1].PublicKey().Marshal(), 48),
Amount: 789,
},
}
payload := &enginev1.ExecutionPayloadElectra{
Timestamp: uint64(timeStamp.Unix()),
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: random,
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
DepositReceipts: dr,
WithdrawalRequests: wr,
}

proposerServer := getProposerServer(db, beaconState, parentRoot[:])
proposerServer.ExecutionEngineCaller = &mockExecution.EngineClient{
PayloadIDBytes: &enginev1.PayloadIDBytes{1},
ExecutionPayloadElectra: payload,
}

randaoReveal, err := util.RandaoReveal(beaconState, 0, privKeys)
require.NoError(t, err)

graffiti := bytesutil.ToBytes32([]byte("eth2"))
require.NoError(t, err)
req := &ethpb.BlockRequest{
Slot: electraSlot + 1,
RandaoReveal: randaoReveal,
Graffiti: graffiti[:],
}

got, err := proposerServer.GetBeaconBlock(ctx, req)
require.NoError(t, err)
p := got.GetElectra().Block.Body.ExecutionPayload
require.DeepEqual(t, dr, p.DepositReceipts)
require.DeepEqual(t, wr, p.WithdrawalRequests)
}

func TestServer_GetBeaconBlock_Optimistic(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
Expand Down Expand Up @@ -744,6 +870,33 @@ func TestProposer_ProposeBlock_OK(t *testing.T) {
},
err: "blob KZG commitments don't match number of blobs or KZG proofs",
},
{
name: "electra block no blob",
block: func(parent [32]byte) *ethpb.GenericSignedBeaconBlock {
sb := &ethpb.SignedBeaconBlockContentsElectra{
Block: &ethpb.SignedBeaconBlockElectra{
Block: &ethpb.BeaconBlockElectra{Slot: 5, ParentRoot: parent[:], Body: util.HydrateBeaconBlockBodyElectra(&ethpb.BeaconBlockBodyElectra{})},
},
}
blk := &ethpb.GenericSignedBeaconBlock_Electra{Electra: sb}
return &ethpb.GenericSignedBeaconBlock{Block: blk, IsBlinded: false}
},
},
{
name: "electra block some blob",
block: func(parent [32]byte) *ethpb.GenericSignedBeaconBlock {

sb := &ethpb.SignedBeaconBlockContentsElectra{
Block: &ethpb.SignedBeaconBlockElectra{
Block: &ethpb.BeaconBlockElectra{Slot: 5, ParentRoot: parent[:], Body: util.HydrateBeaconBlockBodyElectra(&ethpb.BeaconBlockBodyElectra{})},
},
KzgProofs: [][]byte{{0x01}, {0x02}, {0x03}},
Blobs: [][]byte{{0x01}, {0x02}, {0x03}},
}
blk := &ethpb.GenericSignedBeaconBlock_Electra{Electra: sb}
return &ethpb.GenericSignedBeaconBlock{Block: blk, IsBlinded: false}
},
},
{
name: "blind deneb block some blobs",
block: func(parent [32]byte) *ethpb.GenericSignedBeaconBlock {
Expand Down
Loading
Loading