Skip to content

Commit bb148dd

Browse files
authored
core/types: support yParity field in JSON transactions (#27744)
This adds support for the "yParity" field in transaction objects returned by RPC APIs. We somehow forgot to add this field even though it has been in the spec for a long time.
1 parent 57cdbae commit bb148dd

File tree

3 files changed

+961
-58
lines changed

3 files changed

+961
-58
lines changed

core/types/transaction_marshalling.go

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,32 @@ type txJSON struct {
4545
V *hexutil.Big `json:"v"`
4646
R *hexutil.Big `json:"r"`
4747
S *hexutil.Big `json:"s"`
48+
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
4849

4950
// Only used for encoding:
5051
Hash common.Hash `json:"hash"`
5152
}
5253

54+
// yParityValue returns the YParity value from JSON. For backwards-compatibility reasons,
55+
// this can be given in the 'v' field or the 'yParity' field. If both exist, they must match.
56+
func (tx *txJSON) yParityValue() (*big.Int, error) {
57+
if tx.YParity != nil {
58+
val := uint64(*tx.YParity)
59+
if val != 0 && val != 1 {
60+
return nil, errors.New("'yParity' field must be 0 or 1")
61+
}
62+
bigval := new(big.Int).SetUint64(val)
63+
if tx.V != nil && tx.V.ToInt().Cmp(bigval) != 0 {
64+
return nil, errors.New("'v' and 'yParity' fields do not match")
65+
}
66+
return bigval, nil
67+
}
68+
if tx.V != nil {
69+
return tx.V.ToInt(), nil
70+
}
71+
return nil, errors.New("missing 'yParity' or 'v' field in transaction")
72+
}
73+
5374
// MarshalJSON marshals as JSON with a hash.
5475
func (tx *Transaction) MarshalJSON() ([]byte, error) {
5576
var enc txJSON
@@ -85,6 +106,8 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
85106
enc.V = (*hexutil.Big)(itx.V)
86107
enc.R = (*hexutil.Big)(itx.R)
87108
enc.S = (*hexutil.Big)(itx.S)
109+
yparity := itx.V.Uint64()
110+
enc.YParity = (*hexutil.Uint64)(&yparity)
88111

89112
case *DynamicFeeTx:
90113
enc.ChainID = (*hexutil.Big)(itx.ChainID)
@@ -99,6 +122,8 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
99122
enc.V = (*hexutil.Big)(itx.V)
100123
enc.R = (*hexutil.Big)(itx.R)
101124
enc.S = (*hexutil.Big)(itx.S)
125+
yparity := itx.V.Uint64()
126+
enc.YParity = (*hexutil.Uint64)(&yparity)
102127

103128
case *BlobTx:
104129
enc.ChainID = (*hexutil.Big)(itx.ChainID.ToBig())
@@ -115,14 +140,17 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
115140
enc.V = (*hexutil.Big)(itx.V.ToBig())
116141
enc.R = (*hexutil.Big)(itx.R.ToBig())
117142
enc.S = (*hexutil.Big)(itx.S.ToBig())
143+
yparity := itx.V.Uint64()
144+
enc.YParity = (*hexutil.Uint64)(&yparity)
118145
}
119146
return json.Marshal(&enc)
120147
}
121148

122149
// UnmarshalJSON unmarshals from JSON.
123150
func (tx *Transaction) UnmarshalJSON(input []byte) error {
124151
var dec txJSON
125-
if err := json.Unmarshal(input, &dec); err != nil {
152+
err := json.Unmarshal(input, &dec)
153+
if err != nil {
126154
return err
127155
}
128156

@@ -155,20 +183,23 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
155183
return errors.New("missing required field 'input' in transaction")
156184
}
157185
itx.Data = *dec.Input
158-
if dec.V == nil {
159-
return errors.New("missing required field 'v' in transaction")
160-
}
161-
itx.V = (*big.Int)(dec.V)
186+
187+
// signature R
162188
if dec.R == nil {
163189
return errors.New("missing required field 'r' in transaction")
164190
}
165191
itx.R = (*big.Int)(dec.R)
192+
// signature S
166193
if dec.S == nil {
167194
return errors.New("missing required field 's' in transaction")
168195
}
169196
itx.S = (*big.Int)(dec.S)
170-
withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
171-
if withSignature {
197+
// signature V
198+
if dec.V == nil {
199+
return errors.New("missing required field 'v' in transaction")
200+
}
201+
itx.V = (*big.Int)(dec.V)
202+
if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 {
172203
if err := sanityCheckSignature(itx.V, itx.R, itx.S, true); err != nil {
173204
return err
174205
}
@@ -204,23 +235,26 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
204235
return errors.New("missing required field 'input' in transaction")
205236
}
206237
itx.Data = *dec.Input
207-
if dec.V == nil {
208-
return errors.New("missing required field 'v' in transaction")
209-
}
210238
if dec.AccessList != nil {
211239
itx.AccessList = *dec.AccessList
212240
}
213-
itx.V = (*big.Int)(dec.V)
241+
242+
// signature R
214243
if dec.R == nil {
215244
return errors.New("missing required field 'r' in transaction")
216245
}
217246
itx.R = (*big.Int)(dec.R)
247+
// signature S
218248
if dec.S == nil {
219249
return errors.New("missing required field 's' in transaction")
220250
}
221251
itx.S = (*big.Int)(dec.S)
222-
withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
223-
if withSignature {
252+
// signature V
253+
itx.V, err = dec.yParityValue()
254+
if err != nil {
255+
return err
256+
}
257+
if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 {
224258
if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil {
225259
return err
226260
}
@@ -266,17 +300,23 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
266300
if dec.AccessList != nil {
267301
itx.AccessList = *dec.AccessList
268302
}
269-
itx.V = (*big.Int)(dec.V)
303+
304+
// signature R
270305
if dec.R == nil {
271306
return errors.New("missing required field 'r' in transaction")
272307
}
273308
itx.R = (*big.Int)(dec.R)
309+
// signature S
274310
if dec.S == nil {
275311
return errors.New("missing required field 's' in transaction")
276312
}
277313
itx.S = (*big.Int)(dec.S)
278-
withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
279-
if withSignature {
314+
// signature V
315+
itx.V, err = dec.yParityValue()
316+
if err != nil {
317+
return err
318+
}
319+
if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 {
280320
if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil {
281321
return err
282322
}
@@ -331,18 +371,35 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
331371
return errors.New("missing required field 'blobVersionedHashes' in transaction")
332372
}
333373
itx.BlobHashes = dec.BlobVersionedHashes
334-
itx.V = uint256.MustFromBig((*big.Int)(dec.V))
374+
375+
// signature R
376+
var ok bool
335377
if dec.R == nil {
336378
return errors.New("missing required field 'r' in transaction")
337379
}
338-
itx.R = uint256.MustFromBig((*big.Int)(dec.R))
380+
itx.R, ok = uint256.FromBig((*big.Int)(dec.R))
381+
if !ok {
382+
return errors.New("'r' value overflows uint256")
383+
}
384+
// signature S
339385
if dec.S == nil {
340386
return errors.New("missing required field 's' in transaction")
341387
}
342-
itx.S = uint256.MustFromBig((*big.Int)(dec.S))
343-
withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
344-
if withSignature {
345-
if err := sanityCheckSignature(itx.V.ToBig(), itx.R.ToBig(), itx.S.ToBig(), false); err != nil {
388+
itx.S, ok = uint256.FromBig((*big.Int)(dec.S))
389+
if !ok {
390+
return errors.New("'s' value overflows uint256")
391+
}
392+
// signature V
393+
vbig, err := dec.yParityValue()
394+
if err != nil {
395+
return err
396+
}
397+
itx.V, ok = uint256.FromBig(vbig)
398+
if !ok {
399+
return errors.New("'v' value overflows uint256")
400+
}
401+
if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 {
402+
if err := sanityCheckSignature(vbig, itx.R.ToBig(), itx.S.ToBig(), false); err != nil {
346403
return err
347404
}
348405
}

internal/ethapi/api.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,6 +1375,7 @@ type RPCTransaction struct {
13751375
V *hexutil.Big `json:"v"`
13761376
R *hexutil.Big `json:"r"`
13771377
S *hexutil.Big `json:"s"`
1378+
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
13781379
}
13791380

13801381
// newRPCTransaction returns a transaction that will serialize to the RPC
@@ -1402,20 +1403,27 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
14021403
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
14031404
result.TransactionIndex = (*hexutil.Uint64)(&index)
14041405
}
1406+
14051407
switch tx.Type() {
14061408
case types.LegacyTxType:
14071409
// if a legacy transaction has an EIP-155 chain id, include it explicitly
14081410
if id := tx.ChainId(); id.Sign() != 0 {
14091411
result.ChainID = (*hexutil.Big)(id)
14101412
}
1413+
14111414
case types.AccessListTxType:
14121415
al := tx.AccessList()
1416+
yparity := hexutil.Uint64(v.Sign())
14131417
result.Accesses = &al
14141418
result.ChainID = (*hexutil.Big)(tx.ChainId())
1419+
result.YParity = &yparity
1420+
14151421
case types.DynamicFeeTxType:
14161422
al := tx.AccessList()
1423+
yparity := hexutil.Uint64(v.Sign())
14171424
result.Accesses = &al
14181425
result.ChainID = (*hexutil.Big)(tx.ChainId())
1426+
result.YParity = &yparity
14191427
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
14201428
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
14211429
// if the transaction has been mined, compute the effective gas price

0 commit comments

Comments
 (0)