Skip to content
This repository was archived by the owner on Nov 25, 2025. It is now read-only.

Commit d061840

Browse files
committed
Add TestCopyHeader
1 parent 553c4ca commit d061840

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed

core/types/block.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ func CopyHeader(h *Header) *Header {
154154
cpy.Extra = make([]byte, len(h.Extra))
155155
copy(cpy.Extra, h.Extra)
156156
}
157+
if h.WithdrawalsHash != nil {
158+
cpy.WithdrawalsHash = new(common.Hash)
159+
*cpy.WithdrawalsHash = *h.WithdrawalsHash
160+
}
157161
if h.ExcessBlobGas != nil {
158162
cpy.ExcessBlobGas = new(uint64)
159163
*cpy.ExcessBlobGas = *h.ExcessBlobGas

core/types/block_ext_test.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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+
"unicode"
11+
"unsafe"
12+
13+
"github.com/ava-labs/libevm/common"
14+
"github.com/ava-labs/libevm/libevm/pseudo"
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
func TestCopyHeader(t *testing.T) {
20+
t.Run("empty_header", func(t *testing.T) {
21+
empty := &Header{}
22+
23+
headerExtra := &HeaderExtra{}
24+
extras.Header.Set(empty, headerExtra)
25+
26+
cpy := CopyHeader(empty)
27+
28+
want := &Header{
29+
Difficulty: new(big.Int),
30+
Number: new(big.Int),
31+
}
32+
33+
headerExtra = &HeaderExtra{}
34+
extras.Header.Set(want, headerExtra)
35+
36+
assert.Equal(t, want, cpy)
37+
})
38+
39+
t.Run("filled_header", func(t *testing.T) {
40+
h := &Header{
41+
ParentHash: common.Hash{1},
42+
UncleHash: common.Hash{2},
43+
Coinbase: common.Address{3},
44+
Root: common.Hash{4},
45+
TxHash: common.Hash{5},
46+
ReceiptHash: common.Hash{6},
47+
Bloom: Bloom{7},
48+
Difficulty: big.NewInt(8),
49+
Number: big.NewInt(9),
50+
GasLimit: 10,
51+
GasUsed: 11,
52+
Time: 12,
53+
Extra: []byte{13},
54+
MixDigest: common.Hash{14},
55+
Nonce: BlockNonce{15},
56+
BaseFee: big.NewInt(16),
57+
WithdrawalsHash: &common.Hash{17},
58+
BlobGasUsed: ptrTo(uint64(18)),
59+
ExcessBlobGas: ptrTo(uint64(19)),
60+
ParentBeaconRoot: &common.Hash{20},
61+
}
62+
headerExtra := &HeaderExtra{
63+
ExtDataHash: common.Hash{21},
64+
ExtDataGasUsed: big.NewInt(22),
65+
BlockGasCost: big.NewInt(23),
66+
}
67+
extras.Header.Set(h, headerExtra)
68+
69+
allFieldsAreSet(t, h)
70+
allFieldsAreSet(t, headerExtra)
71+
72+
cpy := CopyHeader(h)
73+
74+
want := &Header{
75+
ParentHash: common.Hash{1},
76+
UncleHash: common.Hash{2},
77+
Coinbase: common.Address{3},
78+
Root: common.Hash{4},
79+
TxHash: common.Hash{5},
80+
ReceiptHash: common.Hash{6},
81+
Bloom: Bloom{7},
82+
Difficulty: big.NewInt(8),
83+
Number: big.NewInt(9),
84+
GasLimit: 10,
85+
GasUsed: 11,
86+
Time: 12,
87+
Extra: []byte{13},
88+
MixDigest: common.Hash{14},
89+
Nonce: BlockNonce{15},
90+
BaseFee: big.NewInt(16),
91+
WithdrawalsHash: &common.Hash{17},
92+
BlobGasUsed: ptrTo(uint64(18)),
93+
ExcessBlobGas: ptrTo(uint64(19)),
94+
ParentBeaconRoot: &common.Hash{20},
95+
}
96+
headerExtra = &HeaderExtra{
97+
ExtDataHash: common.Hash{21},
98+
ExtDataGasUsed: big.NewInt(22),
99+
BlockGasCost: big.NewInt(23),
100+
}
101+
extras.Header.Set(want, headerExtra)
102+
103+
assert.Equal(t, want, cpy)
104+
105+
// Mutate each non-value field to ensure they are not shared
106+
fieldsAreDeepCopied(t, h, cpy)
107+
fieldsAreDeepCopied(t, GetHeaderExtra(h), GetHeaderExtra(cpy))
108+
})
109+
}
110+
111+
func ptrTo[T any](x T) *T { return &x }
112+
113+
func allFieldsAreSet(t *testing.T, x any) {
114+
t.Helper()
115+
require.Equal(t, reflect.Ptr.String(), reflect.TypeOf(x).Kind().String(), "x must be a pointer")
116+
v := reflect.ValueOf(x).Elem()
117+
typ := v.Type()
118+
require.Equal(t, reflect.Struct.String(), typ.Kind().String())
119+
for i := 0; i < v.NumField(); i++ {
120+
field := v.Field(i)
121+
fieldName := typ.Field(i).Name
122+
fieldValue := field
123+
if unicode.IsLower(rune(fieldName[0])) { // unexported
124+
if field.Kind() == reflect.Ptr {
125+
require.Falsef(t, field.IsNil(), "field %q is nil", fieldName)
126+
}
127+
field = reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem() //nolint:gosec
128+
fieldValue = field
129+
}
130+
if field.Kind() == reflect.Pointer {
131+
require.NotNilf(t, field.Interface(), "field %q is nil", fieldName)
132+
fieldValue = field.Elem()
133+
}
134+
isSet := fieldValue.IsValid() && !fieldValue.IsZero()
135+
require.True(t, isSet, "field %q is not set", fieldName)
136+
}
137+
}
138+
139+
func fieldsAreDeepCopied(t *testing.T, original, cpy any) {
140+
t.Helper()
141+
require.Equal(t, reflect.Ptr.String(), reflect.TypeOf(original).Kind().String(), "original must be a pointer")
142+
require.Equal(t, reflect.Ptr.String(), reflect.TypeOf(cpy).Kind().String(), "cpy must be a pointer")
143+
144+
v := reflect.ValueOf(original).Elem()
145+
for i := 0; i < v.NumField(); i++ {
146+
field := v.Field(i)
147+
fieldName := v.Type().Field(i).Name
148+
isUnexported := unicode.IsLower(rune(fieldName[0]))
149+
if isUnexported {
150+
field = reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem() //nolint:gosec
151+
}
152+
var originalField any
153+
switch field.Kind() {
154+
case reflect.Array, reflect.Uint64: // values
155+
case reflect.Slice:
156+
originalField = field.Interface()
157+
switch originalField.(type) {
158+
case []byte:
159+
field.Set(reflect.Append(field, reflect.ValueOf(byte(1))))
160+
default:
161+
t.Fatalf("unexpected slice type %T for %q", originalField, fieldName)
162+
}
163+
originalField = field.Interface()
164+
case reflect.Pointer:
165+
originalField = field.Interface()
166+
switch ptr := originalField.(type) {
167+
case *big.Int:
168+
ptr.Add(ptr, big.NewInt(1))
169+
case *uint64:
170+
*ptr++
171+
case *common.Hash:
172+
ptr[0]++
173+
case *pseudo.Type:
174+
continue // extras are checked separately in another call of [fieldsAreDeepCopied]
175+
default:
176+
t.Fatalf("unexpected pointer type %T for %q", ptr, fieldName)
177+
}
178+
default:
179+
t.Fatalf("unexpected field kind %v for %q", field.Kind(), fieldName)
180+
}
181+
182+
cpyField := reflect.ValueOf(cpy).Elem().Field(i)
183+
if isUnexported {
184+
cpyField = reflect.NewAt(cpyField.Type(), unsafe.Pointer(cpyField.UnsafeAddr())).Elem() //nolint:gosec
185+
}
186+
assert.NotEqualf(t, originalField, cpyField.Interface(), "field %q", fieldName)
187+
}
188+
}

scripts/eth-allowed-packages.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"github.com/ava-labs/libevm/event"
2727
"github.com/ava-labs/libevm/libevm"
2828
"github.com/ava-labs/libevm/libevm/legacy"
29+
"github.com/ava-labs/libevm/libevm/pseudo"
2930
"github.com/ava-labs/libevm/libevm/stateconf"
3031
"github.com/ava-labs/libevm/log"
3132
"github.com/ava-labs/libevm/params"

0 commit comments

Comments
 (0)