diff --git a/consensus-types/wrapper/BUILD.bazel b/consensus-types/wrapper/BUILD.bazel index 2880ef383b..441af7dc78 100644 --- a/consensus-types/wrapper/BUILD.bazel +++ b/consensus-types/wrapper/BUILD.bazel @@ -14,6 +14,7 @@ go_library( importpath = "github.com/prysmaticlabs/prysm/consensus-types/wrapper", visibility = ["//visibility:public"], deps = [ + "//consensus-types/forks/bellatrix:go_default_library", "//consensus-types/interfaces:go_default_library", "//consensus-types/primitives:go_default_library", "//proto/engine/v1:go_default_library", @@ -39,6 +40,8 @@ go_test( ], deps = [ ":go_default_library", + "//config/fieldparams:go_default_library", + "//consensus-types/forks/bellatrix:go_default_library", "//consensus-types/primitives:go_default_library", "//encoding/bytesutil:go_default_library", "//proto/engine/v1:go_default_library", @@ -48,5 +51,6 @@ go_test( "//testing/assert:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", + "@com_github_pkg_errors//:go_default_library", ], ) diff --git a/consensus-types/wrapper/beacon_block.go b/consensus-types/wrapper/beacon_block.go index 94536b8ab4..bb86254e63 100644 --- a/consensus-types/wrapper/beacon_block.go +++ b/consensus-types/wrapper/beacon_block.go @@ -1,8 +1,12 @@ package wrapper import ( + "fmt" + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/consensus-types/forks/bellatrix" "github.com/prysmaticlabs/prysm/consensus-types/interfaces" + enginev1 "github.com/prysmaticlabs/prysm/proto/engine/v1" eth "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" ) @@ -145,6 +149,115 @@ func BuildSignedBeaconBlock(blk interfaces.BeaconBlock, signature []byte) (inter } } +// BuildSignedBeaconBlockFromExecutionPayload takes a signed, blinded beacon block and converts into +// a full, signed beacon block by specifying an execution payload. +func BuildSignedBeaconBlockFromExecutionPayload( + blk interfaces.SignedBeaconBlock, payload *enginev1.ExecutionPayload, +) (interfaces.SignedBeaconBlock, error) { + if err := BeaconBlockIsNil(blk); err != nil { + return nil, err + } + b := blk.Block() + payloadHeader, err := b.Body().ExecutionPayloadHeader() + switch { + case errors.Is(err, ErrUnsupportedField): + return nil, errors.Wrap(err, "can only build signed beacon block from blinded format") + case err != nil: + return nil, errors.Wrap(err, "could not get execution payload header") + default: + } + payloadRoot, err := payload.HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "could not hash tree root execution payload") + } + payloadHeaderRoot, err := payloadHeader.HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "could not hash tree root payload header") + } + if payloadRoot != payloadHeaderRoot { + return nil, fmt.Errorf( + "payload %#x and header %#x roots do not match", + payloadRoot, + payloadHeaderRoot, + ) + } + syncAgg, err := b.Body().SyncAggregate() + if err != nil { + return nil, errors.Wrap(err, "could not get sync aggregate from block body") + } + bellatrixFullBlock := ð.SignedBeaconBlockBellatrix{ + Block: ð.BeaconBlockBellatrix{ + Slot: b.Slot(), + ProposerIndex: b.ProposerIndex(), + ParentRoot: b.ParentRoot(), + StateRoot: b.StateRoot(), + Body: ð.BeaconBlockBodyBellatrix{ + RandaoReveal: b.Body().RandaoReveal(), + Eth1Data: b.Body().Eth1Data(), + Graffiti: b.Body().Graffiti(), + ProposerSlashings: b.Body().ProposerSlashings(), + AttesterSlashings: b.Body().AttesterSlashings(), + Attestations: b.Body().Attestations(), + Deposits: b.Body().Deposits(), + VoluntaryExits: b.Body().VoluntaryExits(), + SyncAggregate: syncAgg, + ExecutionPayload: payload, + }, + }, + Signature: blk.Signature(), + } + return wrappedBellatrixSignedBeaconBlock(bellatrixFullBlock) +} + +// WrapSignedBlindedBeaconBlock converts a signed beacon block into a blinded format. +func WrapSignedBlindedBeaconBlock(blk interfaces.SignedBeaconBlock) (interfaces.SignedBeaconBlock, error) { + if err := BeaconBlockIsNil(blk); err != nil { + return nil, err + } + if blk.Block().IsBlinded() { + return blk, nil + } + b := blk.Block() + payload, err := b.Body().ExecutionPayload() + switch { + case errors.Is(err, ErrUnsupportedField): + return nil, ErrUnsupportedSignedBeaconBlock + case err != nil: + return nil, errors.Wrap(err, "could not get execution payload") + default: + } + syncAgg, err := b.Body().SyncAggregate() + if err != nil { + return nil, err + } + header, err := bellatrix.PayloadToHeader(payload) + if err != nil { + return nil, err + } + blindedBlock := ð.SignedBlindedBeaconBlockBellatrix{ + Block: ð.BlindedBeaconBlockBellatrix{ + Slot: b.Slot(), + ProposerIndex: b.ProposerIndex(), + ParentRoot: b.ParentRoot(), + StateRoot: b.StateRoot(), + Body: ð.BlindedBeaconBlockBodyBellatrix{ + RandaoReveal: b.Body().RandaoReveal(), + Eth1Data: b.Body().Eth1Data(), + Graffiti: b.Body().Graffiti(), + ProposerSlashings: b.Body().ProposerSlashings(), + AttesterSlashings: b.Body().AttesterSlashings(), + Attestations: b.Body().Attestations(), + Deposits: b.Body().Deposits(), + VoluntaryExits: b.Body().VoluntaryExits(), + SyncAggregate: syncAgg, + ExecutionPayloadHeader: header, + }, + }, + Signature: blk.Signature(), + } + return wrappedBellatrixSignedBlindedBeaconBlock(blindedBlock) +} + func UnwrapGenericSignedBeaconBlock(gb *eth.GenericSignedBeaconBlock) (interfaces.SignedBeaconBlock, error) { if gb == nil { return nil, ErrNilObjectWrapped diff --git a/consensus-types/wrapper/beacon_block_test.go b/consensus-types/wrapper/beacon_block_test.go index 5719530570..245674477a 100644 --- a/consensus-types/wrapper/beacon_block_test.go +++ b/consensus-types/wrapper/beacon_block_test.go @@ -3,11 +3,121 @@ package wrapper_test import ( "testing" + "github.com/pkg/errors" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/consensus-types/forks/bellatrix" "github.com/prysmaticlabs/prysm/consensus-types/wrapper" + enginev1 "github.com/prysmaticlabs/prysm/proto/engine/v1" "github.com/prysmaticlabs/prysm/testing/require" "github.com/prysmaticlabs/prysm/testing/util" ) +func TestBuildSignedBeaconBlockFromExecutionPayload(t *testing.T) { + t.Run("nil block check", func(t *testing.T) { + _, err := wrapper.BuildSignedBeaconBlockFromExecutionPayload(nil, nil) + require.ErrorIs(t, wrapper.ErrNilSignedBeaconBlock, err) + }) + t.Run("unsupported field payload header", func(t *testing.T) { + altairBlock := util.NewBeaconBlockAltair() + blk, err := wrapper.WrappedSignedBeaconBlock(altairBlock) + require.NoError(t, err) + _, err = wrapper.BuildSignedBeaconBlockFromExecutionPayload(blk, nil) + require.Equal(t, true, errors.Is(err, wrapper.ErrUnsupportedField)) + }) + t.Run("payload header root and payload root mismatch", func(t *testing.T) { + payload := &enginev1.ExecutionPayload{ + ParentHash: make([]byte, fieldparams.RootLength), + FeeRecipient: make([]byte, 20), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, 256), + PrevRandao: make([]byte, fieldparams.RootLength), + BaseFeePerGas: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + Transactions: make([][]byte, 0), + } + header, err := bellatrix.PayloadToHeader(payload) + require.NoError(t, err) + blindedBlock := util.NewBlindedBeaconBlockBellatrix() + + // Modify the header. + header.GasUsed += 1 + blindedBlock.Block.Body.ExecutionPayloadHeader = header + + blk, err := wrapper.WrappedSignedBeaconBlock(blindedBlock) + require.NoError(t, err) + _, err = wrapper.BuildSignedBeaconBlockFromExecutionPayload(blk, payload) + require.ErrorContains(t, "roots do not match", err) + }) + t.Run("ok", func(t *testing.T) { + payload := &enginev1.ExecutionPayload{ + ParentHash: make([]byte, fieldparams.RootLength), + FeeRecipient: make([]byte, 20), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, 256), + PrevRandao: make([]byte, fieldparams.RootLength), + BaseFeePerGas: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + Transactions: make([][]byte, 0), + } + header, err := bellatrix.PayloadToHeader(payload) + require.NoError(t, err) + blindedBlock := util.NewBlindedBeaconBlockBellatrix() + blindedBlock.Block.Body.ExecutionPayloadHeader = header + + blk, err := wrapper.WrappedSignedBeaconBlock(blindedBlock) + require.NoError(t, err) + builtBlock, err := wrapper.BuildSignedBeaconBlockFromExecutionPayload(blk, payload) + require.NoError(t, err) + + got, err := builtBlock.Block().Body().ExecutionPayload() + require.NoError(t, err) + require.DeepEqual(t, payload, got) + }) +} + +func TestWrapSignedBlindedBeaconBlock(t *testing.T) { + t.Run("nil block check", func(t *testing.T) { + _, err := wrapper.BuildSignedBeaconBlockFromExecutionPayload(nil, nil) + require.ErrorIs(t, wrapper.ErrNilSignedBeaconBlock, err) + }) + t.Run("unsupported field execution payload", func(t *testing.T) { + altairBlock := util.NewBeaconBlockAltair() + blk, err := wrapper.WrappedSignedBeaconBlock(altairBlock) + require.NoError(t, err) + _, err = wrapper.BuildSignedBeaconBlockFromExecutionPayload(blk, nil) + require.Equal(t, true, errors.Is(err, wrapper.ErrUnsupportedField)) + }) + t.Run("ok", func(t *testing.T) { + payload := &enginev1.ExecutionPayload{ + ParentHash: make([]byte, fieldparams.RootLength), + FeeRecipient: make([]byte, 20), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, 256), + PrevRandao: make([]byte, fieldparams.RootLength), + BaseFeePerGas: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + Transactions: make([][]byte, 0), + } + bellatrixBlk := util.NewBeaconBlockBellatrix() + bellatrixBlk.Block.Body.ExecutionPayload = payload + + want, err := bellatrix.PayloadToHeader(payload) + require.NoError(t, err) + + blk, err := wrapper.WrappedSignedBeaconBlock(bellatrixBlk) + require.NoError(t, err) + builtBlock, err := wrapper.WrapSignedBlindedBeaconBlock(blk) + require.NoError(t, err) + + got, err := builtBlock.Block().Body().ExecutionPayloadHeader() + require.NoError(t, err) + require.DeepEqual(t, want, got) + }) +} + func TestWrappedSignedBeaconBlock(t *testing.T) { tests := []struct { name string