mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 23:18:15 -05:00
Reconstruct full Capella block (#11732)
* Merge branch 'reconstruct-capella-block' into capella (cherry picked from commitb0601580ef) # 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 commit903cab75ee) # 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:
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user