CORE-2543 Investigate geth fork

This commit is contained in:
Alex Karolis 2025-05-26 13:39:08 +12:00
parent 9fd3f8a0dd
commit c16b1a357b
No known key found for this signature in database
GPG key ID: 8529C2F0B10D0658
6 changed files with 2551 additions and 6352 deletions

View file

@ -734,7 +734,7 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
"to": msg.To,
}
if len(msg.Data) > 0 {
arg["input"] = hexutil.Bytes(msg.Data)
arg["data"] = hexutil.Bytes(msg.Data)
}
if msg.Value != nil {
arg["value"] = (*hexutil.Big)(msg.Value)

View file

@ -225,7 +225,7 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
"to": msg.To,
}
if len(msg.Data) > 0 {
arg["input"] = hexutil.Bytes(msg.Data)
arg["data"] = hexutil.Bytes(msg.Data)
}
if msg.Value != nil {
arg["value"] = (*hexutil.Big)(msg.Value)

View file

@ -250,31 +250,28 @@ func TestConvertAddressDataToSlice(t *testing.T) {
func TestTypedDataArrayValidate(t *testing.T) {
t.Parallel()
type testDataInput struct {
Name string `json:"name"`
Domain TypedDataDomain `json:"domain"`
PrimaryType string `json:"primaryType"`
Types Types `json:"types"`
Message TypedDataMessage `json:"data"`
Digest string `json:"digest"`
type TestDataInput struct {
Name string `json:"name"`
TypedData TypedData `json:"typedData"`
DomainHash string `json:"domainHash"`
MessageHash string `json:"messageHash"`
Digest string `json:"digest"`
}
fc, err := os.ReadFile("./testdata/typed-data.json")
require.NoError(t, err, "error reading test data file")
var tests []testDataInput
var tests []TestDataInput
err = json.Unmarshal(fc, &tests)
require.NoError(t, err, "error unmarshalling test data file contents")
for _, tc := range tests {
for _, tt := range tests {
tc := tt
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
td := TypedData{
Types: tc.Types,
PrimaryType: tc.PrimaryType,
Domain: tc.Domain,
Message: tc.Message,
}
td := tc.TypedData
domainSeparator, tErr := td.HashStruct("EIP712Domain", td.Domain.Map())
assert.NoError(t, tErr, "failed to hash domain separator: %v", tErr)
@ -282,10 +279,13 @@ func TestTypedDataArrayValidate(t *testing.T) {
messageHash, tErr := td.HashStruct(td.PrimaryType, td.Message)
assert.NoError(t, tErr, "failed to hash message: %v", tErr)
digest := crypto.Keccak256Hash(fmt.Appendf(nil, "%s%s%s", "\x19\x01", string(domainSeparator), string(messageHash)))
assert.Equal(t, tc.Digest, digest.String(), "digest doesn't not match")
digest := crypto.Keccak256Hash([]byte(fmt.Sprintf("%s%s%s", "\x19\x01", string(domainSeparator), string(messageHash))))
assert.NoError(t, td.validate(), "validation failed", tErr)
assert.Equal(t, tc.Digest, digest.String(), "digest doesn't not match")
assert.Equal(t, tc.DomainHash, domainSeparator.String(), "domain separator hashes do not match")
assert.Equal(t, tc.MessageHash, messageHash.String(), "message hashes do not match")
assert.NoError(t, td.validate(), "expected typed data to pass validation, got: %v", tErr)
})
}
}

File diff suppressed because it is too large Load diff

View file

@ -18,14 +18,12 @@ package apitypes
import (
"bytes"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"math/big"
"reflect"
"regexp"
"slices"
"sort"
"strconv"
"strings"
@ -36,8 +34,6 @@ import (
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/holiman/uint256"
)
var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Za-z](\w*)(\[\d*\])*$`)
@ -66,10 +62,10 @@ func (vs *ValidationMessages) Info(msg string) {
vs.Messages = append(vs.Messages, ValidationInfo{INFO, msg})
}
// GetWarnings returns an error with all messages of type WARN of above, or nil if no warnings were present
func (vs *ValidationMessages) GetWarnings() error {
// getWarnings returns an error with all messages of type WARN of above, or nil if no warnings were present
func (v *ValidationMessages) GetWarnings() error {
var messages []string
for _, msg := range vs.Messages {
for _, msg := range v.Messages {
if msg.Typ == WARN || msg.Typ == CRIT {
messages = append(messages, msg.Message)
}
@ -96,21 +92,12 @@ type SendTxArgs struct {
// We accept "data" and "input" for backwards-compatibility reasons.
// "input" is the newer name and should be preferred by clients.
// Issue detail: https://github.com/ethereum/go-ethereum/issues/15628
Data *hexutil.Bytes `json:"data,omitempty"`
Data *hexutil.Bytes `json:"data"`
Input *hexutil.Bytes `json:"input,omitempty"`
// For non-legacy transactions
AccessList *types.AccessList `json:"accessList,omitempty"`
ChainID *hexutil.Big `json:"chainId,omitempty"`
// For BlobTxType
BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
BlobHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
// For BlobTxType transactions with blob sidecar
Blobs []kzg4844.Blob `json:"blobs,omitempty"`
Commitments []kzg4844.Commitment `json:"commitments,omitempty"`
Proofs []kzg4844.Proof `json:"proofs,omitempty"`
}
func (args SendTxArgs) String() string {
@ -121,56 +108,24 @@ func (args SendTxArgs) String() string {
return err.Error()
}
// data retrieves the transaction calldata. Input field is preferred.
func (args *SendTxArgs) data() []byte {
if args.Input != nil {
return *args.Input
}
if args.Data != nil {
return *args.Data
}
return nil
}
// ToTransaction converts the arguments to a transaction.
func (args *SendTxArgs) ToTransaction() (*types.Transaction, error) {
func (args *SendTxArgs) ToTransaction() *types.Transaction {
// Add the To-field, if specified
var to *common.Address
if args.To != nil {
dstAddr := args.To.Address()
to = &dstAddr
}
if err := args.validateTxSidecar(); err != nil {
return nil, err
var input []byte
if args.Input != nil {
input = *args.Input
} else if args.Data != nil {
input = *args.Data
}
var data types.TxData
switch {
case args.BlobHashes != nil:
al := types.AccessList{}
if args.AccessList != nil {
al = *args.AccessList
}
data = &types.BlobTx{
To: *to,
ChainID: uint256.MustFromBig((*big.Int)(args.ChainID)),
Nonce: uint64(args.Nonce),
Gas: uint64(args.Gas),
GasFeeCap: uint256.MustFromBig((*big.Int)(args.MaxFeePerGas)),
GasTipCap: uint256.MustFromBig((*big.Int)(args.MaxPriorityFeePerGas)),
Value: uint256.MustFromBig((*big.Int)(&args.Value)),
Data: args.data(),
AccessList: al,
BlobHashes: args.BlobHashes,
BlobFeeCap: uint256.MustFromBig((*big.Int)(args.BlobFeeCap)),
}
if args.Blobs != nil {
data.(*types.BlobTx).Sidecar = &types.BlobTxSidecar{
Blobs: args.Blobs,
Commitments: args.Commitments,
Proofs: args.Proofs,
}
}
case args.MaxFeePerGas != nil:
al := types.AccessList{}
if args.AccessList != nil {
@ -184,7 +139,7 @@ func (args *SendTxArgs) ToTransaction() (*types.Transaction, error) {
GasFeeCap: (*big.Int)(args.MaxFeePerGas),
GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas),
Value: (*big.Int)(&args.Value),
Data: args.data(),
Data: input,
AccessList: al,
}
case args.AccessList != nil:
@ -195,7 +150,7 @@ func (args *SendTxArgs) ToTransaction() (*types.Transaction, error) {
Gas: uint64(args.Gas),
GasPrice: (*big.Int)(args.GasPrice),
Value: (*big.Int)(&args.Value),
Data: args.data(),
Data: input,
AccessList: *args.AccessList,
}
default:
@ -205,81 +160,10 @@ func (args *SendTxArgs) ToTransaction() (*types.Transaction, error) {
Gas: uint64(args.Gas),
GasPrice: (*big.Int)(args.GasPrice),
Value: (*big.Int)(&args.Value),
Data: args.data(),
Data: input,
}
}
return types.NewTx(data), nil
}
// validateTxSidecar validates blob data, if present
func (args *SendTxArgs) validateTxSidecar() error {
// No blobs, we're done.
if args.Blobs == nil {
return nil
}
n := len(args.Blobs)
// Assume user provides either only blobs (w/o hashes), or
// blobs together with commitments and proofs.
if args.Commitments == nil && args.Proofs != nil {
return errors.New(`blob proofs provided while commitments were not`)
} else if args.Commitments != nil && args.Proofs == nil {
return errors.New(`blob commitments provided while proofs were not`)
}
// len(blobs) == len(commitments) == len(proofs) == len(hashes)
if args.Commitments != nil && len(args.Commitments) != n {
return fmt.Errorf("number of blobs and commitments mismatch (have=%d, want=%d)", len(args.Commitments), n)
}
if args.Proofs != nil && len(args.Proofs) != n {
return fmt.Errorf("number of blobs and proofs mismatch (have=%d, want=%d)", len(args.Proofs), n)
}
if args.BlobHashes != nil && len(args.BlobHashes) != n {
return fmt.Errorf("number of blobs and hashes mismatch (have=%d, want=%d)", len(args.BlobHashes), n)
}
if args.Commitments == nil {
// Generate commitment and proof.
commitments := make([]kzg4844.Commitment, n)
proofs := make([]kzg4844.Proof, n)
for i, b := range args.Blobs {
c, err := kzg4844.BlobToCommitment(&b)
if err != nil {
return fmt.Errorf("blobs[%d]: error computing commitment: %v", i, err)
}
commitments[i] = c
p, err := kzg4844.ComputeBlobProof(&b, c)
if err != nil {
return fmt.Errorf("blobs[%d]: error computing proof: %v", i, err)
}
proofs[i] = p
}
args.Commitments = commitments
args.Proofs = proofs
} else {
for i, b := range args.Blobs {
if err := kzg4844.VerifyBlobProof(&b, args.Commitments[i], args.Proofs[i]); err != nil {
return fmt.Errorf("failed to verify blob proof: %v", err)
}
}
}
hashes := make([]common.Hash, n)
hasher := sha256.New()
for i, c := range args.Commitments {
hashes[i] = kzg4844.CalcBlobHashV1(hasher, &c)
}
if args.BlobHashes != nil {
for i, h := range hashes {
if h != args.BlobHashes[i] {
return fmt.Errorf("blob hash verification failed (have=%s, want=%s)", args.BlobHashes[i], h)
}
}
} else {
args.BlobHashes = hashes
}
return nil
return types.NewTx(data)
}
type SigFormat struct {
@ -325,11 +209,9 @@ type Type struct {
Type string `json:"type"`
}
// isArray returns true if the type is a fixed or variable sized array.
// This method may return false positives, in case the Type is not a valid
// expression, e.g. "fooo[[[[".
// isArray returns true if the type is a fixed or variable sized array
func (t *Type) isArray() bool {
return strings.IndexByte(t.Type, '[') > 0
return len(strings.Split(t.Type, "[")) > 1
}
// typeName returns the canonical name of the type. If the type is 'Person[]' or 'Person[2]', then
@ -387,8 +269,16 @@ func (typedData *TypedData) HashStruct(primaryType string, data TypedDataMessage
// Dependencies returns an array of custom types ordered by their hierarchical reference tree
func (typedData *TypedData) Dependencies(primaryType string, found []string) []string {
primaryType = strings.Split(primaryType, "[")[0]
includes := func(arr []string, str string) bool {
for _, obj := range arr {
if obj == str {
return true
}
}
return false
}
if slices.Contains(found, primaryType) {
if includes(found, primaryType) {
return found
}
if typedData.Types[primaryType] == nil {
@ -397,7 +287,7 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s
found = append(found, primaryType)
for _, field := range typedData.Types[primaryType] {
for _, dep := range typedData.Dependencies(field.Type, found) {
if !slices.Contains(found, dep) {
if !includes(found, dep) {
found = append(found, dep)
}
}
@ -464,9 +354,9 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter
encType := field.Type
encValue := data[field.Name]
if encType[len(encType)-1:] == "]" {
encodedData, err := typedData.encodeArrayValue(encValue, encType, depth)
if err != nil {
return nil, err
encodedData, encErr := typedData.encodeArrayValue(encValue, encType, depth)
if encErr != nil {
return nil, encErr
}
buffer.Write(encodedData)
} else if typedData.Types[field.Type] != nil {
@ -496,23 +386,14 @@ func (typedData *TypedData) encodeArrayValue(encValue interface{}, encType strin
return nil, dataMismatchError(encType, encValue)
}
arrayBuffer := new(bytes.Buffer)
arrayBuffer := bytes.Buffer{}
parsedType := strings.Split(encType, "[")[0]
for _, item := range arrayValue {
if reflect.TypeOf(item).Kind() == reflect.Slice ||
reflect.TypeOf(item).Kind() == reflect.Array {
var (
encodedData hexutil.Bytes
err error
)
if reflect.TypeOf(item).Elem().Kind() == reflect.Uint8 {
// the item type is bytes. encode the bytes array directly instead of recursing.
encodedData, err = typedData.EncodePrimitiveValue(parsedType, item, depth+1)
} else {
encodedData, err = typedData.encodeArrayValue(item, parsedType, depth+1)
}
if err != nil {
return nil, err
encodedData, encErr := typedData.encodeArrayValue(item, parsedType, depth+1)
if encErr != nil {
return nil, encErr
}
arrayBuffer.Write(encodedData)
} else {
@ -521,16 +402,16 @@ func (typedData *TypedData) encodeArrayValue(encValue interface{}, encType strin
if !ok {
return nil, dataMismatchError(parsedType, item)
}
encodedData, err := typedData.EncodeData(parsedType, mapValue, depth+1)
if err != nil {
return nil, err
encodedData, encErr := typedData.EncodeData(parsedType, mapValue, depth+1)
if encErr != nil {
return nil, encErr
}
digest := crypto.Keccak256(encodedData)
arrayBuffer.Write(digest)
} else {
bytesValue, err := typedData.EncodePrimitiveValue(parsedType, item, depth)
if err != nil {
return nil, err
bytesValue, encErr := typedData.EncodePrimitiveValue(parsedType, item, depth)
if encErr != nil {
return nil, encErr
}
arrayBuffer.Write(bytesValue)
}
@ -685,7 +566,7 @@ func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interf
if err != nil {
return nil, err
}
return math.U256Bytes(new(big.Int).Set(b)), nil
return math.U256Bytes(b), nil
}
return nil, fmt.Errorf("unrecognized type '%s'", encType)
}
@ -838,11 +719,11 @@ func formatPrimitiveValue(encType string, encValue interface{}) (string, error)
return "", fmt.Errorf("unhandled type %v", encType)
}
// validate checks if the types object is conformant to the specs
// Validate checks if the types object is conformant to the specs
func (t Types) validate() error {
for typeKey, typeArr := range t {
if len(typeKey) == 0 {
return errors.New("empty type key")
return fmt.Errorf("empty type key")
}
for i, typeObj := range typeArr {
if len(typeObj.Type) == 0 {
@ -869,36 +750,34 @@ func (t Types) validate() error {
return nil
}
var validPrimitiveTypes = map[string]struct{}{}
// build the set of valid primitive types
func init() {
// Types those are trivially valid
for _, t := range []string{
"address", "address[]", "bool", "bool[]", "string", "string[]",
"bytes", "bytes[]", "int", "int[]", "uint", "uint[]",
} {
validPrimitiveTypes[t] = struct{}{}
// Checks if the primitive value is valid
func isPrimitiveTypeValid(primitiveType string) bool {
primitiveType = strings.Split(primitiveType, "[")[0]
if primitiveType == "address" ||
primitiveType == "bool" ||
primitiveType == "string" ||
primitiveType == "bytes" ||
primitiveType == "int" ||
primitiveType == "uint" {
return true
}
// For 'bytesN', 'bytesN[]', we allow N from 1 to 32
for n := 1; n <= 32; n++ {
validPrimitiveTypes[fmt.Sprintf("bytes%d", n)] = struct{}{}
validPrimitiveTypes[fmt.Sprintf("bytes%d[]", n)] = struct{}{}
// e.g. 'bytes28' or 'bytes28[]'
if primitiveType == fmt.Sprintf("bytes%d", n) || primitiveType == fmt.Sprintf("bytes%d[]", n) {
return true
}
}
// For 'intN','intN[]' and 'uintN','uintN[]' we allow N in increments of 8, from 8 up to 256
for n := 8; n <= 256; n += 8 {
validPrimitiveTypes[fmt.Sprintf("int%d", n)] = struct{}{}
validPrimitiveTypes[fmt.Sprintf("int%d[]", n)] = struct{}{}
validPrimitiveTypes[fmt.Sprintf("uint%d", n)] = struct{}{}
validPrimitiveTypes[fmt.Sprintf("uint%d[]", n)] = struct{}{}
if primitiveType == fmt.Sprintf("int%d", n) || primitiveType == fmt.Sprintf("int%d[]", n) {
return true
}
if primitiveType == fmt.Sprintf("uint%d", n) || primitiveType == fmt.Sprintf("uint%d[]", n) {
return true
}
}
}
// Checks if the primitive value is valid
func isPrimitiveTypeValid(primitiveType string) bool {
input := strings.Split(primitiveType, "[")[0]
_, ok := validPrimitiveTypes[input]
return ok
return false
}
// validate checks if the given domain is valid, i.e. contains at least

View file

@ -16,16 +16,7 @@
package apitypes
import (
"crypto/sha256"
"encoding/json"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/holiman/uint256"
)
import "testing"
func TestIsPrimitive(t *testing.T) {
t.Parallel()
@ -50,99 +41,6 @@ func TestIsPrimitive(t *testing.T) {
}
}
func TestTxArgs(t *testing.T) {
for i, tc := range []struct {
data []byte
want common.Hash
wantType uint8
}{
{
data: []byte(`{"from":"0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425","accessList":[],"blobVersionedHashes":["0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014"],"chainId":"0x7","gas":"0x124f8","gasPrice":"0x693d4ca8","input":"0x","maxFeePerBlobGas":"0x3b9aca00","maxFeePerGas":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","nonce":"0x0","r":"0x2a922afc784d07e98012da29f2f37cae1f73eda78aa8805d3df6ee5dbb41ec1","s":"0x4f1f75ae6bcdf4970b4f305da1a15d8c5ddb21f555444beab77c9af2baab14","to":"0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425","type":"0x1","v":"0x0","value":"0x0","yParity":"0x0"}`),
want: common.HexToHash("0x7d53234acc11ac5b5948632c901a944694e228795782f511887d36fd76ff15c4"),
wantType: types.BlobTxType,
},
{
// on input, we don't read the type, but infer the type from the arguments present
data: []byte(`{"from":"0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425","accessList":[],"chainId":"0x7","gas":"0x124f8","gasPrice":"0x693d4ca8","input":"0x","maxFeePerBlobGas":"0x3b9aca00","maxFeePerGas":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","nonce":"0x0","r":"0x2a922afc784d07e98012da29f2f37cae1f73eda78aa8805d3df6ee5dbb41ec1","s":"0x4f1f75ae6bcdf4970b4f305da1a15d8c5ddb21f555444beab77c9af2baab14","to":"0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425","type":"0x12","v":"0x0","value":"0x0","yParity":"0x0"}`),
want: common.HexToHash("0x7919e2b0b9b543cb87a137b6ff66491ec7ae937cb88d3c29db4d9b28073dce53"),
wantType: types.DynamicFeeTxType,
},
} {
var txArgs SendTxArgs
if err := json.Unmarshal(tc.data, &txArgs); err != nil {
t.Fatal(err)
}
tx, err := txArgs.ToTransaction()
if err != nil {
t.Fatal(err)
}
if have := tx.Type(); have != tc.wantType {
t.Errorf("test %d, have type %d, want type %d", i, have, tc.wantType)
}
if have := tx.Hash(); have != tc.want {
t.Errorf("test %d: have %v, want %v", i, have, tc.want)
}
d2, err := json.Marshal(txArgs)
if err != nil {
t.Fatal(err)
}
var txArgs2 SendTxArgs
if err := json.Unmarshal(d2, &txArgs2); err != nil {
t.Fatal(err)
}
tx1, _ := txArgs.ToTransaction()
tx2, _ := txArgs2.ToTransaction()
if have, want := tx1.Hash(), tx2.Hash(); have != want {
t.Errorf("test %d: have %v, want %v", i, have, want)
}
}
/*
End to end testing:
$ go run ./cmd/clef --advanced --suppress-bootwarn
$ go run ./cmd/geth --nodiscover --maxpeers 0 --signer /home/user/.clef/clef.ipc console
> tx={"from":"0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425","to":"0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425","gas":"0x124f8","maxFeePerGas":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","nonce":"0x0","input":"0x","accessList":[],"maxFeePerBlobGas":"0x3b9aca00","blobVersionedHashes":["0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014"]}
> eth.signTransaction(tx)
*/
}
func TestBlobTxs(t *testing.T) {
blob := kzg4844.Blob{0x1}
commitment, err := kzg4844.BlobToCommitment(&blob)
if err != nil {
t.Fatal(err)
}
proof, err := kzg4844.ComputeBlobProof(&blob, commitment)
if err != nil {
t.Fatal(err)
}
hash := kzg4844.CalcBlobHashV1(sha256.New(), &commitment)
b := &types.BlobTx{
ChainID: uint256.NewInt(6),
Nonce: 8,
GasTipCap: uint256.NewInt(500),
GasFeeCap: uint256.NewInt(600),
Gas: 21000,
BlobFeeCap: uint256.NewInt(700),
BlobHashes: []common.Hash{hash},
Value: uint256.NewInt(100),
Sidecar: &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{blob},
Commitments: []kzg4844.Commitment{commitment},
Proofs: []kzg4844.Proof{proof},
},
}
tx := types.NewTx(b)
data, err := json.Marshal(tx)
if err != nil {
t.Fatal(err)
}
t.Logf("tx %v", string(data))
}
func TestType_IsArray(t *testing.T) {
t.Parallel()
// Expected positives
@ -229,7 +127,7 @@ func TestType_TypeName(t *testing.T) {
},
} {
if tc.Input.typeName() != tc.Expected {
t.Errorf("test %d: expected typeName value of '%v' but got '%v'", i, tc.Expected, tc.Input)
t.Errorf("test %d: expected typeName value of '%v' to be '%v'", i, tc.Input, tc.Expected)
}
}
}