Reconstruct full Capella block (#11732)

* Merge branch 'reconstruct-capella-block' into capella

(cherry picked from commit b0601580ef)

# Conflicts:
#	beacon-chain/rpc/eth/beacon/blocks.go
#	proto/engine/v1/json_marshal_unmarshal.go

* remove unneeded test

* rename methods

* add doc to interface

* deepsource

(cherry picked from commit 903cab75ee)

# Conflicts:
#	beacon-chain/execution/testing/mock_engine_client.go

* bzl

* fix failing tests

* single ExecutionBlockByHash function

* fix engine mock

* deepsource

* reorder checks

* single execution block type

* update tests

* update doc

* bytes test

* remove toWithdrawalJSON

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
This commit is contained in:
Radosław Kapka
2022-12-14 14:42:48 +01:00
committed by GitHub
parent 9c21809c50
commit 1fbb3f3e51
12 changed files with 293 additions and 34 deletions

View File

@@ -15,6 +15,7 @@ import (
"github.com/prysmaticlabs/prysm/v3/consensus-types/interfaces"
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v3/runtime/version"
"github.com/prysmaticlabs/prysm/v3/time/slots"
"github.com/sirupsen/logrus"
)
@@ -92,6 +93,7 @@ func (s *Service) getBlkParentHashAndTD(ctx context.Context, blkHash []byte) ([]
if blk == nil {
return nil, nil, errors.New("pow block is nil")
}
blk.Version = version.Bellatrix
blkTDBig, err := hexutil.DecodeBig(blk.TotalDifficulty)
if err != nil {
return nil, nil, errors.Wrap(err, "could not decode merge block total difficulty")

View File

@@ -121,6 +121,7 @@ go_test(
"//network/authorization:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",

View File

@@ -57,7 +57,7 @@ type ForkchoiceUpdatedResponse struct {
// block with an execution payload from a signed beacon block and a connection
// to an execution client's engine API.
type ExecutionPayloadReconstructor interface {
ReconstructFullBellatrixBlock(
ReconstructFullBlock(
ctx context.Context, blindedBlock interfaces.SignedBeaconBlock,
) (interfaces.SignedBeaconBlock, error)
ReconstructFullBellatrixBlockBatch(
@@ -403,9 +403,9 @@ func (s *Service) HeaderByNumber(ctx context.Context, number *big.Int) (*types.H
return hdr, err
}
// ReconstructFullBellatrixBlock takes in a blinded beacon block and reconstructs
// ReconstructFullBlock takes in a blinded beacon block and reconstructs
// a beacon block with a full execution payload via the engine API.
func (s *Service) ReconstructFullBellatrixBlock(
func (s *Service) ReconstructFullBlock(
ctx context.Context, blindedBlock interfaces.SignedBeaconBlock,
) (interfaces.SignedBeaconBlock, error) {
if err := blocks.BeaconBlockIsNil(blindedBlock); err != nil {
@@ -437,11 +437,12 @@ func (s *Service) ReconstructFullBellatrixBlock(
if executionBlock == nil {
return nil, fmt.Errorf("received nil execution block for request by hash %#x", executionBlockHash)
}
executionBlock.Version = blindedBlock.Version()
payload, err := fullPayloadFromExecutionBlock(header, executionBlock)
if err != nil {
return nil, err
}
fullBlock, err := blocks.BuildSignedBeaconBlockFromExecutionPayload(blindedBlock, payload)
fullBlock, err := blocks.BuildSignedBeaconBlockFromExecutionPayload(blindedBlock, payload.Proto())
if err != nil {
return nil, err
}
@@ -504,7 +505,7 @@ func (s *Service) ReconstructFullBellatrixBlockBatch(
if err != nil {
return nil, err
}
fullBlock, err := blocks.BuildSignedBeaconBlockFromExecutionPayload(blindedBlocks[realIdx], payload)
fullBlock, err := blocks.BuildSignedBeaconBlockFromExecutionPayload(blindedBlocks[realIdx], payload.Proto())
if err != nil {
return nil, err
}
@@ -526,26 +527,47 @@ func (s *Service) ReconstructFullBellatrixBlockBatch(
func fullPayloadFromExecutionBlock(
header interfaces.ExecutionData, block *pb.ExecutionBlock,
) (*pb.ExecutionPayload, error) {
) (interfaces.ExecutionData, error) {
if header.IsNil() || block == nil {
return nil, errors.New("execution block and header cannot be nil")
}
if !bytes.Equal(header.BlockHash(), block.Hash[:]) {
blockHash := block.Hash
if !bytes.Equal(header.BlockHash(), blockHash[:]) {
return nil, fmt.Errorf(
"block hash field in execution header %#x does not match execution block hash %#x",
header.BlockHash(),
block.Hash,
blockHash,
)
}
txs := make([][]byte, len(block.Transactions))
for i, tx := range block.Transactions {
blockTransactions := block.Transactions
txs := make([][]byte, len(blockTransactions))
for i, tx := range blockTransactions {
txBin, err := tx.MarshalBinary()
if err != nil {
return nil, err
}
txs[i] = txBin
}
return &pb.ExecutionPayload{
if block.Version == version.Bellatrix {
return blocks.WrappedExecutionPayload(&pb.ExecutionPayload{
ParentHash: header.ParentHash(),
FeeRecipient: header.FeeRecipient(),
StateRoot: header.StateRoot(),
ReceiptsRoot: header.ReceiptsRoot(),
LogsBloom: header.LogsBloom(),
PrevRandao: header.PrevRandao(),
BlockNumber: header.BlockNumber(),
GasLimit: header.GasLimit(),
GasUsed: header.GasUsed(),
Timestamp: header.Timestamp(),
ExtraData: header.ExtraData(),
BaseFeePerGas: header.BaseFeePerGas(),
BlockHash: blockHash[:],
Transactions: txs,
})
}
return blocks.WrappedExecutionPayloadCapella(&pb.ExecutionPayloadCapella{
ParentHash: header.ParentHash(),
FeeRecipient: header.FeeRecipient(),
StateRoot: header.StateRoot(),
@@ -558,9 +580,10 @@ func fullPayloadFromExecutionBlock(
Timestamp: header.Timestamp(),
ExtraData: header.ExtraData(),
BaseFeePerGas: header.BaseFeePerGas(),
BlockHash: block.Hash[:],
BlockHash: blockHash[:],
Transactions: txs,
}, nil
Withdrawals: block.Withdrawals,
})
}
// Handles errors received from the RPC server according to the specification.

View File

@@ -27,6 +27,7 @@ import (
payloadattribute "github.com/prysmaticlabs/prysm/v3/consensus-types/payload-attribute"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
pb "github.com/prysmaticlabs/prysm/v3/proto/engine/v1"
"github.com/prysmaticlabs/prysm/v3/runtime/version"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/require"
"github.com/prysmaticlabs/prysm/v3/testing/util"
@@ -493,7 +494,7 @@ func TestReconstructFullBellatrixBlock(t *testing.T) {
t.Run("nil block", func(t *testing.T) {
service := &Service{}
_, err := service.ReconstructFullBellatrixBlock(ctx, nil)
_, err := service.ReconstructFullBlock(ctx, nil)
require.ErrorContains(t, "nil data", err)
})
t.Run("only blinded block", func(t *testing.T) {
@@ -502,7 +503,7 @@ func TestReconstructFullBellatrixBlock(t *testing.T) {
bellatrixBlock := util.NewBeaconBlockBellatrix()
wrapped, err := blocks.NewSignedBeaconBlock(bellatrixBlock)
require.NoError(t, err)
_, err = service.ReconstructFullBellatrixBlock(ctx, wrapped)
_, err = service.ReconstructFullBlock(ctx, wrapped)
require.ErrorContains(t, want, err)
})
t.Run("pre-merge execution payload", func(t *testing.T) {
@@ -517,7 +518,7 @@ func TestReconstructFullBellatrixBlock(t *testing.T) {
require.NoError(t, err)
wantedWrapped, err := blocks.NewSignedBeaconBlock(wanted)
require.NoError(t, err)
reconstructed, err := service.ReconstructFullBellatrixBlock(ctx, wrapped)
reconstructed, err := service.ReconstructFullBlock(ctx, wrapped)
require.NoError(t, err)
require.DeepEqual(t, wantedWrapped, reconstructed)
})
@@ -590,7 +591,7 @@ func TestReconstructFullBellatrixBlock(t *testing.T) {
blindedBlock.Block.Body.ExecutionPayloadHeader = header
wrapped, err := blocks.NewSignedBeaconBlock(blindedBlock)
require.NoError(t, err)
reconstructed, err := service.ReconstructFullBellatrixBlock(ctx, wrapped)
reconstructed, err := service.ReconstructFullBlock(ctx, wrapped)
require.NoError(t, err)
got, err := reconstructed.Block().Body().Execution()
@@ -1149,6 +1150,7 @@ func fixtures() map[string]interface{} {
receiptsRoot := bytesutil.PadTo([]byte("receiptsRoot"), fieldparams.RootLength)
logsBloom := bytesutil.PadTo([]byte("logs"), fieldparams.LogsBloomLength)
executionBlock := &pb.ExecutionBlock{
Version: version.Bellatrix,
Header: gethtypes.Header{
ParentHash: common.BytesToHash(parent),
UncleHash: common.BytesToHash(sha3Uncles),
@@ -1257,7 +1259,7 @@ func Test_fullPayloadFromExecutionBlock(t *testing.T) {
tests := []struct {
name string
args args
want *pb.ExecutionPayload
want func() interfaces.ExecutionData
err string
}{
{
@@ -1267,7 +1269,8 @@ func Test_fullPayloadFromExecutionBlock(t *testing.T) {
BlockHash: []byte("foo"),
},
block: &pb.ExecutionBlock{
Hash: common.BytesToHash([]byte("bar")),
Version: version.Bellatrix,
Hash: common.BytesToHash([]byte("bar")),
},
},
err: "does not match execution block hash",
@@ -1279,22 +1282,30 @@ func Test_fullPayloadFromExecutionBlock(t *testing.T) {
BlockHash: wantedHash[:],
},
block: &pb.ExecutionBlock{
Hash: wantedHash,
Version: version.Bellatrix,
Hash: wantedHash,
},
},
want: &pb.ExecutionPayload{
BlockHash: wantedHash[:],
want: func() interfaces.ExecutionData {
p, err := blocks.WrappedExecutionPayload(&pb.ExecutionPayload{
BlockHash: wantedHash[:],
Transactions: [][]byte{},
})
require.NoError(t, err)
return p
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
wrapped, err := blocks.WrappedExecutionPayloadHeader(tt.args.header)
require.NoError(t, err)
got, err := fullPayloadFromExecutionBlock(wrapped, tt.args.block)
if (err != nil) && !strings.Contains(err.Error(), tt.err) {
t.Fatalf("Wanted err %s got %v", tt.err, err)
if err != nil {
assert.ErrorContains(t, tt.err, err)
} else {
assert.DeepEqual(t, tt.want(), got)
}
require.DeepEqual(t, tt.want, got)
})
}
}

View File

@@ -76,7 +76,8 @@ func (e *EngineClient) ExecutionBlockByHash(_ context.Context, h common.Hash, _
return b, e.ErrExecBlockByHash
}
func (e *EngineClient) ReconstructFullBellatrixBlock(
// ReconstructFullBlock --
func (e *EngineClient) ReconstructFullBlock(
_ context.Context, blindedBlock interfaces.SignedBeaconBlock,
) (interfaces.SignedBeaconBlock, error) {
if !blindedBlock.Block().IsBlinded() {
@@ -94,12 +95,13 @@ func (e *EngineClient) ReconstructFullBellatrixBlock(
return blocks.BuildSignedBeaconBlockFromExecutionPayload(blindedBlock, payload)
}
// ReconstructFullBellatrixBlockBatch --
func (e *EngineClient) ReconstructFullBellatrixBlockBatch(
ctx context.Context, blindedBlocks []interfaces.SignedBeaconBlock,
) ([]interfaces.SignedBeaconBlock, error) {
fullBlocks := make([]interfaces.SignedBeaconBlock, 0, len(blindedBlocks))
for _, b := range blindedBlocks {
newBlock, err := e.ReconstructFullBellatrixBlock(ctx, b)
newBlock, err := e.ReconstructFullBlock(ctx, b)
if err != nil {
return nil, err
}

View File

@@ -506,7 +506,7 @@ func (bs *Server) GetBlockV2(ctx context.Context, req *ethpbv2.BlockRequestV2) (
if blindedBellatrixBlk == nil {
return nil, status.Error(codes.Internal, "Nil block")
}
signedFullBlock, err := bs.ExecutionPayloadReconstructor.ReconstructFullBellatrixBlock(ctx, blk)
signedFullBlock, err := bs.ExecutionPayloadReconstructor.ReconstructFullBlock(ctx, blk)
if err != nil {
return nil, status.Errorf(
codes.Internal,
@@ -641,7 +641,7 @@ func (bs *Server) GetBlockSSZV2(ctx context.Context, req *ethpbv2.BlockRequestV2
if blindedBellatrixBlk == nil {
return nil, status.Error(codes.Internal, "Nil block")
}
signedFullBlock, err := bs.ExecutionPayloadReconstructor.ReconstructFullBellatrixBlock(ctx, blk)
signedFullBlock, err := bs.ExecutionPayloadReconstructor.ReconstructFullBlock(ctx, blk)
if err != nil {
return nil, status.Errorf(
codes.Internal,

View File

@@ -75,7 +75,7 @@ func (s *Service) beaconBlocksRootRPCHandler(ctx context.Context, msg interface{
}
if blk.Block().IsBlinded() {
blk, err = s.cfg.executionPayloadReconstructor.ReconstructFullBellatrixBlock(ctx, blk)
blk, err = s.cfg.executionPayloadReconstructor.ReconstructFullBlock(ctx, blk)
if err != nil {
log.WithError(err).Error("Could not get reconstruct full bellatrix block from blinded body")
s.writeErrorResponseToStream(responseCodeServerError, types.ErrGeneric.Error(), stream)

View File

@@ -100,6 +100,15 @@ func ToBytes4(x []byte) [4]byte {
return y
}
// ToBytes20 is a convenience method for converting a byte slice to a fix
// sized 20 byte array. This method will truncate the input if it is larger
// than 20 bytes.
func ToBytes20(x []byte) [20]byte {
var y [20]byte
copy(y[:], x)
return y
}
// ToBytes32 is a convenience method for converting a byte slice to a fix
// sized 32 byte array. This method will truncate the input if it is larger
// than 32 bytes.

View File

@@ -637,6 +637,24 @@ func TestToBytes48Array(t *testing.T) {
}
}
func TestToBytes20(t *testing.T) {
tests := []struct {
a []byte
b [20]byte
}{
{nil, [20]byte{}},
{[]byte{}, [20]byte{}},
{[]byte{1}, [20]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
{[]byte{1, 2, 3}, [20]byte{1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
{[]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}},
{[]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21}, [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}},
}
for _, tt := range tests {
b := bytesutil.ToBytes20(tt.a)
assert.DeepEqual(t, tt.b, b)
}
}
func TestLittleEndianBytesToBigInt(t *testing.T) {
bytes := make([]byte, 8)
binary.LittleEndian.PutUint64(bytes, 1234567890)

View File

@@ -76,14 +76,17 @@ go_library(
deps = [
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/eth/ext:go_default_library",
"//runtime/version:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_ethereum_go_ethereum//core/types:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@go_googleapis//google/api:annotations_go_proto",
"@io_bazel_rules_go//proto/wkt:descriptor_go_proto",
"@io_bazel_rules_go//proto/wkt:timestamp_go_proto",
@@ -114,6 +117,7 @@ go_test(
":go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//testing/require:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",

View File

@@ -11,7 +11,9 @@ import (
gethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/pkg/errors"
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v3/runtime/version"
)
// PayloadIDBytes defines a custom type for Payload IDs used by the engine API
@@ -26,10 +28,12 @@ func (b PayloadIDBytes) MarshalJSON() ([]byte, error) {
// ExecutionBlock is the response kind received by the eth_getBlockByHash and
// eth_getBlockByNumber endpoints via JSON-RPC.
type ExecutionBlock struct {
Version int
gethtypes.Header
Hash common.Hash `json:"hash"`
Transactions []*gethtypes.Transaction `json:"transactions"`
TotalDifficulty string `json:"totalDifficulty"`
Withdrawals []*Withdrawal `json:"withdrawals"`
}
func (e *ExecutionBlock) MarshalJSON() ([]byte, error) {
@@ -44,13 +48,22 @@ func (e *ExecutionBlock) MarshalJSON() ([]byte, error) {
decoded["hash"] = e.Hash.String()
decoded["transactions"] = e.Transactions
decoded["totalDifficulty"] = e.TotalDifficulty
if e.Version == version.Capella {
decoded["withdrawals"] = e.Withdrawals
}
return json.Marshal(decoded)
}
func (e *ExecutionBlock) UnmarshalJSON(enc []byte) error {
type transactionJson struct {
type transactionsJson struct {
Transactions []*gethtypes.Transaction `json:"transactions"`
}
type withdrawalsJson struct {
Withdrawals []*withdrawalJSON `json:"withdrawals"`
}
if err := e.Header.UnmarshalJSON(enc); err != nil {
return err
}
@@ -71,6 +84,26 @@ func (e *ExecutionBlock) UnmarshalJSON(enc []byte) error {
if !ok {
return errors.New("expected `totalDifficulty` field in JSON response")
}
rawWithdrawals, ok := decoded["withdrawals"]
if !ok || rawWithdrawals == nil {
e.Version = version.Bellatrix
} else {
e.Version = version.Capella
j := &withdrawalsJson{}
if err := json.Unmarshal(enc, j); err != nil {
return err
}
ws := make([]*Withdrawal, len(j.Withdrawals))
for i, wj := range j.Withdrawals {
ws[i], err = wj.ToWithdrawal()
if err != nil {
return err
}
}
e.Withdrawals = ws
}
rawTxList, ok := decoded["transactions"]
if !ok || rawTxList == nil {
// Exit early if there are no transactions stored in the json payload.
@@ -80,8 +113,6 @@ func (e *ExecutionBlock) UnmarshalJSON(enc []byte) error {
if !ok {
return errors.Errorf("expected transaction list to be of a slice interface type.")
}
//
for _, tx := range txsList {
// If the transaction is just a hex string, do not attempt to
// unmarshal into a full transaction object.
@@ -91,7 +122,7 @@ func (e *ExecutionBlock) UnmarshalJSON(enc []byte) error {
}
// If the block contains a list of transactions, we JSON unmarshal
// them into a list of geth transaction objects.
txJson := &transactionJson{}
txJson := &transactionsJson{}
if err := json.Unmarshal(enc, txJson); err != nil {
return err
}
@@ -109,6 +140,70 @@ func (b *PayloadIDBytes) UnmarshalJSON(enc []byte) error {
return nil
}
type withdrawalJSON struct {
Index *hexutil.Uint64 `json:"index"`
Validator *hexutil.Uint64 `json:"validatorIndex"`
Address *common.Address `json:"address"`
Amount string `json:"amount"`
}
func (j *withdrawalJSON) ToWithdrawal() (*Withdrawal, error) {
w := &Withdrawal{}
b, err := json.Marshal(j)
if err != nil {
return nil, err
}
if err := w.UnmarshalJSON(b); err != nil {
return nil, err
}
return w, nil
}
func (w *Withdrawal) MarshalJSON() ([]byte, error) {
index := hexutil.Uint64(w.WithdrawalIndex)
validatorIndex := hexutil.Uint64(w.ValidatorIndex)
address := common.BytesToAddress(w.ExecutionAddress)
wei := new(big.Int).SetUint64(1000000000)
amountWei := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), wei)
return json.Marshal(withdrawalJSON{
Index: &index,
Validator: &validatorIndex,
Address: &address,
Amount: hexutil.EncodeBig(amountWei),
})
}
func (w *Withdrawal) UnmarshalJSON(enc []byte) error {
dec := withdrawalJSON{}
if err := json.Unmarshal(enc, &dec); err != nil {
return err
}
if dec.Index == nil {
return errors.New("missing withdrawal index")
}
if dec.Validator == nil {
return errors.New("missing validator index")
}
if dec.Address == nil {
return errors.New("missing execution address")
}
*w = Withdrawal{}
w.WithdrawalIndex = uint64(*dec.Index)
w.ValidatorIndex = types.ValidatorIndex(*dec.Validator)
w.ExecutionAddress = dec.Address.Bytes()
wei := new(big.Int).SetUint64(1000000000)
amountWei, err := hexutil.DecodeBig(dec.Amount)
if err != nil {
return err
}
amount := new(big.Int).Div(amountWei, wei)
if !amount.IsUint64() {
return errors.New("withdrawal amount overflow")
}
w.Amount = amount.Uint64()
return nil
}
type executionPayloadJSON struct {
ParentHash *common.Hash `json:"parentHash"`
FeeRecipient *common.Address `json:"feeRecipient"`

View File

@@ -6,15 +6,24 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
gethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
"github.com/prysmaticlabs/prysm/v3/config/params"
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1"
"github.com/prysmaticlabs/prysm/v3/testing/require"
)
type withdrawalJSON struct {
Index *hexutil.Uint64 `json:"index"`
Validator *hexutil.Uint64 `json:"validatorIndex"`
Address *common.Address `json:"address"`
Amount string `json:"amount"`
}
func TestJsonMarshalUnmarshal(t *testing.T) {
t.Run("payload attributes", func(t *testing.T) {
random := bytesutil.PadTo([]byte("random"), fieldparams.RootLength)
@@ -312,6 +321,91 @@ func TestJsonMarshalUnmarshal(t *testing.T) {
require.Equal(t, 1, len(payloadPb.Transactions))
require.DeepEqual(t, txs[0].Hash(), payloadPb.Transactions[0].Hash())
})
t.Run("execution block with withdrawals", func(t *testing.T) {
baseFeePerGas := big.NewInt(1770307273)
want := &gethtypes.Header{
Number: big.NewInt(1),
ParentHash: common.BytesToHash([]byte("parent")),
UncleHash: common.BytesToHash([]byte("uncle")),
Coinbase: common.BytesToAddress([]byte("coinbase")),
Root: common.BytesToHash([]byte("uncle")),
TxHash: common.BytesToHash([]byte("txHash")),
ReceiptHash: common.BytesToHash([]byte("receiptHash")),
Bloom: gethtypes.BytesToBloom([]byte("bloom")),
Difficulty: big.NewInt(2),
GasLimit: 3,
GasUsed: 4,
Time: 5,
BaseFee: baseFeePerGas,
Extra: []byte("extraData"),
MixDigest: common.BytesToHash([]byte("mix")),
Nonce: gethtypes.EncodeNonce(6),
}
enc, err := json.Marshal(want)
require.NoError(t, err)
payloadItems := make(map[string]interface{})
require.NoError(t, json.Unmarshal(enc, &payloadItems))
blockHash := want.Hash()
payloadItems["hash"] = blockHash.String()
payloadItems["totalDifficulty"] = "0x393a2e53de197c"
withdrawalIndex1 := hexutil.Uint64(1)
withdrawalIndex2 := hexutil.Uint64(2)
withdrawalValidator1 := hexutil.Uint64(1)
withdrawalValidator2 := hexutil.Uint64(2)
address1 := common.Address(bytesutil.ToBytes20([]byte("address1")))
address2 := common.Address(bytesutil.ToBytes20([]byte("address2")))
payloadItems["withdrawals"] = []*withdrawalJSON{
{
Index: &withdrawalIndex1,
Validator: &withdrawalValidator1,
Address: &address1,
Amount: "0x3b9aca00",
},
{
Index: &withdrawalIndex2,
Validator: &withdrawalValidator2,
Address: &address2,
Amount: "0x77359400",
},
}
encodedPayloadItems, err := json.Marshal(payloadItems)
require.NoError(t, err)
payloadPb := &enginev1.ExecutionBlock{}
require.NoError(t, json.Unmarshal(encodedPayloadItems, payloadPb))
require.DeepEqual(t, blockHash, payloadPb.Hash)
require.DeepEqual(t, want.Number, payloadPb.Number)
require.DeepEqual(t, want.ParentHash, payloadPb.ParentHash)
require.DeepEqual(t, want.UncleHash, payloadPb.UncleHash)
require.DeepEqual(t, want.Coinbase, payloadPb.Coinbase)
require.DeepEqual(t, want.Root, payloadPb.Root)
require.DeepEqual(t, want.TxHash, payloadPb.TxHash)
require.DeepEqual(t, want.ReceiptHash, payloadPb.ReceiptHash)
require.DeepEqual(t, want.Bloom, payloadPb.Bloom)
require.DeepEqual(t, want.Difficulty, payloadPb.Difficulty)
require.DeepEqual(t, payloadItems["totalDifficulty"], payloadPb.TotalDifficulty)
require.DeepEqual(t, want.GasUsed, payloadPb.GasUsed)
require.DeepEqual(t, want.GasLimit, payloadPb.GasLimit)
require.DeepEqual(t, want.Time, payloadPb.Time)
require.DeepEqual(t, want.BaseFee, payloadPb.BaseFee)
require.DeepEqual(t, want.Extra, payloadPb.Extra)
require.DeepEqual(t, want.MixDigest, payloadPb.MixDigest)
require.DeepEqual(t, want.Nonce, payloadPb.Nonce)
require.Equal(t, 2, len(payloadPb.Withdrawals))
require.Equal(t, uint64(1), payloadPb.Withdrawals[0].WithdrawalIndex)
require.Equal(t, types.ValidatorIndex(1), payloadPb.Withdrawals[0].ValidatorIndex)
require.DeepEqual(t, bytesutil.PadTo([]byte("address1"), 20), payloadPb.Withdrawals[0].ExecutionAddress)
require.Equal(t, uint64(1), payloadPb.Withdrawals[0].Amount)
require.Equal(t, uint64(2), payloadPb.Withdrawals[1].WithdrawalIndex)
require.Equal(t, types.ValidatorIndex(2), payloadPb.Withdrawals[1].ValidatorIndex)
require.DeepEqual(t, bytesutil.PadTo([]byte("address2"), 20), payloadPb.Withdrawals[1].ExecutionAddress)
require.Equal(t, uint64(2), payloadPb.Withdrawals[1].Amount)
})
}
func TestPayloadIDBytes_MarshalUnmarshalJSON(t *testing.T) {