Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions consensus/dummy/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,9 @@ func verifyHeaderGasFields(config *extras.ChainConfig, header *types.Header, par
parent,
header.Time,
)
if !utils.BigEqual(header.BlockGasCost, expectedBlockGasCost) {
return fmt.Errorf("invalid block gas cost: have %d, want %d", header.BlockGasCost, expectedBlockGasCost)
headerExtra := types.GetHeaderExtra(header)
if !utils.BigEqual(headerExtra.BlockGasCost, expectedBlockGasCost) {
return fmt.Errorf("invalid block gas cost: have %d, want %d", headerExtra.BlockGasCost, expectedBlockGasCost)
}
return nil
}
Expand Down Expand Up @@ -373,7 +374,8 @@ func (eng *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, h
config := params.GetExtra(chain.Config())

// Calculate the required block gas cost for this block.
header.BlockGasCost = customheader.BlockGasCost(
headerExtra := types.GetHeaderExtra(header)
headerExtra.BlockGasCost = customheader.BlockGasCost(
config,
feeConfig,
parent,
Expand All @@ -383,7 +385,7 @@ func (eng *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, h
// Verify that this block covers the block fee.
if err := eng.verifyBlockFee(
header.BaseFee,
header.BlockGasCost,
headerExtra.BlockGasCost,
txs,
receipts,
); err != nil {
Expand Down
3 changes: 2 additions & 1 deletion core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,8 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
}

if params.GetExtra(config).IsSubnetEVM(header.Time) {
header.BlockGasCost = big.NewInt(0)
headerExtra := types.GetHeaderExtra(header)
headerExtra.BlockGasCost = big.NewInt(0)
}
var receipts []*types.Receipt
// The post-state result doesn't need to be correct (this is a bad block), but we do need something there
Expand Down
2 changes: 1 addition & 1 deletion core/types/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"github.com/ava-labs/libevm/common/math"
)

//go:generate go run github.com/fjl/gencodec -type Account -field-override accountMarshaling -out gen_account.go
//go:generate go run github.com/fjl/gencodec@a3c3302847cea77ab534228aefa025992dc2c696 -type Account -field-override accountMarshaling -out gen_account.go

// Account represents an Ethereum account and its attached data.
// This type is used to specify accounts in the genesis block state, and
Expand Down
128 changes: 13 additions & 115 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,123 +31,12 @@ import (
"encoding/binary"
"io"
"math/big"
"reflect"
"sync/atomic"

"github.com/ava-labs/libevm/common"
"github.com/ava-labs/libevm/common/hexutil"
"github.com/ava-labs/libevm/rlp"
)

// A BlockNonce is a 64-bit hash which proves (combined with the
// mix-hash) that a sufficient amount of computation has been carried
// out on a block.
type BlockNonce [8]byte

// EncodeNonce converts the given integer to a block nonce.
func EncodeNonce(i uint64) BlockNonce {
var n BlockNonce
binary.BigEndian.PutUint64(n[:], i)
return n
}

// Uint64 returns the integer value of a block nonce.
func (n BlockNonce) Uint64() uint64 {
return binary.BigEndian.Uint64(n[:])
}

// MarshalText encodes n as a hex string with 0x prefix.
func (n BlockNonce) MarshalText() ([]byte, error) {
return hexutil.Bytes(n[:]).MarshalText()
}

// UnmarshalText implements encoding.TextUnmarshaler.
func (n *BlockNonce) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("BlockNonce", input, n[:])
}

//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
//go:generate go run github.com/ava-labs/libevm/rlp/rlpgen -type Header -out gen_header_rlp.go

// Header represents a block header in the Ethereum blockchain.
type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
Coinbase common.Address `json:"miner" gencodec:"required"`
Root common.Hash `json:"stateRoot" gencodec:"required"`
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"`
Difficulty *big.Int `json:"difficulty" gencodec:"required"`
Number *big.Int `json:"number" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Time uint64 `json:"timestamp" gencodec:"required"`
Extra []byte `json:"extraData" gencodec:"required"`
MixDigest common.Hash `json:"mixHash"`
Nonce BlockNonce `json:"nonce"`

// BaseFee was added by EIP-1559 and is ignored in legacy headers.
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`

// BlockGasCost was added by SubnetEVM and is ignored in legacy
// headers.
BlockGasCost *big.Int `json:"blockGasCost" rlp:"optional"`

// BlobGasUsed was added by EIP-4844 and is ignored in legacy headers.
BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"`

// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.
ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"`

// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
}

// field type overrides for gencodec
type headerMarshaling struct {
Difficulty *hexutil.Big
Number *hexutil.Big
GasLimit hexutil.Uint64
GasUsed hexutil.Uint64
Time hexutil.Uint64
Extra hexutil.Bytes
BaseFee *hexutil.Big
BlockGasCost *hexutil.Big
Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON
BlobGasUsed *hexutil.Uint64
ExcessBlobGas *hexutil.Uint64
}

// Hash returns the block hash of the header, which is simply the keccak256 hash of its
// RLP encoding.
func (h *Header) Hash() common.Hash {
return rlpHash(h)
}

var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size())

// Size returns the approximate memory used by all internal contents. It is used
// to approximate and limit the memory consumption of various caches.
func (h *Header) Size() common.StorageSize {
var baseFeeBits int
if h.BaseFee != nil {
baseFeeBits = h.BaseFee.BitLen()
}
return headerSize + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+baseFeeBits)/8)
}

// EmptyBody returns true if there is no additional 'body' to complete the header
// that is: no transactions and no uncles.
func (h *Header) EmptyBody() bool {
return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash
}

// EmptyReceipts returns true if there are no receipts for this header/block.
func (h *Header) EmptyReceipts() bool {
return h.ReceiptHash == EmptyReceiptsHash
}

// 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 {
Expand Down Expand Up @@ -232,6 +121,10 @@ func NewBlock(
// CopyHeader creates a deep copy of a block header.
func CopyHeader(h *Header) *Header {
cpy := *h
hExtra := GetHeaderExtra(h)
cpyExtra := &HeaderExtra{}
SetHeaderExtra(&cpy, cpyExtra)

if cpy.Difficulty = new(big.Int); h.Difficulty != nil {
cpy.Difficulty.Set(h.Difficulty)
}
Expand All @@ -241,13 +134,17 @@ func CopyHeader(h *Header) *Header {
if h.BaseFee != nil {
cpy.BaseFee = new(big.Int).Set(h.BaseFee)
}
if h.BlockGasCost != nil {
cpy.BlockGasCost = new(big.Int).Set(h.BlockGasCost)
if hExtra.BlockGasCost != nil {
cpyExtra.BlockGasCost = new(big.Int).Set(hExtra.BlockGasCost)
}
if len(h.Extra) > 0 {
cpy.Extra = make([]byte, len(h.Extra))
copy(cpy.Extra, h.Extra)
}
if h.WithdrawalsHash != nil {
cpy.WithdrawalsHash = new(common.Hash)
*cpy.WithdrawalsHash = *h.WithdrawalsHash
}
if h.ExcessBlobGas != nil {
cpy.ExcessBlobGas = new(uint64)
*cpy.ExcessBlobGas = *h.ExcessBlobGas
Expand Down Expand Up @@ -358,10 +255,11 @@ func (b *Block) BlobGasUsed() *uint64 {
}

func (b *Block) BlockGasCost() *big.Int {
if b.header.BlockGasCost == nil {
cost := GetHeaderExtra(b.header).BlockGasCost
if cost == nil {
return nil
}
return new(big.Int).Set(b.header.BlockGasCost)
return new(big.Int).Set(cost)
}

// Size returns the true RLP encoded storage size of the block, either by encoding
Expand Down
108 changes: 108 additions & 0 deletions core/types/block_ext_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// (c) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package types

import (
"math/big"
"reflect"
"testing"
"unsafe"

"github.com/ava-labs/libevm/common"
"github.com/stretchr/testify/assert"
)

func TestCopyHeader(t *testing.T) {
t.Parallel()

t.Run("empty_header", func(t *testing.T) {
t.Parallel()

empty := &Header{}

headerExtra := &HeaderExtra{}
extras.Header.Set(empty, headerExtra)

cpy := CopyHeader(empty)

want := &Header{
Difficulty: new(big.Int),
Number: new(big.Int),
}

headerExtra = &HeaderExtra{}
extras.Header.Set(want, headerExtra)

assert.Equal(t, want, cpy)
})

t.Run("filled_header", func(t *testing.T) {
t.Parallel()

header, _ := headerWithNonZeroFields() // the header carries the [HeaderExtra] so we can ignore it

gotHeader := CopyHeader(header)
gotExtra := GetHeaderExtra(gotHeader)

wantHeader, wantExtra := headerWithNonZeroFields()
assert.Equal(t, wantHeader, gotHeader)
assert.Equal(t, wantExtra, gotExtra)

exportedFieldsPointToDifferentMemory(t, header, gotHeader)
exportedFieldsPointToDifferentMemory(t, GetHeaderExtra(header), gotExtra)
})
}

func exportedFieldsPointToDifferentMemory[T interface {
Header | HeaderExtra
}](t *testing.T, original, cpy *T) {
t.Helper()

v := reflect.ValueOf(*original)
typ := v.Type()
cp := reflect.ValueOf(*cpy)
for i := range v.NumField() {
field := typ.Field(i)
if !field.IsExported() {
continue
}
switch field.Type.Kind() {
case reflect.Array, reflect.Uint64:
// Not pointers, but using explicit Kinds for safety
continue
}

t.Run(field.Name, func(t *testing.T) {
fieldCp := cp.Field(i).Interface()
switch f := v.Field(i).Interface().(type) {
case *big.Int:
assertDifferentPointers(t, f, fieldCp)
case *common.Hash:
assertDifferentPointers(t, f, fieldCp)
case *uint64:
assertDifferentPointers(t, f, fieldCp)
case []uint8:
assertDifferentPointers(t, unsafe.SliceData(f), unsafe.SliceData(fieldCp.([]uint8)))
default:
t.Errorf("field %q type %T needs to be added to switch cases of exportedFieldsDeepCopied", field.Name, f)
}
})
}
}

// assertDifferentPointers asserts that `a` and `b` are both non-nil
// pointers pointing to different memory locations.
func assertDifferentPointers[T any](t *testing.T, a *T, b any) {
t.Helper()
switch {
case a == nil:
t.Errorf("a (%T) cannot be nil", a)
case b == nil:
t.Errorf("b (%T) cannot be nil", b)
case a == b:
t.Errorf("pointers to same memory")
}
// Note: no need to check `b` is of the same type as `a`, otherwise
// the memory address would be different as well.
}
Loading
Loading