From 5e413e4ab7f18d35696b95f142d4ffb65c7c9ff1 Mon Sep 17 00:00:00 2001 From: Quentin Mc Gaw Date: Thu, 6 Feb 2025 14:29:45 +0100 Subject: [PATCH] chore(core/types): add `Test_CopyHeader` test --- core/types/block_test.go | 141 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/core/types/block_test.go b/core/types/block_test.go index 1f61be89a5..d6c79ae3f8 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -22,6 +22,8 @@ import ( "math/big" "reflect" "testing" + "unicode" + "unsafe" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -29,6 +31,8 @@ import ( "github.com/ethereum/go-ethereum/internal/blocktest" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // from bcValidBlockTest.json, "SimpleTx" @@ -318,3 +322,140 @@ func TestRlpDecodeParentHash(t *testing.T) { } } } + +func TestCopyHeader(t *testing.T) { + t.Parallel() + + t.Run("empty_header", func(t *testing.T) { + t.Parallel() + + empty := Header{} + cpy := CopyHeader(&empty) + + want := &Header{ + Difficulty: new(big.Int), + Number: new(big.Int), + } + assert.Equal(t, want, cpy) + }) + + t.Run("filled_header", func(t *testing.T) { + t.Parallel() + + h := Header{ + ParentHash: common.Hash{1}, + UncleHash: common.Hash{2}, + Coinbase: common.Address{3}, + Root: common.Hash{4}, + TxHash: common.Hash{5}, + ReceiptHash: common.Hash{6}, + Bloom: Bloom{7}, + Difficulty: big.NewInt(8), + Number: big.NewInt(9), + GasLimit: 10, + GasUsed: 11, + Time: 12, + Extra: []byte{13}, + MixDigest: common.Hash{14}, + Nonce: BlockNonce{15}, + BaseFee: big.NewInt(16), + WithdrawalsHash: &common.Hash{17}, + BlobGasUsed: ptrTo(uint64(18)), + ExcessBlobGas: ptrTo(uint64(19)), + ParentBeaconRoot: &common.Hash{20}, + RequestsHash: &common.Hash{21}, + } + + allFieldsAreSet(t, h) + + cpy := CopyHeader(&h) + + want := &Header{ + ParentHash: common.Hash{1}, + UncleHash: common.Hash{2}, + Coinbase: common.Address{3}, + Root: common.Hash{4}, + TxHash: common.Hash{5}, + ReceiptHash: common.Hash{6}, + Bloom: Bloom{7}, + Difficulty: big.NewInt(8), + Number: big.NewInt(9), + GasLimit: 10, + GasUsed: 11, + Time: 12, + Extra: []byte{13}, + MixDigest: common.Hash{14}, + Nonce: BlockNonce{15}, + BaseFee: big.NewInt(16), + WithdrawalsHash: &common.Hash{17}, + BlobGasUsed: ptrTo(uint64(18)), + ExcessBlobGas: ptrTo(uint64(19)), + ParentBeaconRoot: &common.Hash{20}, + RequestsHash: &common.Hash{21}, + } + assert.Equal(t, want, cpy) + + // Mutate each non-value field to ensure they are not shared + v := reflect.ValueOf(&h).Elem() + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + fieldName := v.Type().Field(i).Name + if unicode.IsLower(rune(fieldName[0])) { + field = reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem() + } + var originalField any + switch field.Kind() { + case reflect.Array, reflect.Uint64: // values + case reflect.Slice: + originalField = field.Interface() + switch originalField.(type) { + case []byte: + field.Set(reflect.Append(field, reflect.ValueOf(byte(1)))) + default: + t.Fatalf("unexpected slice type %T for %q", originalField, fieldName) + } + originalField = field.Interface() + case reflect.Pointer: + originalField = field.Interface() + switch ptr := originalField.(type) { + case *big.Int: + ptr.Add(ptr, big.NewInt(1)) + case *uint64: + *ptr++ + case *common.Hash: + ptr[0]++ + default: + t.Fatalf("unexpected pointer type %T for %q", ptr, fieldName) + } + default: + t.Fatalf("unexpected field kind %v for %q", field.Kind(), fieldName) + } + cpyField := reflect.ValueOf(*cpy).Field(i).Interface() + assert.NotEqualf(t, originalField, cpyField, "field %q", fieldName) + } + }) +} + +func ptrTo[T any](x T) *T { return &x } + +func allFieldsAreSet(t *testing.T, x any) { + v := reflect.ValueOf(x) + typ := v.Type() + require.Equal(t, reflect.Struct, typ.Kind()) + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + fieldName := typ.Field(i).Name + fieldValue := field + if unicode.IsLower(rune(fieldName[0])) { // unexported + require.Falsef(t, field.IsNil(), "field %q is nil", fieldName) + field = reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem() + fieldValue = field + } + if field.Kind() == reflect.Pointer { + require.NotNilf(t, field.Interface(), "field %q is nil", fieldName) + fieldValue = field.Elem() + } + isSet := fieldValue.IsValid() && !fieldValue.IsZero() + require.True(t, isSet, "field %q is not set", fieldName) + } +}