diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index ffbd8b0abe..b93864a9fd 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -377,12 +377,12 @@ func (eng *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *types if extDataGasUsed == nil { extDataGasUsed = new(big.Int).Set(common.Big0) } - if blockExtDataGasUsed := block.ExtDataGasUsed(); blockExtDataGasUsed == nil || !blockExtDataGasUsed.IsUint64() || blockExtDataGasUsed.Cmp(extDataGasUsed) != 0 { + if blockExtDataGasUsed := types.BlockExtDataGasUsed(block); blockExtDataGasUsed == nil || !blockExtDataGasUsed.IsUint64() || blockExtDataGasUsed.Cmp(extDataGasUsed) != 0 { return fmt.Errorf("invalid extDataGasUsed: have %d, want %d", blockExtDataGasUsed, extDataGasUsed) } // Verify the BlockGasCost set in the header matches the expected value. - blockGasCost := block.BlockGasCost() + blockGasCost := types.BlockGasCost(block) expectedBlockGasCost := customheader.BlockGasCost( configExtra, parent, diff --git a/core/blockchain.go b/core/blockchain.go index 6b2b7d4cf5..81806728a5 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1367,7 +1367,7 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { "parentHash", block.ParentHash(), "uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(), "elapsed", common.PrettyDuration(time.Since(start)), - "root", block.Root(), "baseFeePerGas", block.BaseFee(), "blockGasCost", block.BlockGasCost(), + "root", block.Root(), "baseFeePerGas", block.BaseFee(), "blockGasCost", types.BlockGasCost(block), ) processedBlockGasUsedCounter.Inc(int64(block.GasUsed())) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index dba05750a7..21eb1ff843 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -522,7 +522,7 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block { if body == nil { return nil } - return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles).WithExtData(body.Version, body.ExtData) + return types.NewBlockWithHeader(header).WithBody(*body) } // WriteBlock serializes a block into the database, header and body separately. diff --git a/core/types/block.go b/core/types/block.go deleted file mode 100644 index 01c723c475..0000000000 --- a/core/types/block.go +++ /dev/null @@ -1,301 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package types contains data types related to Ethereum consensus. -package types - -import ( - "encoding/binary" - "io" - "math/big" - "sync/atomic" - - "github.com/ava-labs/libevm/common" - "github.com/ava-labs/libevm/rlp" -) - -// Body is a simple (mutable, non-safe) data container for storing and moving -// a block's data contents (transactions and uncles) together. -type Body struct { - Transactions []*Transaction - Uncles []*Header - Version uint32 - ExtData *[]byte `rlp:"nil"` -} - -// Block represents an Ethereum block. -// -// Note the Block type tries to be 'immutable', and contains certain caches that rely -// on that. The rules around block immutability are as follows: -// -// - We copy all data when the block is constructed. This makes references held inside -// the block independent of whatever value was passed in. -// -// - We copy all header data on access. This is because any change to the header would mess -// up the cached hash and size values in the block. Calling code is expected to take -// advantage of this to avoid over-allocating! -// -// - When new body data is attached to the block, a shallow copy of the block is returned. -// This ensures block modifications are race-free. -// -// - We do not copy body data on access because it does not affect the caches, and also -// because it would be too expensive. -type Block struct { - header *Header - uncles []*Header - transactions Transactions - - // Coreth specific data structures to support atomic transactions - version uint32 - extdata *[]byte - - // caches - hash atomic.Value - size atomic.Value -} - -// "external" block encoding. used for eth protocol, etc. -type extblock struct { - Header *Header - Txs []*Transaction - Uncles []*Header - Version uint32 - ExtData *[]byte `rlp:"nil"` -} - -// NewBlock creates a new block. The input data is copied, changes to header and to the -// field values will not affect the block. -// -// The values of TxHash, UncleHash, ReceiptHash and Bloom in header -// are ignored and set to values derived from the given txs, uncles -// and receipts. -func NewBlock( - header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher, -) *Block { - b := &Block{header: CopyHeader(header)} - - // TODO: panic if len(txs) != len(receipts) - if len(txs) == 0 { - b.header.TxHash = EmptyTxsHash - } else { - b.header.TxHash = DeriveSha(Transactions(txs), hasher) - b.transactions = make(Transactions, len(txs)) - copy(b.transactions, txs) - } - - if len(receipts) == 0 { - b.header.ReceiptHash = EmptyReceiptsHash - } else { - b.header.ReceiptHash = DeriveSha(Receipts(receipts), hasher) - b.header.Bloom = CreateBloom(receipts) - } - - if len(uncles) == 0 { - b.header.UncleHash = EmptyUncleHash - } else { - b.header.UncleHash = CalcUncleHash(uncles) - b.uncles = make([]*Header, len(uncles)) - for i := range uncles { - b.uncles[i] = CopyHeader(uncles[i]) - } - } - - return b -} - -// DecodeRLP decodes a block from RLP. -func (b *Block) DecodeRLP(s *rlp.Stream) error { - var eb extblock - _, size, _ := s.Kind() - if err := s.Decode(&eb); err != nil { - return err - } - b.header, b.uncles, b.transactions, b.version, b.extdata = eb.Header, eb.Uncles, eb.Txs, eb.Version, eb.ExtData - b.size.Store(rlp.ListSize(size)) - return nil -} - -// EncodeRLP serializes a block as RLP. -func (b *Block) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, &extblock{ - Header: b.header, - Txs: b.transactions, - Uncles: b.uncles, - Version: b.version, - ExtData: b.extdata, - }) -} - -// Body returns the non-header content of the block. -// Note the returned data is not an independent copy. -func (b *Block) Body() *Body { - return &Body{b.transactions, b.uncles, b.version, b.extdata} -} - -// Accessors for body data. These do not return a copy because the content -// of the body slices does not affect the cached hash/size in block. - -func (b *Block) Uncles() []*Header { return b.uncles } -func (b *Block) Transactions() Transactions { return b.transactions } - -func (b *Block) Transaction(hash common.Hash) *Transaction { - for _, transaction := range b.transactions { - if transaction.Hash() == hash { - return transaction - } - } - return nil -} - -// Header returns the block header (as a copy). -func (b *Block) Header() *Header { - return CopyHeader(b.header) -} - -// Header value accessors. These do copy! - -func (b *Block) Number() *big.Int { return new(big.Int).Set(b.header.Number) } -func (b *Block) GasLimit() uint64 { return b.header.GasLimit } -func (b *Block) GasUsed() uint64 { return b.header.GasUsed } -func (b *Block) Difficulty() *big.Int { return new(big.Int).Set(b.header.Difficulty) } -func (b *Block) Time() uint64 { return b.header.Time } - -func (b *Block) NumberU64() uint64 { return b.header.Number.Uint64() } -func (b *Block) MixDigest() common.Hash { return b.header.MixDigest } -func (b *Block) Nonce() uint64 { return binary.BigEndian.Uint64(b.header.Nonce[:]) } -func (b *Block) Bloom() Bloom { return b.header.Bloom } -func (b *Block) Coinbase() common.Address { return b.header.Coinbase } -func (b *Block) Root() common.Hash { return b.header.Root } -func (b *Block) ParentHash() common.Hash { return b.header.ParentHash } -func (b *Block) TxHash() common.Hash { return b.header.TxHash } -func (b *Block) ReceiptHash() common.Hash { return b.header.ReceiptHash } -func (b *Block) UncleHash() common.Hash { return b.header.UncleHash } -func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Extra) } - -func (b *Block) BaseFee() *big.Int { - if b.header.BaseFee == nil { - return nil - } - return new(big.Int).Set(b.header.BaseFee) -} - -func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot } - -func (b *Block) ExcessBlobGas() *uint64 { - var excessBlobGas *uint64 - if b.header.ExcessBlobGas != nil { - excessBlobGas = new(uint64) - *excessBlobGas = *b.header.ExcessBlobGas - } - return excessBlobGas -} - -func (b *Block) BlobGasUsed() *uint64 { - var blobGasUsed *uint64 - if b.header.BlobGasUsed != nil { - blobGasUsed = new(uint64) - *blobGasUsed = *b.header.BlobGasUsed - } - return blobGasUsed -} - -func (b *Block) BlockGasCost() *big.Int { - cost := GetHeaderExtra(b.header).BlockGasCost - if cost == nil { - return nil - } - return new(big.Int).Set(cost) -} - -// Size returns the true RLP encoded storage size of the block, either by encoding -// and returning it, or returning a previously cached value. -func (b *Block) Size() uint64 { - if size := b.size.Load(); size != nil { - return size.(uint64) - } - c := writeCounter(0) - rlp.Encode(&c, b) - b.size.Store(uint64(c)) - return uint64(c) -} - -type writeCounter uint64 - -func (c *writeCounter) Write(b []byte) (int, error) { - *c += writeCounter(len(b)) - return len(b), nil -} - -func CalcUncleHash(uncles []*Header) common.Hash { - if len(uncles) == 0 { - return EmptyUncleHash - } - return rlpHash(uncles) -} - -// NewBlockWithHeader creates a block with the given header data. The -// header data is copied, changes to header and to the field values -// will not affect the block. -func NewBlockWithHeader(header *Header) *Block { - return &Block{header: CopyHeader(header)} -} - -// WithSeal returns a new block with the data from b but the header replaced with -// the sealed one. -func (b *Block) WithSeal(header *Header) *Block { - return &Block{ - header: CopyHeader(header), - transactions: b.transactions, - uncles: b.uncles, - } -} - -// WithBody returns a copy of the block with the given transaction and uncle contents. -func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block { - block := &Block{ - header: b.header, - transactions: make([]*Transaction, len(transactions)), - uncles: make([]*Header, len(uncles)), - } - copy(block.transactions, transactions) - for i := range uncles { - block.uncles[i] = CopyHeader(uncles[i]) - } - return block -} - -// Hash returns the keccak256 hash of b's header. -// The hash is computed on the first call and cached thereafter. -func (b *Block) Hash() common.Hash { - if hash := b.hash.Load(); hash != nil { - return hash.(common.Hash) - } - v := b.header.Hash() - b.hash.Store(v) - return v -} - -type Blocks []*Block diff --git a/core/types/block_ext.go b/core/types/block_ext.go index 2cfb4b2888..bf2b1ecd90 100644 --- a/core/types/block_ext.go +++ b/core/types/block_ext.go @@ -5,52 +5,129 @@ package types import ( "math/big" + "slices" "github.com/ava-labs/libevm/common" + ethtypes "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/rlp" ) -func (b *Block) WithExtData(version uint32, extdata *[]byte) *Block { - b.version = version - b.setExtDataHelper(extdata, false) - return b +// SetBlockExtra sets the [BlockBodyExtra] `extra` in the [Block] `b`. +func SetBlockExtra(b *Block, extra *BlockBodyExtra) { + extras.Block.Set(b, extra) } -func (b *Block) setExtDataHelper(data *[]byte, recalc bool) { - if data == nil { - b.setExtData(nil, recalc) - return +// BlockBodyExtra is a struct containing extra fields used by Avalanche +// in the [Block] and [Body]. +type BlockBodyExtra struct { + Version uint32 + ExtData *[]byte +} + +// Copy deep copies the [BlockBodyExtra] `b` and returns it. +// It is notably used in the following functions: +// - [ethtypes.Block.Body] +// - [ethtypes.Block.WithSeal] +// - [ethtypes.Block.WithBody] +// - [ethtypes.Block.WithWithdrawals] +func (b *BlockBodyExtra) Copy() *BlockBodyExtra { + cpy := *b + if b.ExtData != nil { + data := slices.Clone(*b.ExtData) + cpy.ExtData = &data } - b.setExtData(*data, recalc) + return &cpy } -func (b *Block) setExtData(data []byte, recalc bool) { - _data := make([]byte, len(data)) - b.extdata = &_data - copy(*b.extdata, data) - if recalc { - GetHeaderExtra(b.header).ExtDataHash = CalcExtDataHash(*b.extdata) +// BodyRLPFieldPointersForEncoding returns the fields that should be encoded +// for the [Body] and [BlockBodyExtra]. +// Note the following fields are added (+) and removed (-) compared to geth: +// - (-) [ethtypes.Body] `Withdrawals` field +// - (+) [BlockBodyExtra] `Version` field +// - (+) [BlockBodyExtra] `ExtData` field +func (b *BlockBodyExtra) BodyRLPFieldsForEncoding(body *Body) *rlp.Fields { + return &rlp.Fields{ + Required: []any{ + body.Transactions, + body.Uncles, + b.Version, + b.ExtData, + }, } } -func (b *Block) ExtData() []byte { - if b.extdata == nil { - return nil +// BodyRLPFieldPointersForDecoding returns the fields that should be decoded to +// for the [Body] and [BlockBodyExtra]. +func (b *BlockBodyExtra) BodyRLPFieldPointersForDecoding(body *Body) *rlp.Fields { + return &rlp.Fields{ + Required: []any{ + &body.Transactions, + &body.Uncles, + &b.Version, + &b.ExtData, + }, + } +} + +// BlockRLPFieldPointersForEncoding returns the fields that should be encoded +// for the [Block] and [BlockBodyExtra]. +// Note the following fields are added (+) and removed (-) compared to geth: +// - (-) [ethtypes.Block] `Withdrawals` field +// - (+) [BlockBodyExtra] `Version` field +// - (+) [BlockBodyExtra] `ExtData` field +func (b *BlockBodyExtra) BlockRLPFieldsForEncoding(block *ethtypes.BlockRLPProxy) *rlp.Fields { + return &rlp.Fields{ + Required: []any{ + block.Header, + block.Txs, + block.Uncles, + b.Version, + b.ExtData, + }, + } +} + +// BlockRLPFieldPointersForDecoding returns the fields that should be decoded to +// for the [Block] and [BlockBodyExtra]. +func (b *BlockBodyExtra) BlockRLPFieldPointersForDecoding(block *ethtypes.BlockRLPProxy) *rlp.Fields { + return &rlp.Fields{ + Required: []any{ + &block.Header, + &block.Txs, + &block.Uncles, + &b.Version, + &b.ExtData, + }, } - return *b.extdata } -func (b *Block) Version() uint32 { - return b.version +func BlockExtData(b *Block) []byte { + if data := extras.Block.Get(b).ExtData; data != nil { + return *data + } + return nil +} + +func BlockVersion(b *Block) uint32 { + return extras.Block.Get(b).Version } -func (b *Block) ExtDataGasUsed() *big.Int { - used := GetHeaderExtra(b.header).ExtDataGasUsed +func BlockExtDataGasUsed(b *Block) *big.Int { + used := GetHeaderExtra(b.Header()).ExtDataGasUsed if used == nil { return nil } return new(big.Int).Set(used) } +func BlockGasCost(b *Block) *big.Int { + cost := GetHeaderExtra(b.Header()).BlockGasCost + if cost == nil { + return nil + } + return new(big.Int).Set(cost) +} + func CalcExtDataHash(extdata []byte) common.Hash { if len(extdata) == 0 { return EmptyExtDataHash @@ -62,7 +139,16 @@ func NewBlockWithExtData( header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher, extdata []byte, recalc bool, ) *Block { - b := NewBlock(header, txs, uncles, receipts, hasher) - b.setExtData(extdata, recalc) - return b + if recalc { + headerExtra := GetHeaderExtra(header) + headerExtra.ExtDataHash = CalcExtDataHash(extdata) + } + block := NewBlock(header, txs, uncles, receipts, hasher) + extdataCopy := make([]byte, len(extdata)) + copy(extdataCopy, extdata) + extra := &BlockBodyExtra{ + ExtData: &extdataCopy, + } + extras.Block.Set(block, extra) + return block } diff --git a/core/types/block_ext_test.go b/core/types/block_ext_test.go index df7d988e0c..49c68d8af8 100644 --- a/core/types/block_ext_test.go +++ b/core/types/block_ext_test.go @@ -4,13 +4,19 @@ package types import ( + "encoding/hex" "math/big" "reflect" "testing" "unsafe" "github.com/ava-labs/libevm/common" + ethtypes "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/rlp" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestCopyHeader(t *testing.T) { @@ -55,7 +61,7 @@ func TestCopyHeader(t *testing.T) { } func exportedFieldsPointToDifferentMemory[T interface { - Header | HeaderExtra + Header | HeaderExtra | BlockBodyExtra }](t *testing.T, original, cpy *T) { t.Helper() @@ -68,7 +74,7 @@ func exportedFieldsPointToDifferentMemory[T interface { continue } switch field.Type.Kind() { - case reflect.Array, reflect.Uint64: + case reflect.Array, reflect.Uint64, reflect.Uint32: // Not pointers, but using explicit Kinds for safety continue } @@ -82,6 +88,8 @@ func exportedFieldsPointToDifferentMemory[T interface { assertDifferentPointers(t, f, fieldCp) case *uint64: assertDifferentPointers(t, f, fieldCp) + case *[]uint8: + assertDifferentPointers(t, f, fieldCp) case []uint8: assertDifferentPointers(t, unsafe.SliceData(f), unsafe.SliceData(fieldCp.([]uint8))) default: @@ -106,3 +114,375 @@ func assertDifferentPointers[T any](t *testing.T, a *T, b any) { // Note: no need to check `b` is of the same type as `a`, otherwise // the memory address would be different as well. } + +// blockWithNonZeroFields returns a [Block] and a [BlockBodyExtra], +// each with all fields set to non-zero values. +// The [BlockBodyExtra] extra payload is set in the [Block] via `extras.Block.Set`. +// +// NOTE: They can be used to demonstrate that RLP round-trip encoding +// can recover all fields, but not that the encoded format is correct. This is +// very important as the RLP encoding of a [Block] defines its hash. +func blockWithNonZeroFields() (*Block, *BlockBodyExtra) { + header := &Header{ + ParentHash: common.Hash{1}, + } + headerExtra := &HeaderExtra{ + ExtDataHash: common.Hash{2}, + } + SetHeaderExtra(header, headerExtra) + + tx := NewTransaction(1, common.Address{2}, big.NewInt(3), 4, big.NewInt(5), []byte{6}) + txs := []*Transaction{tx} + + uncle := &Header{ + Difficulty: big.NewInt(7), + Number: big.NewInt(8), + ParentHash: common.Hash{9}, + } + uncleExtra := &HeaderExtra{ExtDataHash: common.Hash{10}} + SetHeaderExtra(uncle, uncleExtra) + uncles := []*Header{uncle} + + receipts := []*Receipt{{PostState: []byte{11}}} + + block := NewBlock(header, txs, uncles, receipts, stubHasher{}) + withdrawals := []*ethtypes.Withdrawal{{Index: 12}} + block = block.WithWithdrawals(withdrawals) + extra := &BlockBodyExtra{ + Version: 13, + ExtData: &[]byte{14}, + } + SetBlockExtra(block, extra) + return block, extra +} + +func TestBlockWithNonZeroFields(t *testing.T) { + t.Parallel() + + block, extra := blockWithNonZeroFields() + t.Run("Block", func(t *testing.T) { + ignoreFields := []string{"extra", "hash", "size", "ReceivedAt", "ReceivedFrom"} + allFieldsSet(t, block, ignoreFields...) + }) + t.Run("BlockExtra", func(t *testing.T) { allFieldsSet(t, extra) }) +} + +// bodyWithNonZeroFields returns a [Body] and a [BlockBodyExtra], +// each with all fields set to non-zero values. +// The [BlockBodyExtra] extra payload is set in the [Body] via `extras.Block.Set` +// and the extra copying done in [ethtypes.Block.Body]. +// +// NOTE: They can be used to demonstrate that RLP round-trip encoding +// can recover all fields, but not that the encoded format is correct. This is +// very important as the RLP encoding of a [Body] defines its hash. +func bodyWithNonZeroFields() (*Body, *BlockBodyExtra) { + block, extra := blockWithNonZeroFields() + return block.Body(), extra +} + +func TestBodyWithNonZeroFields(t *testing.T) { + t.Parallel() + + body, extra := bodyWithNonZeroFields() + t.Run("Body", func(t *testing.T) { + ignoredFields := []string{"extra"} + allFieldsSet(t, body, ignoredFields...) + }) + t.Run("BodyExtra", func(t *testing.T) { allFieldsSet(t, extra) }) +} + +func txHashComparer() cmp.Option { + return cmp.Comparer(func(a, b *Transaction) bool { + return a.Hash() == b.Hash() + }) +} + +func headerHashComparer() cmp.Option { + return cmp.Comparer(func(a, b *Header) bool { + return a.Hash() == b.Hash() + }) +} + +func TestBodyExtraRLP(t *testing.T) { + t.Parallel() + + body, _ := bodyWithNonZeroFields() // the body carries the [BlockBodyExtra] so we can ignore it + + encoded, err := rlp.EncodeToBytes(body) + require.NoError(t, err) + + gotBody := new(Body) + err = rlp.DecodeBytes(encoded, gotBody) + require.NoError(t, err) + + wantBody, wantExtra := bodyWithNonZeroFields() + wantBody.Withdrawals = nil + + opts := cmp.Options{ + txHashComparer(), + headerHashComparer(), + cmpopts.IgnoreUnexported(Body{}), + } + if diff := cmp.Diff(wantBody, gotBody, opts); diff != "" { + t.Errorf("%T diff after RLP round-trip (-want +got):\n%s", wantBody, diff) + } + + gotExtra := extras.Body.Get(gotBody) + if diff := cmp.Diff(wantExtra, gotExtra); diff != "" { + t.Errorf("%T diff after RLP round-trip of %T (-want +got):\n%s", wantExtra, wantBody, diff) + } + + // Golden data from original coreth implementation, before integration of + // libevm. WARNING: changing these values can break backwards compatibility + // with extreme consequences. + const wantHex = "f90235dedd0105049402000000000000000000000000000000000000000306808080f90211f9020ea00900000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070880808080a00000000000000000000000000000000000000000000000000000000000000000880000000000000000a00a000000000000000000000000000000000000000000000000000000000000000d0e" + + assert.Equal(t, wantHex, hex.EncodeToString(encoded), "golden data") +} + +func TestBlockExtraRLP(t *testing.T) { + t.Parallel() + + block, _ := blockWithNonZeroFields() // the block carries the [BlockBodyExtra] so we can ignore it + + encoded, err := rlp.EncodeToBytes(block) + require.NoError(t, err) + + gotBlock := new(Block) + err = rlp.DecodeBytes(encoded, gotBlock) + require.NoError(t, err) + + wantBlock, wantExtra := blockWithNonZeroFields() + wantBlock = wantBlock.WithWithdrawals(nil) // withdrawals are not encoded + + opts := cmp.Options{ + txHashComparer(), + headerHashComparer(), + cmpopts.IgnoreUnexported(Block{}), + } + if diff := cmp.Diff(wantBlock, gotBlock, opts); diff != "" { + t.Errorf("%T diff after RLP round-trip (-want +got):\n%s", gotBlock, diff) + } + + gotExtra := extras.Block.Get(gotBlock) + if diff := cmp.Diff(wantExtra, gotExtra); diff != "" { + t.Errorf("%T diff after RLP round-trip of %T (-want +got):\n%s", wantExtra, wantBlock, diff) + } + + // Golden data from original coreth implementation, before integration of + // libevm. WARNING: changing these values can break backwards compatibility + // with extreme consequences. + const wantHex = "f90446f9020ea00100000000000000000000000000000000000000000000000000000000000000a008539331084089cedbaf7771d0f5f69847f246e0676e4d96091a49c53c89360b940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808080808080a00000000000000000000000000000000000000000000000000000000000000000880000000000000000a00200000000000000000000000000000000000000000000000000000000000000dedd0105049402000000000000000000000000000000000000000306808080f90211f9020ea00900000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070880808080a00000000000000000000000000000000000000000000000000000000000000000880000000000000000a00a000000000000000000000000000000000000000000000000000000000000000d0e" + + assert.Equal(t, wantHex, hex.EncodeToString(encoded), "golden data") +} + +// TestBlockBody tests the [BlockBodyExtra.Copy] method is implemented correctly. +func TestBlockBody(t *testing.T) { + t.Parallel() + + const version = 1 + extData := &[]byte{2} + + blockExtras := &BlockBodyExtra{ + Version: version, + ExtData: extData, + } + allFieldsSet(t, blockExtras) // make sure each field is checked + block := NewBlock(&Header{}, nil, nil, nil, stubHasher{}) + extras.Block.Set(block, blockExtras) + + wantExtra := &BlockBodyExtra{ + Version: version, + ExtData: extData, + } + gotExtra := extras.Body.Get(block.Body()) // [types.Block.Body] invokes [BlockBodyExtra.Copy] + assert.Equal(t, wantExtra, gotExtra) + + exportedFieldsPointToDifferentMemory(t, blockExtras, gotExtra) +} + +func TestBlockGetters(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + headerExtra *HeaderExtra + blockExtra *BlockBodyExtra + wantExtDataGasUsed *big.Int + wantBlockGasCost *big.Int + wantVersion uint32 + wantExtData []byte + }{ + { + name: "empty", + headerExtra: &HeaderExtra{}, + blockExtra: &BlockBodyExtra{}, + }, + { + name: "fields_set", + headerExtra: &HeaderExtra{ + ExtDataGasUsed: big.NewInt(1), + BlockGasCost: big.NewInt(2), + }, + blockExtra: &BlockBodyExtra{ + Version: 3, + ExtData: &[]byte{4}, + }, + wantExtDataGasUsed: big.NewInt(1), + wantBlockGasCost: big.NewInt(2), + wantVersion: 3, + wantExtData: []byte{4}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + header := &Header{} + SetHeaderExtra(header, test.headerExtra) + + block := NewBlock(header, nil, nil, nil, stubHasher{}) + extras.Block.Set(block, test.blockExtra) + + extData := BlockExtData(block) + assert.Equal(t, test.wantExtData, extData, "BlockExtData()") + + version := BlockVersion(block) + assert.Equal(t, test.wantVersion, version, "BlockVersion()") + + extDataGasUsed := BlockExtDataGasUsed(block) + assert.Equal(t, test.wantExtDataGasUsed, extDataGasUsed, "BlockExtDataGasUsed()") + + blockGasCost := BlockGasCost(block) + assert.Equal(t, test.wantBlockGasCost, blockGasCost, "BlockGasCost()") + }) + } +} + +func TestNewBlockWithExtData(t *testing.T) { + t.Parallel() + + // This transaction is generated beforehand because of its unexported time field being set + // on creation. + testTx := NewTransaction(0, common.Address{1}, big.NewInt(2), 3, big.NewInt(4), []byte{5}) + + tests := []struct { + name string + header func() *Header + txs []*Transaction + uncles func() []*Header + receipts []*Receipt + extdata []byte + recalc bool + wantBlock func() *Block + }{ + { + name: "empty", + header: func() *Header { + header := &Header{} + SetHeaderExtra(header, &HeaderExtra{}) + return header + }, + wantBlock: func() *Block { + header := &Header{} + SetHeaderExtra(header, &HeaderExtra{}) + block := NewBlock(header, nil, nil, nil, stubHasher{}) + blockExtra := &BlockBodyExtra{ExtData: &[]byte{}} + extras.Block.Set(block, blockExtra) + return block + }, + }, + { + name: "with_recalc", + header: func() *Header { + header := &Header{} + extra := &HeaderExtra{ + ExtDataHash: common.Hash{1}, // should be overwritten + } + SetHeaderExtra(header, extra) + return header + }, + extdata: []byte{2}, + recalc: true, + wantBlock: func() *Block { + header := &Header{} + extra := &HeaderExtra{ + ExtDataHash: CalcExtDataHash([]byte{2}), + } + SetHeaderExtra(header, extra) + block := NewBlock(header, nil, nil, nil, stubHasher{}) + blockExtra := &BlockBodyExtra{ExtData: &[]byte{2}} + extras.Block.Set(block, blockExtra) + return block + }, + }, + { + name: "filled_no_recalc", + header: func() *Header { + header := &Header{GasLimit: 1} + extra := &HeaderExtra{ + ExtDataHash: common.Hash{2}, + ExtDataGasUsed: big.NewInt(3), + BlockGasCost: big.NewInt(4), + } + SetHeaderExtra(header, extra) + return header + }, + txs: []*Transaction{testTx}, + uncles: func() []*Header { + uncle := &Header{GasLimit: 5} + SetHeaderExtra(uncle, &HeaderExtra{BlockGasCost: big.NewInt(6)}) + return []*Header{uncle} + }, + receipts: []*Receipt{{PostState: []byte{7}}}, + extdata: []byte{8}, + wantBlock: func() *Block { + header := &Header{GasLimit: 1} + extra := &HeaderExtra{ + ExtDataHash: common.Hash{2}, + ExtDataGasUsed: big.NewInt(3), + BlockGasCost: big.NewInt(4), + } + SetHeaderExtra(header, extra) + uncle := &Header{GasLimit: 5} + SetHeaderExtra(uncle, &HeaderExtra{BlockGasCost: big.NewInt(6)}) + uncles := []*Header{uncle} + block := NewBlock(header, []*Transaction{testTx}, uncles, []*Receipt{{PostState: []byte{7}}}, stubHasher{}) + blockExtra := &BlockBodyExtra{ExtData: &[]byte{8}} + extras.Block.Set(block, blockExtra) + return block + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + var uncles []*Header + if test.uncles != nil { + uncles = test.uncles() + } + + block := NewBlockWithExtData( + test.header(), + test.txs, + uncles, + test.receipts, + stubHasher{}, + test.extdata, + test.recalc, + ) + + assert.Equal(t, test.wantBlock(), block) + }) + } +} + +type stubHasher struct{} + +func (h stubHasher) Reset() {} +func (h stubHasher) Update(key, value []byte) error { return nil } +func (h stubHasher) Hash() common.Hash { return common.Hash{} } diff --git a/core/types/block_test.go b/core/types/block_test.go index 95546c83c3..9cadaeff76 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -69,10 +69,10 @@ func TestBlockEncoding(t *testing.T) { check("Extra", block.Extra(), common.FromHex("")) check("MixDigest", block.MixDigest(), common.HexToHash("0000000000000000000000000000000000000000000000000000000000000000")) check("Nonce", block.Nonce(), uint64(0)) - check("ExtDataHash", GetHeaderExtra(block.header).ExtDataHash, common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")) + check("ExtDataHash", GetHeaderExtra(block.Header()).ExtDataHash, common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")) check("BaseFee", block.BaseFee(), (*big.Int)(nil)) - check("ExtDataGasUsed", block.ExtDataGasUsed(), (*big.Int)(nil)) - check("BlockGasCost", block.BlockGasCost(), (*big.Int)(nil)) + check("ExtDataGasUsed", BlockExtDataGasUsed(&block), (*big.Int)(nil)) + check("BlockGasCost", BlockGasCost(&block), (*big.Int)(nil)) check("Size", block.Size(), uint64(len(blockEnc))) check("BlockHash", block.Hash(), common.HexToHash("0608e5d5e13c337f226b621a0b08b3d50470f1961329826fd59f5a241d1df49e")) @@ -113,8 +113,8 @@ func TestEIP1559BlockEncoding(t *testing.T) { check("Time", block.Time(), uint64(1426516743)) check("Size", block.Size(), uint64(len(blockEnc))) check("BaseFee", block.BaseFee(), new(big.Int).SetUint64(1_000_000_000)) - check("ExtDataGasUsed", block.ExtDataGasUsed(), (*big.Int)(nil)) - check("BlockGasCost", block.BlockGasCost(), (*big.Int)(nil)) + check("ExtDataGasUsed", BlockExtDataGasUsed(&block), (*big.Int)(nil)) + check("BlockGasCost", BlockGasCost(&block), (*big.Int)(nil)) tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil) tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100")) @@ -177,10 +177,10 @@ func TestEIP2718BlockEncoding(t *testing.T) { check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4)) check("Time", block.Time(), uint64(1426516743)) check("Size", block.Size(), uint64(len(blockEnc))) - check("ExtDataHash", GetHeaderExtra(block.header).ExtDataHash, common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")) + check("ExtDataHash", GetHeaderExtra(block.Header()).ExtDataHash, common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")) check("BaseFee", block.BaseFee(), (*big.Int)(nil)) - check("ExtDataGasUsed", block.ExtDataGasUsed(), (*big.Int)(nil)) - check("BlockGasCost", block.BlockGasCost(), (*big.Int)(nil)) + check("ExtDataGasUsed", BlockExtDataGasUsed(&block), (*big.Int)(nil)) + check("BlockGasCost", BlockGasCost(&block), (*big.Int)(nil)) // Create legacy tx. to := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") @@ -212,8 +212,8 @@ func TestEIP2718BlockEncoding(t *testing.T) { check("Transactions[1].Hash", block.Transactions()[1].Hash(), tx2.Hash()) check("Transactions[1].Type()", block.Transactions()[1].Type(), uint8(AccessListTxType)) - if !bytes.Equal(block.ExtData(), []byte{}) { - t.Errorf("Block ExtraData field mismatch, expected empty byte array, but found 0x%x", block.ExtData()) + if !bytes.Equal(BlockExtData(&block), []byte{}) { + t.Errorf("Block ExtraData field mismatch, expected empty byte array, but found 0x%x", BlockExtData(&block)) } ourBlockEnc, err := rlp.EncodeToBytes(&block) @@ -252,10 +252,10 @@ func TestBlockEncodingWithExtraData(t *testing.T) { check("Extra", block.Extra(), common.FromHex("")) check("MixDigest", block.MixDigest(), common.HexToHash("0000000000000000000000000000000000000000000000000000000000000000")) check("Nonce", block.Nonce(), uint64(0)) - check("ExtDataHash", GetHeaderExtra(block.header).ExtDataHash, common.HexToHash("296ff3bfdebf7c4b1fb71f589d69ed03b1c59b278d1780d54dc86ea7cb87cf17")) + check("ExtDataHash", GetHeaderExtra(block.Header()).ExtDataHash, common.HexToHash("296ff3bfdebf7c4b1fb71f589d69ed03b1c59b278d1780d54dc86ea7cb87cf17")) check("BaseFee", block.BaseFee(), (*big.Int)(nil)) - check("ExtDataGasUsed", block.ExtDataGasUsed(), (*big.Int)(nil)) - check("BlockGasCost", block.BlockGasCost(), (*big.Int)(nil)) + check("ExtDataGasUsed", BlockExtDataGasUsed(&block), (*big.Int)(nil)) + check("BlockGasCost", BlockGasCost(&block), (*big.Int)(nil)) check("Size", block.Size(), uint64(len(blockEnc))) check("BlockHash", block.Hash(), common.HexToHash("4504ee98a94d16dbd70a35370501a3cb00c2965b012672085fbd328a72962902")) @@ -263,8 +263,8 @@ func TestBlockEncodingWithExtraData(t *testing.T) { check("len(Transactions)", len(block.Transactions()), 0) expectedBlockExtraData := common.FromHex("00000000000000003039c85fc1980a77c5da78fe5486233fc09a769bb812bcb2cc548cf9495d046b3f1bd891ad56056d9c01f18f43f58b5c784ad07a4a49cf3d1f11623804b5cba2c6bf000000028a0f7c3e4d840143671a4c4ecacccb4d60fb97dce97a7aa5d60dfd072a7509cf00000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000500002d79883d20000000000100000000e0d5c4edc78f594b79025a56c44933c28e8ba3e51e6e23318727eeaac10eb27d00000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000500002d79883d20000000000100000000000000016dc8ea73dd39ab12fa2ecbc3427abaeb87d56fd800005af3107a4000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000200000009000000010d9f115cd63c3ab78b5b82cfbe4339cd6be87f21cda14cf192b269c7a6cb2d03666aa8f8b23ca0a2ceee4050e75c9b05525a17aa1dd0e9ea391a185ce395943f0000000009000000010d9f115cd63c3ab78b5b82cfbe4339cd6be87f21cda14cf192b269c7a6cb2d03666aa8f8b23ca0a2ceee4050e75c9b05525a17aa1dd0e9ea391a185ce395943f00") - if !bytes.Equal(block.ExtData(), expectedBlockExtraData) { - t.Errorf("Block ExtraData field mismatch, expected 0x%x, but found 0x%x", block.ExtData(), expectedBlockExtraData) + if !bytes.Equal(BlockExtData(&block), expectedBlockExtraData) { + t.Errorf("Block ExtraData field mismatch, expected 0x%x, but found 0x%x", BlockExtData(&block), expectedBlockExtraData) } ourBlockEnc, err := rlp.EncodeToBytes(&block) @@ -365,8 +365,8 @@ func TestAP4BlockEncoding(t *testing.T) { check("Time", block.Time(), uint64(1426516743)) check("Size", block.Size(), uint64(len(blockEnc))) check("BaseFee", block.BaseFee(), big.NewInt(1_000_000_000)) - check("ExtDataGasUsed", block.ExtDataGasUsed(), big.NewInt(25_000)) - check("BlockGasCost", block.BlockGasCost(), big.NewInt(1_000_000)) + check("ExtDataGasUsed", BlockExtDataGasUsed(&block), big.NewInt(25_000)) + check("BlockGasCost", BlockGasCost(&block), big.NewInt(1_000_000)) tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil) tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100")) diff --git a/core/types/header_ext_test.go b/core/types/header_ext_test.go index 769052a552..299746f88a 100644 --- a/core/types/header_ext_test.go +++ b/core/types/header_ext_test.go @@ -8,7 +8,9 @@ import ( "encoding/json" "math/big" "reflect" + "slices" "testing" + "unsafe" "github.com/ava-labs/libevm/common" ethtypes "github.com/ava-labs/libevm/core/types" @@ -73,8 +75,8 @@ func TestHeaderWithNonZeroFields(t *testing.T) { t.Parallel() header, extra := headerWithNonZeroFields() - t.Run("Header", func(t *testing.T) { allExportedFieldsSet(t, header) }) - t.Run("HeaderExtra", func(t *testing.T) { allExportedFieldsSet(t, extra) }) + t.Run("Header", func(t *testing.T) { allFieldsSet(t, header, "extra") }) + t.Run("HeaderExtra", func(t *testing.T) { allFieldsSet(t, extra) }) } // headerWithNonZeroFields returns a [Header] and a [HeaderExtra], @@ -116,22 +118,31 @@ func headerWithNonZeroFields() (*Header, *HeaderExtra) { return header, extra } -func allExportedFieldsSet[T interface { - ethtypes.Header | HeaderExtra -}](t *testing.T, x *T) { +func allFieldsSet[T interface { + Header | HeaderExtra | Block | Body | BlockBodyExtra +}](t *testing.T, x *T, ignoredFields ...string) { // We don't test for nil pointers because we're only confirming that // test-input data is well-formed. A panic due to a dereference will be // reported anyway. - v := reflect.ValueOf(*x) + v := reflect.ValueOf(x).Elem() for i := range v.Type().NumField() { field := v.Type().Field(i) - if !field.IsExported() { + if slices.Contains(ignoredFields, field.Name) { continue } t.Run(field.Name, func(t *testing.T) { - switch f := v.Field(i).Interface().(type) { + fieldValue := v.Field(i) + if !field.IsExported() { + // Note: we need to check unexported fields especially for [Block]. + if fieldValue.Kind() == reflect.Ptr { + require.Falsef(t, fieldValue.IsNil(), "field %q is nil", field.Name) + } + fieldValue = reflect.NewAt(fieldValue.Type(), unsafe.Pointer(fieldValue.UnsafeAddr())).Elem() //nolint:gosec + } + + switch f := fieldValue.Interface().(type) { case common.Hash: assertNonZero(t, f) case common.Address: @@ -140,6 +151,8 @@ func allExportedFieldsSet[T interface { assertNonZero(t, f) case Bloom: assertNonZero(t, f) + case uint32: + assertNonZero(t, f) case uint64: assertNonZero(t, f) case *big.Int: @@ -148,7 +161,11 @@ func allExportedFieldsSet[T interface { assertNonZero(t, f) case *uint64: assertNonZero(t, f) - case []uint8: + case *[]uint8: + assertNonZero(t, f) + case *Header: + assertNonZero(t, f) + case []uint8, []*Header, Transactions, []*Transaction, ethtypes.Withdrawals, []*ethtypes.Withdrawal: assert.NotEmpty(t, f) default: t.Errorf("Field %q has unsupported type %T", field.Name, f) @@ -158,8 +175,8 @@ func allExportedFieldsSet[T interface { } func assertNonZero[T interface { - common.Hash | common.Address | BlockNonce | uint64 | Bloom | - *big.Int | *common.Hash | *uint64 + common.Hash | common.Address | BlockNonce | uint32 | uint64 | Bloom | + *big.Int | *common.Hash | *uint64 | *[]uint8 | *Header }](t *testing.T, v T) { t.Helper() var zero T diff --git a/core/types/imports.go b/core/types/imports.go index e630ea5366..2a0ad279bf 100644 --- a/core/types/imports.go +++ b/core/types/imports.go @@ -15,8 +15,11 @@ type ( AccessTuple = ethtypes.AccessTuple BlobTx = ethtypes.BlobTx BlobTxSidecar = ethtypes.BlobTxSidecar + Block = ethtypes.Block BlockNonce = ethtypes.BlockNonce + Blocks = ethtypes.Blocks Bloom = ethtypes.Bloom + Body = ethtypes.Body DynamicFeeTx = ethtypes.DynamicFeeTx Header = ethtypes.Header HomesteadSigner = ethtypes.HomesteadSigner @@ -51,11 +54,14 @@ const ( var ( BloomLookup = ethtypes.BloomLookup BytesToBloom = ethtypes.BytesToBloom + CalcUncleHash = ethtypes.CalcUncleHash CopyHeader = ethtypes.CopyHeader CreateBloom = ethtypes.CreateBloom EncodeNonce = ethtypes.EncodeNonce FullAccount = ethtypes.FullAccount FullAccountRLP = ethtypes.FullAccountRLP + NewBlock = ethtypes.NewBlock + NewBlockWithHeader = ethtypes.NewBlockWithHeader NewContractCreation = ethtypes.NewContractCreation NewEmptyStateAccount = ethtypes.NewEmptyStateAccount NewReceipt = ethtypes.NewReceipt diff --git a/core/types/libevm.go b/core/types/libevm.go index a5cc469bca..32d81467e7 100644 --- a/core/types/libevm.go +++ b/core/types/libevm.go @@ -9,6 +9,6 @@ import ( var extras = ethtypes.RegisterExtras[ HeaderExtra, *HeaderExtra, - ethtypes.NOOPBlockBodyHooks, *ethtypes.NOOPBlockBodyHooks, + BlockBodyExtra, *BlockBodyExtra, isMultiCoin, ]() diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index fda588e45b..5ffb722096 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -265,7 +265,18 @@ func (ec *client) getBlock(ctx context.Context, method string, args ...interface } txs[i] = tx.tx } - return types.NewBlockWithHeader(head).WithBody(txs, uncles).WithExtData(body.Version, (*[]byte)(body.BlockExtraData)), nil + + block := types.NewBlockWithHeader(head).WithBody( + types.Body{ + Transactions: txs, + Uncles: uncles, + }) + extra := &types.BlockBodyExtra{ + Version: body.Version, + ExtData: (*[]byte)(body.BlockExtraData), + } + types.SetBlockExtra(block, extra) + return block, nil } // HeaderByHash returns the block header with the given hash. diff --git a/go.mod b/go.mod index 9e25296e41..3da197357e 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set/v2 v2.1.0 + github.com/google/go-cmp v0.7.0 github.com/gorilla/rpc v1.2.0 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/go-bexpr v0.1.10 diff --git a/go.sum b/go.sum index 40ed39bb9d..d8f3ed41d8 100644 --- a/go.sum +++ b/go.sum @@ -268,8 +268,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 05bc4ab4e1..4f883f457b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1279,7 +1279,7 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *params.ChainConfig) map[string]interface{} { fields := RPCMarshalHeader(block.Header()) fields["size"] = hexutil.Uint64(block.Size()) - fields["blockExtraData"] = hexutil.Bytes(block.ExtData()) + fields["blockExtraData"] = hexutil.Bytes(types.BlockExtData(block)) if inclTx { formatTx := func(idx int, tx *types.Transaction) interface{} { diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 766167e21f..274928cf8d 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -120,7 +120,7 @@ type Block struct { // newBlock returns a new Block wrapping the ethBlock type and implementing the snowman.Block interface func (vm *VM) newBlock(ethBlock *types.Block) (*Block, error) { isApricotPhase5 := vm.chainConfigExtra().IsApricotPhase5(ethBlock.Time()) - atomicTxs, err := atomic.ExtractAtomicTxs(ethBlock.ExtData(), isApricotPhase5, atomic.Codec) + atomicTxs, err := atomic.ExtractAtomicTxs(types.BlockExtData(ethBlock), isApricotPhase5, atomic.Codec) if err != nil { return nil, err } diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index 7d73e3eece..34fdbb393f 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -54,7 +54,7 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { if !rulesExtra.IsApricotPhase1 { if v.extDataHashes != nil { - extData := b.ethBlock.ExtData() + extData := types.BlockExtData(b.ethBlock) extDataHash := types.CalcExtDataHash(extData) // If there is no extra data, check that there is no extra data in the hash map either to ensure we do not // have a block that is unexpectedly missing extra data. @@ -81,7 +81,9 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { // Verify the ExtDataHash field headerExtra := types.GetHeaderExtra(ethHeader) if rulesExtra.IsApricotPhase1 { - if hash := types.CalcExtDataHash(b.ethBlock.ExtData()); headerExtra.ExtDataHash != hash { + extraData := types.BlockExtData(b.ethBlock) + hash := types.CalcExtDataHash(extraData) + if headerExtra.ExtDataHash != hash { return fmt.Errorf("extra data hash mismatch: have %x, want %x", headerExtra.ExtDataHash, hash) } } else { @@ -133,8 +135,8 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { return err } - if b.ethBlock.Version() != 0 { - return fmt.Errorf("invalid version: %d", b.ethBlock.Version()) + if version := types.BlockVersion(b.ethBlock); version != 0 { + return fmt.Errorf("invalid version: %d", version) } // Check that the tx hash in the header matches the body diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index 931a123f03..aab53572a0 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -605,7 +605,7 @@ func patchBlock(blk *types.Block, root common.Hash, db ethdb.Database) *types.Bl header.Root = root receipts := rawdb.ReadRawReceipts(db, blk.Hash(), blk.NumberU64()) newBlk := types.NewBlockWithExtData( - header, blk.Transactions(), blk.Uncles(), receipts, trie.NewStackTrie(nil), blk.ExtData(), true, + header, blk.Transactions(), blk.Uncles(), receipts, trie.NewStackTrie(nil), types.BlockExtData(blk), true, ) rawdb.WriteBlock(db, newBlk) rawdb.WriteCanonicalHash(db, newBlk.Hash(), newBlk.NumberU64()) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index b6111d1e4c..904dc5a582 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -935,7 +935,7 @@ func (vm *VM) onExtraStateChange(block *types.Block, state *state.StateDB) (*big rulesExtra = *params.GetRulesExtra(rules) ) - txs, err := atomic.ExtractAtomicTxs(block.ExtData(), rulesExtra.IsApricotPhase5, atomic.Codec) + txs, err := atomic.ExtractAtomicTxs(types.BlockExtData(block), rulesExtra.IsApricotPhase5, atomic.Codec) if err != nil { return nil, nil, err } diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index d79c966d1c..0224f9e2ca 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -2389,7 +2389,7 @@ func TestUncleBlock(t *testing.T) { uncles, nil, trie.NewStackTrie(nil), - blkDEthBlock.ExtData(), + types.BlockExtData(blkDEthBlock), false, ) uncleBlock, err := vm2.newBlock(uncleEthBlock) @@ -2450,7 +2450,7 @@ func TestEmptyBlock(t *testing.T) { false, ) - if len(emptyEthBlock.ExtData()) != 0 || types.GetHeaderExtra(emptyEthBlock.Header()).ExtDataHash != (common.Hash{}) { + if len(types.BlockExtData(emptyEthBlock)) != 0 || types.GetHeaderExtra(emptyEthBlock.Header()).ExtDataHash != (common.Hash{}) { t.Fatalf("emptyEthBlock should not have any extra data") } @@ -2716,7 +2716,7 @@ func TestFutureBlock(t *testing.T) { nil, nil, new(trie.Trie), - internalBlkA.ethBlock.ExtData(), + types.BlockExtData(internalBlkA.ethBlock), false, ) @@ -3271,10 +3271,10 @@ func TestBuildApricotPhase4Block(t *testing.T) { } ethBlk := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - if eBlockGasCost := ethBlk.BlockGasCost(); eBlockGasCost == nil || eBlockGasCost.Cmp(common.Big0) != 0 { + if eBlockGasCost := types.BlockGasCost(ethBlk); eBlockGasCost == nil || eBlockGasCost.Cmp(common.Big0) != 0 { t.Fatalf("expected blockGasCost to be 0 but got %d", eBlockGasCost) } - if eExtDataGasUsed := ethBlk.ExtDataGasUsed(); eExtDataGasUsed == nil || eExtDataGasUsed.Cmp(big.NewInt(1230)) != 0 { + if eExtDataGasUsed := types.BlockExtDataGasUsed(ethBlk); eExtDataGasUsed == nil || eExtDataGasUsed.Cmp(big.NewInt(1230)) != 0 { t.Fatalf("expected extDataGasUsed to be 1000 but got %d", eExtDataGasUsed) } minRequiredTip, err := header.EstimateRequiredTip(vm.chainConfigExtra(), ethBlk.Header()) @@ -3330,11 +3330,11 @@ func TestBuildApricotPhase4Block(t *testing.T) { } ethBlk = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - if ethBlk.BlockGasCost() == nil || ethBlk.BlockGasCost().Cmp(big.NewInt(100)) < 0 { - t.Fatalf("expected blockGasCost to be at least 100 but got %d", ethBlk.BlockGasCost()) + if types.BlockGasCost(ethBlk) == nil || types.BlockGasCost(ethBlk).Cmp(big.NewInt(100)) < 0 { + t.Fatalf("expected blockGasCost to be at least 100 but got %d", types.BlockGasCost(ethBlk)) } - if ethBlk.ExtDataGasUsed() == nil || ethBlk.ExtDataGasUsed().Cmp(common.Big0) != 0 { - t.Fatalf("expected extDataGasUsed to be 0 but got %d", ethBlk.ExtDataGasUsed()) + if types.BlockExtDataGasUsed(ethBlk) == nil || types.BlockExtDataGasUsed(ethBlk).Cmp(common.Big0) != 0 { + t.Fatalf("expected extDataGasUsed to be 0 but got %d", types.BlockExtDataGasUsed(ethBlk)) } minRequiredTip, err = header.EstimateRequiredTip(vm.chainConfigExtra(), ethBlk.Header()) if err != nil { @@ -3441,10 +3441,10 @@ func TestBuildApricotPhase5Block(t *testing.T) { } ethBlk := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - if eBlockGasCost := ethBlk.BlockGasCost(); eBlockGasCost == nil || eBlockGasCost.Cmp(common.Big0) != 0 { + if eBlockGasCost := types.BlockGasCost(ethBlk); eBlockGasCost == nil || eBlockGasCost.Cmp(common.Big0) != 0 { t.Fatalf("expected blockGasCost to be 0 but got %d", eBlockGasCost) } - if eExtDataGasUsed := ethBlk.ExtDataGasUsed(); eExtDataGasUsed == nil || eExtDataGasUsed.Cmp(big.NewInt(11230)) != 0 { + if eExtDataGasUsed := types.BlockExtDataGasUsed(ethBlk); eExtDataGasUsed == nil || eExtDataGasUsed.Cmp(big.NewInt(11230)) != 0 { t.Fatalf("expected extDataGasUsed to be 11230 but got %d", eExtDataGasUsed) } minRequiredTip, err := header.EstimateRequiredTip(vm.chainConfigExtra(), ethBlk.Header()) @@ -3492,11 +3492,11 @@ func TestBuildApricotPhase5Block(t *testing.T) { } ethBlk = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - if ethBlk.BlockGasCost() == nil || ethBlk.BlockGasCost().Cmp(big.NewInt(100)) < 0 { - t.Fatalf("expected blockGasCost to be at least 100 but got %d", ethBlk.BlockGasCost()) + if types.BlockGasCost(ethBlk) == nil || types.BlockGasCost(ethBlk).Cmp(big.NewInt(100)) < 0 { + t.Fatalf("expected blockGasCost to be at least 100 but got %d", types.BlockGasCost(ethBlk)) } - if ethBlk.ExtDataGasUsed() == nil || ethBlk.ExtDataGasUsed().Cmp(common.Big0) != 0 { - t.Fatalf("expected extDataGasUsed to be 0 but got %d", ethBlk.ExtDataGasUsed()) + if types.BlockExtDataGasUsed(ethBlk) == nil || types.BlockExtDataGasUsed(ethBlk).Cmp(common.Big0) != 0 { + t.Fatalf("expected extDataGasUsed to be 0 but got %d", types.BlockExtDataGasUsed(ethBlk)) } minRequiredTip, err = header.EstimateRequiredTip(vm.chainConfigExtra(), ethBlk.Header()) if err != nil { @@ -3909,7 +3909,7 @@ func TestParentBeaconRootBlock(t *testing.T) { nil, nil, new(trie.Trie), - ethBlock.ExtData(), + types.BlockExtData(ethBlock), false, ) diff --git a/sync/client/client_test.go b/sync/client/client_test.go index 70c7f6f118..a45438bab6 100644 --- a/sync/client/client_test.go +++ b/sync/client/client_test.go @@ -266,7 +266,7 @@ func TestGetBlocks(t *testing.T) { return responseBytes }, - expectedErr: "failed to unmarshal response: rlp: expected input list for types.extblock", + expectedErr: "failed to unmarshal response: rlp: expected List", }, "incorrect starting point": { request: message.BlockRequest{ @@ -381,7 +381,7 @@ func TestGetBlocks(t *testing.T) { if err == nil { t.Fatalf("Expected error: %s, but found no error", test.expectedErr) } - assert.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error to contain [%s], but found [%s]", test.expectedErr, err) + assert.ErrorContains(t, err, test.expectedErr) return } if err != nil {