Skip to content

Commit 191be9a

Browse files
Darioush Jalaliqdm12ARR4Nceyonur
committed
chore(core/types): header JSON and RLP serialization hooks
- Define `HeaderExtra` with `RLP` and `JSON` serialization methods - remove `BlockNonce` - remove `EncodeNonce` - new functions `GetHeaderExtra` and `SetHeaderExtra` - Migrate existing custom `Header` to `HeaderSerializable` in block_ext.go with only `Hash` method for RLP code generation - Rename files gen_header_json.go to gen_header_serializable_json.go - Rename files gen_header_rlp.go to gen_header_serializable_rlp.go See original PR ava-labs/coreth#746 Signed-off-by: Quentin McGaw <[email protected]> Co-authored-by: Quentin Mc Gaw <[email protected]> Co-authored-by: Arran Schlosberg <[email protected]> Co-authored-by: Ceyhun Onur <[email protected]> Co-authored-by: Darioush Jalili <[email protected]>
1 parent 32ae53a commit 191be9a

17 files changed

+608
-164
lines changed

consensus/dummy/consensus.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,9 @@ func verifyHeaderGasFields(config *extras.ChainConfig, header *types.Header, par
162162
parent,
163163
header.Time,
164164
)
165-
if !utils.BigEqual(header.BlockGasCost, expectedBlockGasCost) {
166-
return fmt.Errorf("invalid block gas cost: have %d, want %d", header.BlockGasCost, expectedBlockGasCost)
165+
headerExtra := types.GetHeaderExtra(header)
166+
if !utils.BigEqual(headerExtra.BlockGasCost, expectedBlockGasCost) {
167+
return fmt.Errorf("invalid block gas cost: have %d, want %d", headerExtra.BlockGasCost, expectedBlockGasCost)
167168
}
168169
return nil
169170
}
@@ -373,7 +374,8 @@ func (eng *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, h
373374
config := params.GetExtra(chain.Config())
374375

375376
// Calculate the required block gas cost for this block.
376-
header.BlockGasCost = customheader.BlockGasCost(
377+
headerExtra := types.GetHeaderExtra(header)
378+
headerExtra.BlockGasCost = customheader.BlockGasCost(
377379
config,
378380
feeConfig,
379381
parent,
@@ -383,7 +385,7 @@ func (eng *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, h
383385
// Verify that this block covers the block fee.
384386
if err := eng.verifyBlockFee(
385387
header.BaseFee,
386-
header.BlockGasCost,
388+
headerExtra.BlockGasCost,
387389
txs,
388390
receipts,
389391
); err != nil {

core/state_processor_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,8 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
379379
}
380380

381381
if params.GetExtra(config).IsSubnetEVM(header.Time) {
382-
header.BlockGasCost = big.NewInt(0)
382+
headerExtra := types.GetHeaderExtra(header)
383+
headerExtra.BlockGasCost = big.NewInt(0)
383384
}
384385
var receipts []*types.Receipt
385386
// The post-state result doesn't need to be correct (this is a bad block), but we do need something there

core/types/account.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
"github.com/ava-labs/libevm/common/math"
2929
)
3030

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

3333
// Account represents an Ethereum account and its attached data.
3434
// This type is used to specify accounts in the genesis block state, and

core/types/block.go

Lines changed: 13 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -31,123 +31,12 @@ import (
3131
"encoding/binary"
3232
"io"
3333
"math/big"
34-
"reflect"
3534
"sync/atomic"
3635

3736
"github.com/ava-labs/libevm/common"
38-
"github.com/ava-labs/libevm/common/hexutil"
3937
"github.com/ava-labs/libevm/rlp"
4038
)
4139

42-
// A BlockNonce is a 64-bit hash which proves (combined with the
43-
// mix-hash) that a sufficient amount of computation has been carried
44-
// out on a block.
45-
type BlockNonce [8]byte
46-
47-
// EncodeNonce converts the given integer to a block nonce.
48-
func EncodeNonce(i uint64) BlockNonce {
49-
var n BlockNonce
50-
binary.BigEndian.PutUint64(n[:], i)
51-
return n
52-
}
53-
54-
// Uint64 returns the integer value of a block nonce.
55-
func (n BlockNonce) Uint64() uint64 {
56-
return binary.BigEndian.Uint64(n[:])
57-
}
58-
59-
// MarshalText encodes n as a hex string with 0x prefix.
60-
func (n BlockNonce) MarshalText() ([]byte, error) {
61-
return hexutil.Bytes(n[:]).MarshalText()
62-
}
63-
64-
// UnmarshalText implements encoding.TextUnmarshaler.
65-
func (n *BlockNonce) UnmarshalText(input []byte) error {
66-
return hexutil.UnmarshalFixedText("BlockNonce", input, n[:])
67-
}
68-
69-
//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
70-
//go:generate go run github.com/ava-labs/libevm/rlp/rlpgen -type Header -out gen_header_rlp.go
71-
72-
// Header represents a block header in the Ethereum blockchain.
73-
type Header struct {
74-
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
75-
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
76-
Coinbase common.Address `json:"miner" gencodec:"required"`
77-
Root common.Hash `json:"stateRoot" gencodec:"required"`
78-
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
79-
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
80-
Bloom Bloom `json:"logsBloom" gencodec:"required"`
81-
Difficulty *big.Int `json:"difficulty" gencodec:"required"`
82-
Number *big.Int `json:"number" gencodec:"required"`
83-
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
84-
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
85-
Time uint64 `json:"timestamp" gencodec:"required"`
86-
Extra []byte `json:"extraData" gencodec:"required"`
87-
MixDigest common.Hash `json:"mixHash"`
88-
Nonce BlockNonce `json:"nonce"`
89-
90-
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
91-
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
92-
93-
// BlockGasCost was added by SubnetEVM and is ignored in legacy
94-
// headers.
95-
BlockGasCost *big.Int `json:"blockGasCost" rlp:"optional"`
96-
97-
// BlobGasUsed was added by EIP-4844 and is ignored in legacy headers.
98-
BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"`
99-
100-
// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.
101-
ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"`
102-
103-
// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
104-
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
105-
}
106-
107-
// field type overrides for gencodec
108-
type headerMarshaling struct {
109-
Difficulty *hexutil.Big
110-
Number *hexutil.Big
111-
GasLimit hexutil.Uint64
112-
GasUsed hexutil.Uint64
113-
Time hexutil.Uint64
114-
Extra hexutil.Bytes
115-
BaseFee *hexutil.Big
116-
BlockGasCost *hexutil.Big
117-
Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON
118-
BlobGasUsed *hexutil.Uint64
119-
ExcessBlobGas *hexutil.Uint64
120-
}
121-
122-
// Hash returns the block hash of the header, which is simply the keccak256 hash of its
123-
// RLP encoding.
124-
func (h *Header) Hash() common.Hash {
125-
return rlpHash(h)
126-
}
127-
128-
var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size())
129-
130-
// Size returns the approximate memory used by all internal contents. It is used
131-
// to approximate and limit the memory consumption of various caches.
132-
func (h *Header) Size() common.StorageSize {
133-
var baseFeeBits int
134-
if h.BaseFee != nil {
135-
baseFeeBits = h.BaseFee.BitLen()
136-
}
137-
return headerSize + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+baseFeeBits)/8)
138-
}
139-
140-
// EmptyBody returns true if there is no additional 'body' to complete the header
141-
// that is: no transactions and no uncles.
142-
func (h *Header) EmptyBody() bool {
143-
return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash
144-
}
145-
146-
// EmptyReceipts returns true if there are no receipts for this header/block.
147-
func (h *Header) EmptyReceipts() bool {
148-
return h.ReceiptHash == EmptyReceiptsHash
149-
}
150-
15140
// Body is a simple (mutable, non-safe) data container for storing and moving
15241
// a block's data contents (transactions and uncles) together.
15342
type Body struct {
@@ -232,6 +121,10 @@ func NewBlock(
232121
// CopyHeader creates a deep copy of a block header.
233122
func CopyHeader(h *Header) *Header {
234123
cpy := *h
124+
hExtra := GetHeaderExtra(h)
125+
cpyExtra := &HeaderExtra{}
126+
SetHeaderExtra(&cpy, cpyExtra)
127+
235128
if cpy.Difficulty = new(big.Int); h.Difficulty != nil {
236129
cpy.Difficulty.Set(h.Difficulty)
237130
}
@@ -241,13 +134,17 @@ func CopyHeader(h *Header) *Header {
241134
if h.BaseFee != nil {
242135
cpy.BaseFee = new(big.Int).Set(h.BaseFee)
243136
}
244-
if h.BlockGasCost != nil {
245-
cpy.BlockGasCost = new(big.Int).Set(h.BlockGasCost)
137+
if hExtra.BlockGasCost != nil {
138+
cpyExtra.BlockGasCost = new(big.Int).Set(hExtra.BlockGasCost)
246139
}
247140
if len(h.Extra) > 0 {
248141
cpy.Extra = make([]byte, len(h.Extra))
249142
copy(cpy.Extra, h.Extra)
250143
}
144+
if h.WithdrawalsHash != nil {
145+
cpy.WithdrawalsHash = new(common.Hash)
146+
*cpy.WithdrawalsHash = *h.WithdrawalsHash
147+
}
251148
if h.ExcessBlobGas != nil {
252149
cpy.ExcessBlobGas = new(uint64)
253150
*cpy.ExcessBlobGas = *h.ExcessBlobGas
@@ -358,10 +255,11 @@ func (b *Block) BlobGasUsed() *uint64 {
358255
}
359256

360257
func (b *Block) BlockGasCost() *big.Int {
361-
if b.header.BlockGasCost == nil {
258+
cost := GetHeaderExtra(b.header).BlockGasCost
259+
if cost == nil {
362260
return nil
363261
}
364-
return new(big.Int).Set(b.header.BlockGasCost)
262+
return new(big.Int).Set(cost)
365263
}
366264

367265
// Size returns the true RLP encoded storage size of the block, either by encoding

core/types/block_ext_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// (c) 2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package types
5+
6+
import (
7+
"math/big"
8+
"reflect"
9+
"testing"
10+
"unsafe"
11+
12+
"github.com/ava-labs/libevm/common"
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func TestCopyHeader(t *testing.T) {
17+
t.Parallel()
18+
19+
t.Run("empty_header", func(t *testing.T) {
20+
t.Parallel()
21+
22+
empty := &Header{}
23+
24+
headerExtra := &HeaderExtra{}
25+
extras.Header.Set(empty, headerExtra)
26+
27+
cpy := CopyHeader(empty)
28+
29+
want := &Header{
30+
Difficulty: new(big.Int),
31+
Number: new(big.Int),
32+
}
33+
34+
headerExtra = &HeaderExtra{}
35+
extras.Header.Set(want, headerExtra)
36+
37+
assert.Equal(t, want, cpy)
38+
})
39+
40+
t.Run("filled_header", func(t *testing.T) {
41+
t.Parallel()
42+
43+
header, _ := headerWithNonZeroFields() // the header carries the [HeaderExtra] so we can ignore it
44+
45+
gotHeader := CopyHeader(header)
46+
gotExtra := GetHeaderExtra(gotHeader)
47+
48+
wantHeader, wantExtra := headerWithNonZeroFields()
49+
assert.Equal(t, wantHeader, gotHeader)
50+
assert.Equal(t, wantExtra, gotExtra)
51+
52+
exportedFieldsPointToDifferentMemory(t, header, gotHeader)
53+
exportedFieldsPointToDifferentMemory(t, GetHeaderExtra(header), gotExtra)
54+
})
55+
}
56+
57+
func exportedFieldsPointToDifferentMemory[T interface {
58+
Header | HeaderExtra
59+
}](t *testing.T, original, cpy *T) {
60+
t.Helper()
61+
62+
v := reflect.ValueOf(*original)
63+
typ := v.Type()
64+
cp := reflect.ValueOf(*cpy)
65+
for i := range v.NumField() {
66+
field := typ.Field(i)
67+
if !field.IsExported() {
68+
continue
69+
}
70+
switch field.Type.Kind() {
71+
case reflect.Array, reflect.Uint64:
72+
// Not pointers, but using explicit Kinds for safety
73+
continue
74+
}
75+
76+
t.Run(field.Name, func(t *testing.T) {
77+
fieldCp := cp.Field(i).Interface()
78+
switch f := v.Field(i).Interface().(type) {
79+
case *big.Int:
80+
assertDifferentPointers(t, f, fieldCp)
81+
case *common.Hash:
82+
assertDifferentPointers(t, f, fieldCp)
83+
case *uint64:
84+
assertDifferentPointers(t, f, fieldCp)
85+
case []uint8:
86+
assertDifferentPointers(t, unsafe.SliceData(f), unsafe.SliceData(fieldCp.([]uint8)))
87+
default:
88+
t.Errorf("field %q type %T needs to be added to switch cases of exportedFieldsDeepCopied", field.Name, f)
89+
}
90+
})
91+
}
92+
}
93+
94+
// assertDifferentPointers asserts that `a` and `b` are both non-nil
95+
// pointers pointing to different memory locations.
96+
func assertDifferentPointers[T any](t *testing.T, a *T, b any) {
97+
t.Helper()
98+
switch {
99+
case a == nil:
100+
t.Errorf("a (%T) cannot be nil", a)
101+
case b == nil:
102+
t.Errorf("b (%T) cannot be nil", b)
103+
case a == b:
104+
t.Errorf("pointers to same memory")
105+
}
106+
// Note: no need to check `b` is of the same type as `a`, otherwise
107+
// the memory address would be different as well.
108+
}

0 commit comments

Comments
 (0)