Skip to content

Commit

Permalink
cover v2 cases with an electra block
Browse files Browse the repository at this point in the history
  • Loading branch information
kasey committed May 17, 2024
1 parent f601dab commit e5f8d40
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 6 deletions.
72 changes: 67 additions & 5 deletions beacon-chain/execution/payload_body_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type blindedBlockFixtures struct {
denebBlock *fullAndBlinded
emptyDenebBlock *fullAndBlinded
afterSkipDeneb *fullAndBlinded
electra *fullAndBlinded
}

type fullAndBlinded struct {
Expand All @@ -102,27 +103,39 @@ func denebSlot(t *testing.T) primitives.Slot {
return s
}

func electraSlot(t *testing.T) primitives.Slot {
s, err := slots.EpochStart(params.BeaconConfig().ElectraForkEpoch)
require.NoError(t, err)
return s
}

func testBlindedBlockFixtures(t *testing.T) *blindedBlockFixtures {
pfx := fixturesStruct()
fx := &blindedBlockFixtures{}
full := pfx.ExecutionPayloadDeneb
// this func overrides fixture blockhashes to ensure they are unique
full.BlockHash = bytesutil.PadTo([]byte("full"), 32)
denebBlock, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, denebSlot(t), 0, util.WithGeneratorOptionPayload(full))
denebBlock, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, denebSlot(t), 0, util.WithPayloadSetter(full))
fx.denebBlock = blindedBlockWithHeader(t, denebBlock)

empty := pfx.EmptyExecutionPayloadDeneb
empty.BlockHash = bytesutil.PadTo([]byte("empty"), 32)
empty.BlockNumber = 2
emptyDenebBlock, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, denebSlot(t), 0, util.WithGeneratorOptionPayload(empty))
emptyDenebBlock, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, denebSlot(t)+1, 0, util.WithPayloadSetter(empty))
fx.emptyDenebBlock = blindedBlockWithHeader(t, emptyDenebBlock)

afterSkip := fixturesStruct().ExecutionPayloadDeneb
// this func overrides fixture blockhashes to ensure they are unique
afterSkip.BlockHash = bytesutil.PadTo([]byte("afterSkip"), 32)
afterSkip.BlockNumber = 4
afterSkipBlock, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, denebSlot(t), 0, util.WithGeneratorOptionPayload(afterSkip))
afterSkipBlock, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, denebSlot(t)+3, 0, util.WithPayloadSetter(afterSkip))
fx.afterSkipDeneb = blindedBlockWithHeader(t, afterSkipBlock)

electra := fixturesStruct().ExecutionPayloadElectra
electra.BlockHash = bytesutil.PadTo([]byte("electra"), 32)
electra.BlockNumber = 5
electraBlock, _ := util.GenerateTestElectraBlockWithSidecar(t, [32]byte{}, electraSlot(t), 0, util.WithElectraPayload(electra))
fx.electra = blindedBlockWithHeader(t, electraBlock)
return fx
}

Expand Down Expand Up @@ -264,6 +277,8 @@ func TestComputeRanges(t *testing.T) {
}

func TestReconstructBlindedBlockBatchFallbackToRange(t *testing.T) {
undo := util.HackElectraMaxuint(t)
defer undo()
ctx := context.Background()
t.Run("fallback fails", func(t *testing.T) {
cli, srv := newMockEngine(t)
Expand Down Expand Up @@ -335,12 +350,59 @@ func TestReconstructBlindedBlockBatchFallbackToRange(t *testing.T) {
}
mockWriteResult(t, w, msg, executionPayloadBodies)
})
unblind := []interfaces.ReadOnlySignedBeaconBlock{
// separate methods for the electra block
srv.register(GetPayloadBodiesByHashV2, func(msg *jsonrpcMessage, w http.ResponseWriter, r *http.Request) {
executionPayloadBodies := []*pb.ExecutionPayloadBody{nil}
mockWriteResult(t, w, msg, executionPayloadBodies)
})
srv.register(GetPayloadBodiesByRangeV2, func(msg *jsonrpcMessage, w http.ResponseWriter, r *http.Request) {
p := mockParseUintList(t, msg.Params)
require.Equal(t, 2, len(p))
start, count := p[0], p[1]
require.Equal(t, fx.electra.blinded.header.BlockNumber(), start)
require.Equal(t, uint64(1), count)
executionPayloadBodies := []*pb.ExecutionPayloadBody{
payloadToBody(t, fx.electra.blinded.header),
}
mockWriteResult(t, w, msg, executionPayloadBodies)
})
blind := []interfaces.ReadOnlySignedBeaconBlock{
fx.denebBlock.blinded.block,
fx.emptyDenebBlock.blinded.block,
fx.afterSkipDeneb.blinded.block,
}
_, err := reconstructBlindedBlockBatch(ctx, cli, unblind)
unblind, err := reconstructBlindedBlockBatch(ctx, cli, blind)
require.NoError(t, err)
for i := range unblind {
testAssertReconstructedEquivalent(t, blind[i], unblind[i])
}
})
}

func TestReconstructBlindedBlockBatchDenebAndElectra(t *testing.T) {
undo := util.HackElectraMaxuint(t)
defer undo()
t.Run("deneb and electra", func(t *testing.T) {
cli, srv := newMockEngine(t)
fx := testBlindedBlockFixtures(t)
// The reconstructed should make separate calls for the deneb (v1) and electra (v2) blocks.
srv.register(GetPayloadBodiesByHashV1, func(msg *jsonrpcMessage, w http.ResponseWriter, r *http.Request) {
executionPayloadBodies := []*pb.ExecutionPayloadBody{payloadToBody(t, fx.denebBlock.blinded.header)}
mockWriteResult(t, w, msg, executionPayloadBodies)
})
srv.register(GetPayloadBodiesByHashV2, func(msg *jsonrpcMessage, w http.ResponseWriter, r *http.Request) {
executionPayloadBodies := []*pb.ExecutionPayloadBody{payloadToBody(t, fx.electra.blinded.header)}
mockWriteResult(t, w, msg, executionPayloadBodies)
})
blinded := []interfaces.ReadOnlySignedBeaconBlock{
fx.denebBlock.blinded.block,
fx.electra.blinded.block,
}
unblinded, err := reconstructBlindedBlockBatch(context.Background(), cli, blinded)
require.NoError(t, err)
require.Equal(t, len(blinded), len(unblinded))
for i := range unblinded {
testAssertReconstructedEquivalent(t, blinded[i], unblinded[i])
}
})
}
2 changes: 1 addition & 1 deletion testing/util/deneb.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func WithProposerSigning(idx primitives.ValidatorIndex, sk bls.SecretKey, valRoo
}
}

func WithGeneratorOptionPayload(p *enginev1.ExecutionPayloadDeneb) DenebBlockGeneratorOption {
func WithPayloadSetter(p *enginev1.ExecutionPayloadDeneb) DenebBlockGeneratorOption {
return func(g *denebBlockGenerator) {
g.payload = p
}
Expand Down
175 changes: 175 additions & 0 deletions testing/util/electra.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
package util

import (
"encoding/binary"
"math"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/network/forks"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)

// HackElectraMaxuint is helpful for tests that need to set up cases where the electra fork has passed.
Expand All @@ -23,3 +36,165 @@ func HackElectraMaxuint(t *testing.T) func() {
require.NoError(t, undo())
}
}

type ElectraBlockGeneratorOption func(*electraBlockGenerator)

type electraBlockGenerator struct {
parent [32]byte
slot primitives.Slot
nblobs int
sign bool
sk bls.SecretKey
proposer primitives.ValidatorIndex
valRoot []byte
payload *enginev1.ExecutionPayloadElectra
}

func WithElectraProposerSigning(idx primitives.ValidatorIndex, sk bls.SecretKey, valRoot []byte) ElectraBlockGeneratorOption {
return func(g *electraBlockGenerator) {
g.sign = true
g.proposer = idx
g.sk = sk
g.valRoot = valRoot
}
}

func WithElectraPayload(p *enginev1.ExecutionPayloadElectra) ElectraBlockGeneratorOption {
return func(g *electraBlockGenerator) {
g.payload = p
}
}

func GenerateTestElectraBlockWithSidecar(t *testing.T, parent [32]byte, slot primitives.Slot, nblobs int, opts ...ElectraBlockGeneratorOption) (blocks.ROBlock, []blocks.ROBlob) {
g := &electraBlockGenerator{
parent: parent,
slot: slot,
nblobs: nblobs,
}
for _, o := range opts {
o(g)
}

if g.payload == nil {
stateRoot := bytesutil.PadTo([]byte("stateRoot"), fieldparams.RootLength)
ads := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87")
tx := gethTypes.NewTx(&gethTypes.LegacyTx{
Nonce: 0,
To: &ads,
Value: big.NewInt(0),
Gas: 0,
GasPrice: big.NewInt(0),
Data: nil,
})

txs := []*gethTypes.Transaction{tx}
encodedBinaryTxs := make([][]byte, 1)
var err error
encodedBinaryTxs[0], err = txs[0].MarshalBinary()
require.NoError(t, err)
blockHash := bytesutil.ToBytes32([]byte("foo"))
logsBloom := bytesutil.PadTo([]byte("logs"), fieldparams.LogsBloomLength)
receiptsRoot := bytesutil.PadTo([]byte("receiptsRoot"), fieldparams.RootLength)
parentHash := bytesutil.PadTo([]byte("parentHash"), fieldparams.RootLength)
g.payload = &enginev1.ExecutionPayloadElectra{
ParentHash: parentHash,
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: stateRoot,
ReceiptsRoot: receiptsRoot,
LogsBloom: logsBloom,
PrevRandao: blockHash[:],
BlockNumber: 0,
GasLimit: 0,
GasUsed: 0,
Timestamp: 0,
ExtraData: make([]byte, 0),
BaseFeePerGas: bytesutil.PadTo([]byte("baseFeePerGas"), fieldparams.RootLength),
BlockHash: blockHash[:],
Transactions: encodedBinaryTxs,
Withdrawals: make([]*enginev1.Withdrawal, 0),
BlobGasUsed: 0,
ExcessBlobGas: 0,
DepositReceipts: generateTestDepositRequests(uint64(g.slot), 4),
WithdrawalRequests: generateTestWithdrawalRequests(uint64(g.slot), 4),
}
}

block := NewBeaconBlockElectra()
block.Block.Body.ExecutionPayload = g.payload
block.Block.Slot = g.slot
block.Block.ParentRoot = g.parent[:]
block.Block.ProposerIndex = g.proposer
commitments := make([][48]byte, g.nblobs)
block.Block.Body.BlobKzgCommitments = make([][]byte, g.nblobs)
for i := range commitments {
binary.LittleEndian.PutUint16(commitments[i][0:16], uint16(i))
binary.LittleEndian.PutUint16(commitments[i][16:32], uint16(g.slot))
block.Block.Body.BlobKzgCommitments[i] = commitments[i][:]
}

body, err := blocks.NewBeaconBlockBody(block.Block.Body)
require.NoError(t, err)
inclusion := make([][][]byte, len(commitments))
for i := range commitments {
proof, err := blocks.MerkleProofKZGCommitment(body, i)
require.NoError(t, err)
inclusion[i] = proof
}
if g.sign {
epoch := slots.ToEpoch(block.Block.Slot)
schedule := forks.NewOrderedSchedule(params.BeaconConfig())
version, err := schedule.VersionForEpoch(epoch)
require.NoError(t, err)
fork, err := schedule.ForkFromVersion(version)
require.NoError(t, err)
domain := params.BeaconConfig().DomainBeaconProposer
sig, err := signing.ComputeDomainAndSignWithoutState(fork, epoch, domain, g.valRoot, block.Block, g.sk)
require.NoError(t, err)
block.Signature = sig
}

root, err := block.Block.HashTreeRoot()
require.NoError(t, err)

sidecars := make([]blocks.ROBlob, len(commitments))
sbb, err := blocks.NewSignedBeaconBlock(block)
require.NoError(t, err)

sh, err := sbb.Header()
require.NoError(t, err)
for i, c := range block.Block.Body.BlobKzgCommitments {
sidecars[i] = GenerateTestDenebBlobSidecar(t, root, sh, i, c, inclusion[i])
}

rob, err := blocks.NewROBlock(sbb)
require.NoError(t, err)
return rob, sidecars
}

func generateTestDepositRequests(offset, n uint64) []*enginev1.DepositReceipt {
r := make([]*enginev1.DepositReceipt, n)
var i uint64
for i = 0; i < n; i++ {
r[i] = &enginev1.DepositReceipt{
Pubkey: make([]byte, 48),
WithdrawalCredentials: make([]byte, 32),
Amount: offset + i,
Signature: make([]byte, 96),
Index: offset + i + 100,
}
}
return r
}

func generateTestWithdrawalRequests(offset, n uint64) []*enginev1.ExecutionLayerWithdrawalRequest {
r := make([]*enginev1.ExecutionLayerWithdrawalRequest, n)
var i uint64
for i = 0; i < n; i++ {
r[i] = &enginev1.ExecutionLayerWithdrawalRequest{
SourceAddress: make([]byte, 20),
ValidatorPubkey: make([]byte, 48),
Amount: offset + i,
}
}
return r
}

0 comments on commit e5f8d40

Please sign in to comment.