mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 13:28:01 -05:00
Add is_merge_block helper (#10026)
* Add merge complete helper * Update BUILD.bazel * minimal * Add is_merge_block helper * rename to `IsMergeBlock`
This commit is contained in:
@@ -11,6 +11,7 @@ go_library(
|
||||
"genesis.go",
|
||||
"header.go",
|
||||
"log.go",
|
||||
"payload.go",
|
||||
"proposer_slashing.go",
|
||||
"randao.go",
|
||||
"signature.go",
|
||||
@@ -67,6 +68,7 @@ go_test(
|
||||
"exit_test.go",
|
||||
"genesis_test.go",
|
||||
"header_test.go",
|
||||
"payload_test.go",
|
||||
"proposer_slashing_regression_test.go",
|
||||
"proposer_slashing_test.go",
|
||||
"randao_test.go",
|
||||
|
||||
140
beacon-chain/core/blocks/payload.go
Normal file
140
beacon-chain/core/blocks/payload.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package blocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
)
|
||||
|
||||
// MergeComplete returns true if the transition to merge has completed.
|
||||
// Meaning the header header in beacon state is not `ExecutionPayloadHeader()` (i.e. not empty).
|
||||
//
|
||||
// Spec code:
|
||||
// def is_merge_complete(state: BeaconState) -> bool:
|
||||
// return state.latest_execution_payload_header != ExecutionPayloadHeader()
|
||||
func MergeComplete(st state.BeaconState) (bool, error) {
|
||||
h, err := st.LatestExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return !isEmptyHeader(h), nil
|
||||
}
|
||||
|
||||
// IsMergeBlock returns true if the input block is the terminal merge block.
|
||||
// Meaning the header header in beacon state is `ExecutionPayloadHeader()` (i.e. empty).
|
||||
// And the input block has a non-empty header.
|
||||
//
|
||||
// Spec code:
|
||||
// def is_merge_block(state: BeaconState, body: BeaconBlockBody) -> bool:
|
||||
// return not is_merge_complete(state) and body.execution_payload != ExecutionPayload()
|
||||
func IsMergeBlock(st state.BeaconState, blk block.BeaconBlockBody) (bool, error) {
|
||||
mergeComplete, err := MergeComplete(st)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if mergeComplete {
|
||||
return false, err
|
||||
}
|
||||
|
||||
payload, err := blk.ExecutionPayload()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !isEmptyPayload(payload), nil
|
||||
}
|
||||
|
||||
func isEmptyPayload(p *ethpb.ExecutionPayload) bool {
|
||||
if !bytes.Equal(p.ParentHash, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(p.FeeRecipient, make([]byte, fieldparams.FeeRecipientLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(p.StateRoot, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(p.ReceiptRoot, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(p.LogsBloom, make([]byte, fieldparams.LogsBloomLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(p.Random, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(p.BaseFeePerGas, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(p.BlockHash, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if p.Transactions != nil {
|
||||
return false
|
||||
}
|
||||
if p.ExtraData != nil {
|
||||
return false
|
||||
}
|
||||
if p.BlockNumber != 0 {
|
||||
return false
|
||||
}
|
||||
if p.GasLimit != 0 {
|
||||
return false
|
||||
}
|
||||
if p.GasUsed != 0 {
|
||||
return false
|
||||
}
|
||||
if p.Timestamp != 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isEmptyHeader(h *ethpb.ExecutionPayloadHeader) bool {
|
||||
if !bytes.Equal(h.ParentHash, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(h.FeeRecipient, make([]byte, fieldparams.FeeRecipientLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(h.StateRoot, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(h.ReceiptRoot, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(h.LogsBloom, make([]byte, fieldparams.LogsBloomLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(h.Random, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(h.BaseFeePerGas, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(h.BlockHash, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(h.TransactionsRoot, make([]byte, fieldparams.RootLength)) {
|
||||
return false
|
||||
}
|
||||
if h.ExtraData != nil {
|
||||
return false
|
||||
}
|
||||
if h.BlockNumber != 0 {
|
||||
return false
|
||||
}
|
||||
if h.GasLimit != 0 {
|
||||
return false
|
||||
}
|
||||
if h.GasUsed != 0 {
|
||||
return false
|
||||
}
|
||||
if h.Timestamp != 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
383
beacon-chain/core/blocks/payload_test.go
Normal file
383
beacon-chain/core/blocks/payload_test.go
Normal file
@@ -0,0 +1,383 @@
|
||||
package blocks_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
)
|
||||
|
||||
func Test_MergeComplete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload *ethpb.ExecutionPayloadHeader
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty header header",
|
||||
payload: emptyPayloadHeader(),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "has parent hash",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.ParentHash = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has fee recipient",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.FeeRecipient = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has state root",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.StateRoot = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has receipt root",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.ReceiptRoot = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has logs bloom",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.LogsBloom = bytesutil.PadTo([]byte{'a'}, fieldparams.LogsBloomLength)
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has random",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.Random = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has base fee",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.BaseFeePerGas = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has block hash",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.BlockHash = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has tx root",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.TransactionsRoot = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has extra data",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.ExtraData = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has block number",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.BlockNumber = 1
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has gas limit",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.GasLimit = 1
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has gas used",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.GasUsed = 1
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has time stamp",
|
||||
payload: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.Timestamp = 1
|
||||
return h
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateMerge(t, 1)
|
||||
require.NoError(t, st.SetLatestExecutionPayloadHeader(tt.payload))
|
||||
got, err := blocks.MergeComplete(st)
|
||||
require.NoError(t, err)
|
||||
if got != tt.want {
|
||||
t.Errorf("mergeComplete() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MergeBlock(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload *ethpb.ExecutionPayload
|
||||
header *ethpb.ExecutionPayloadHeader
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty header, empty payload",
|
||||
payload: emptyPayload(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "non-empty header, empty payload",
|
||||
payload: emptyPayload(),
|
||||
header: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.ParentHash = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return h
|
||||
}(),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has parent hash",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.ParentHash = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has fee recipient",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.FeeRecipient = bytesutil.PadTo([]byte{'a'}, fieldparams.FeeRecipientLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has state root",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.StateRoot = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has receipt root",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.ReceiptRoot = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has logs bloom",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.LogsBloom = bytesutil.PadTo([]byte{'a'}, fieldparams.LogsBloomLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has random",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.Random = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has base fee",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.BaseFeePerGas = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has block hash",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.BlockHash = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has tx",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.Transactions = [][]byte{{'a'}}
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has extra data",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.ExtraData = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has block number",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.BlockNumber = 1
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has gas limit",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.GasLimit = 1
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has gas used",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.GasUsed = 1
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has timestamp",
|
||||
payload: func() *ethpb.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.Timestamp = 1
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateMerge(t, 1)
|
||||
require.NoError(t, st.SetLatestExecutionPayloadHeader(tt.header))
|
||||
blk := util.NewBeaconBlockMerge()
|
||||
blk.Block.Body.ExecutionPayload = tt.payload
|
||||
body, err := wrapper.WrappedMergeBeaconBlockBody(blk.Block.Body)
|
||||
require.NoError(t, err)
|
||||
got, err := blocks.IsMergeBlock(st, body)
|
||||
require.NoError(t, err)
|
||||
if got != tt.want {
|
||||
t.Errorf("IsMergeBlock() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMergeComplete(b *testing.B) {
|
||||
st, _ := util.DeterministicGenesisStateMerge(b, 1)
|
||||
require.NoError(b, st.SetLatestExecutionPayloadHeader(emptyPayloadHeader()))
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := blocks.MergeComplete(st)
|
||||
require.NoError(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
func emptyPayloadHeader() *ethpb.ExecutionPayloadHeader {
|
||||
return ðpb.ExecutionPayloadHeader{
|
||||
ParentHash: make([]byte, fieldparams.RootLength),
|
||||
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
||||
StateRoot: make([]byte, fieldparams.RootLength),
|
||||
ReceiptRoot: make([]byte, fieldparams.RootLength),
|
||||
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
|
||||
Random: make([]byte, fieldparams.RootLength),
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
TransactionsRoot: make([]byte, fieldparams.RootLength),
|
||||
}
|
||||
}
|
||||
|
||||
func emptyPayload() *ethpb.ExecutionPayload {
|
||||
return ðpb.ExecutionPayload{
|
||||
ParentHash: make([]byte, fieldparams.RootLength),
|
||||
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
||||
StateRoot: make([]byte, fieldparams.RootLength),
|
||||
ReceiptRoot: make([]byte, fieldparams.RootLength),
|
||||
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
|
||||
Random: make([]byte, fieldparams.RootLength),
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,7 @@ type WriteOnlyBeaconState interface {
|
||||
SetSlashings(val []uint64) error
|
||||
UpdateSlashingsAtIndex(idx, val uint64) error
|
||||
AppendHistoricalRoots(root [32]byte) error
|
||||
SetLatestExecutionPayloadHeader(payload *ethpb.ExecutionPayloadHeader) error
|
||||
}
|
||||
|
||||
// ReadOnlyValidator defines a struct which only has read access to validator methods.
|
||||
|
||||
@@ -15,4 +15,6 @@ const (
|
||||
SyncCommitteeLength = 512 // SYNC_COMMITTEE_SIZE
|
||||
RootLength = 32 // RootLength defines the byte length of a Merkle root.
|
||||
BLSSignatureLength = 96 // BLSSignatureLength defines the byte length of a BLSSignature.
|
||||
FeeRecipientLength = 20 // FeeRecipientLength defines the byte length of a fee recipient.
|
||||
LogsBloomLength = 256 // LogsBloomLength defines the byte length of a logs bloom.
|
||||
)
|
||||
|
||||
@@ -15,4 +15,6 @@ const (
|
||||
SyncCommitteeLength = 32 // SYNC_COMMITTEE_SIZE
|
||||
RootLength = 32 // RootLength defines the byte length of a Merkle root.
|
||||
BLSSignatureLength = 96 // BLSSignatureLength defines the byte length of a BLSSignature.
|
||||
FeeRecipientLength = 20 // FeeRecipientLength defines the byte length of a fee recipient.
|
||||
LogsBloomLength = 256 // LogsBloomLength defines the byte length of a logs bloom.
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user