Skip to content

Commit 347fecd

Browse files
authored
core/types: make 'v' optional for DynamicFeeTx and BlobTx (#28564)
This fixes an issue where transactions would not be accepted when they have only 'yParity' and not 'v'.
1 parent e9f59b5 commit 347fecd

File tree

3 files changed

+100
-9
lines changed

3 files changed

+100
-9
lines changed

core/types/transaction.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ var (
3737
ErrTxTypeNotSupported = errors.New("transaction type not supported")
3838
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
3939
errShortTypedTx = errors.New("typed transaction too short")
40+
errInvalidYParity = errors.New("'yParity' field must be 0 or 1")
41+
errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match")
42+
errVYParityMissing = errors.New("missing 'yParity' or 'v' field in transaction")
4043
)
4144

4245
// Transaction types.

core/types/transaction_marshalling.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,18 @@ func (tx *txJSON) yParityValue() (*big.Int, error) {
5757
if tx.YParity != nil {
5858
val := uint64(*tx.YParity)
5959
if val != 0 && val != 1 {
60-
return nil, errors.New("'yParity' field must be 0 or 1")
60+
return nil, errInvalidYParity
6161
}
6262
bigval := new(big.Int).SetUint64(val)
6363
if tx.V != nil && tx.V.ToInt().Cmp(bigval) != 0 {
64-
return nil, errors.New("'v' and 'yParity' fields do not match")
64+
return nil, errVYParityMismatch
6565
}
6666
return bigval, nil
6767
}
6868
if tx.V != nil {
6969
return tx.V.ToInt(), nil
7070
}
71-
return nil, errors.New("missing 'yParity' or 'v' field in transaction")
71+
return nil, errVYParityMissing
7272
}
7373

7474
// MarshalJSON marshals as JSON with a hash.
@@ -294,9 +294,6 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
294294
return errors.New("missing required field 'input' in transaction")
295295
}
296296
itx.Data = *dec.Input
297-
if dec.V == nil {
298-
return errors.New("missing required field 'v' in transaction")
299-
}
300297
if dec.AccessList != nil {
301298
itx.AccessList = *dec.AccessList
302299
}
@@ -361,9 +358,6 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
361358
return errors.New("missing required field 'input' in transaction")
362359
}
363360
itx.Data = *dec.Input
364-
if dec.V == nil {
365-
return errors.New("missing required field 'v' in transaction")
366-
}
367361
if dec.AccessList != nil {
368362
itx.AccessList = *dec.AccessList
369363
}

core/types/transaction_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,3 +451,97 @@ func TestTransactionSizes(t *testing.T) {
451451
}
452452
}
453453
}
454+
455+
func TestYParityJSONUnmarshalling(t *testing.T) {
456+
baseJson := map[string]interface{}{
457+
// type is filled in by the test
458+
"chainId": "0x7",
459+
"nonce": "0x0",
460+
"to": "0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425",
461+
"gas": "0x124f8",
462+
"gasPrice": "0x693d4ca8",
463+
"maxPriorityFeePerGas": "0x3b9aca00",
464+
"maxFeePerGas": "0x6fc23ac00",
465+
"maxFeePerBlobGas": "0x3b9aca00",
466+
"value": "0x0",
467+
"input": "0x",
468+
"accessList": []interface{}{},
469+
"blobVersionedHashes": []string{
470+
"0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014",
471+
},
472+
473+
// v and yParity are filled in by the test
474+
"r": "0x2a922afc784d07e98012da29f2f37cae1f73eda78aa8805d3df6ee5dbb41ec1",
475+
"s": "0x4f1f75ae6bcdf4970b4f305da1a15d8c5ddb21f555444beab77c9af2baab14",
476+
}
477+
478+
tests := []struct {
479+
name string
480+
v string
481+
yParity string
482+
wantErr error
483+
}{
484+
// Valid v and yParity
485+
{"valid v and yParity, 0x0", "0x0", "0x0", nil},
486+
{"valid v and yParity, 0x1", "0x1", "0x1", nil},
487+
488+
// Valid v, missing yParity
489+
{"valid v, missing yParity, 0x0", "0x0", "", nil},
490+
{"valid v, missing yParity, 0x1", "0x1", "", nil},
491+
492+
// Valid yParity, missing v
493+
{"valid yParity, missing v, 0x0", "", "0x0", nil},
494+
{"valid yParity, missing v, 0x1", "", "0x1", nil},
495+
496+
// Invalid yParity
497+
{"invalid yParity, 0x2", "", "0x2", errInvalidYParity},
498+
499+
// Conflicting v and yParity
500+
{"conflicting v and yParity", "0x1", "0x0", errVYParityMismatch},
501+
502+
// Missing v and yParity
503+
{"missing v and yParity", "", "", errVYParityMissing},
504+
}
505+
506+
// Run for all types that accept yParity
507+
t.Parallel()
508+
for _, txType := range []uint64{
509+
AccessListTxType,
510+
DynamicFeeTxType,
511+
BlobTxType,
512+
} {
513+
txType := txType
514+
for _, test := range tests {
515+
test := test
516+
t.Run(fmt.Sprintf("txType=%d: %s", txType, test.name), func(t *testing.T) {
517+
// Copy the base json
518+
testJson := make(map[string]interface{})
519+
for k, v := range baseJson {
520+
testJson[k] = v
521+
}
522+
523+
// Set v, yParity and type
524+
if test.v != "" {
525+
testJson["v"] = test.v
526+
}
527+
if test.yParity != "" {
528+
testJson["yParity"] = test.yParity
529+
}
530+
testJson["type"] = fmt.Sprintf("0x%x", txType)
531+
532+
// Marshal the JSON
533+
jsonBytes, err := json.Marshal(testJson)
534+
if err != nil {
535+
t.Fatal(err)
536+
}
537+
538+
// Unmarshal the tx
539+
var tx Transaction
540+
err = tx.UnmarshalJSON(jsonBytes)
541+
if err != test.wantErr {
542+
t.Fatalf("wrong error: got %v, want %v", err, test.wantErr)
543+
}
544+
})
545+
}
546+
}
547+
}

0 commit comments

Comments
 (0)