Skip to content

Commit 38bcec8

Browse files
committed
core/types: support yParity field in JSON transactions ethereum#27744
1 parent 25f07b1 commit 38bcec8

File tree

4 files changed

+285
-96
lines changed

4 files changed

+285
-96
lines changed

core/types/transaction.go

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

4245
skipNonceDestinationAddress = map[common.Address]bool{
4346
common.XDCXAddrBinary: true,

core/types/transaction_marshalling.go

Lines changed: 122 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -29,79 +29,104 @@ import (
2929
type txJSON struct {
3030
Type hexutil.Uint64 `json:"type"`
3131

32-
// Common transaction fields:
32+
ChainID *hexutil.Big `json:"chainId,omitempty"`
3333
Nonce *hexutil.Uint64 `json:"nonce"`
34+
To *common.Address `json:"to"`
35+
Gas *hexutil.Uint64 `json:"gas"`
3436
GasPrice *hexutil.Big `json:"gasPrice"`
3537
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
3638
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
37-
Gas *hexutil.Uint64 `json:"gas"`
3839
Value *hexutil.Big `json:"value"`
3940
Data *hexutil.Bytes `json:"input"`
41+
AccessList *AccessList `json:"accessList,omitempty"`
4042
V *hexutil.Big `json:"v"`
4143
R *hexutil.Big `json:"r"`
4244
S *hexutil.Big `json:"s"`
43-
To *common.Address `json:"to"`
44-
45-
// Access list transaction fields:
46-
ChainID *hexutil.Big `json:"chainId,omitempty"`
47-
AccessList *AccessList `json:"accessList,omitempty"`
45+
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
4846

4947
// Only used for encoding:
5048
Hash common.Hash `json:"hash"`
5149
}
5250

51+
// yParityValue returns the YParity value from JSON. For backwards-compatibility reasons,
52+
// this can be given in the 'v' field or the 'yParity' field. If both exist, they must match.
53+
func (tx *txJSON) yParityValue() (*big.Int, error) {
54+
if tx.YParity != nil {
55+
val := uint64(*tx.YParity)
56+
if val != 0 && val != 1 {
57+
return nil, errInvalidYParity
58+
}
59+
bigval := new(big.Int).SetUint64(val)
60+
if tx.V != nil && tx.V.ToInt().Cmp(bigval) != 0 {
61+
return nil, errVYParityMismatch
62+
}
63+
return bigval, nil
64+
}
65+
if tx.V != nil {
66+
return tx.V.ToInt(), nil
67+
}
68+
return nil, errVYParityMissing
69+
}
70+
5371
// MarshalJSON marshals as JSON with a hash.
54-
func (t *Transaction) MarshalJSON() ([]byte, error) {
72+
func (tx *Transaction) MarshalJSON() ([]byte, error) {
5573
var enc txJSON
5674
// These are set for all tx types.
57-
enc.Hash = t.Hash()
58-
enc.Type = hexutil.Uint64(t.Type())
75+
enc.Hash = tx.Hash()
76+
enc.Type = hexutil.Uint64(tx.Type())
5977

60-
// Other fields are set conditionally depending on tx type.
61-
switch tx := t.inner.(type) {
78+
// Other fields are set conditionally depending on itx type.
79+
switch itx := tx.inner.(type) {
6280
case *LegacyTx:
63-
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
64-
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
65-
enc.GasPrice = (*hexutil.Big)(tx.GasPrice)
66-
enc.Value = (*hexutil.Big)(tx.Value)
67-
enc.Data = (*hexutil.Bytes)(&tx.Data)
68-
enc.To = t.To()
69-
enc.V = (*hexutil.Big)(tx.V)
70-
enc.R = (*hexutil.Big)(tx.R)
71-
enc.S = (*hexutil.Big)(tx.S)
81+
enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
82+
enc.To = tx.To()
83+
enc.Gas = (*hexutil.Uint64)(&itx.Gas)
84+
enc.GasPrice = (*hexutil.Big)(itx.GasPrice)
85+
enc.Value = (*hexutil.Big)(itx.Value)
86+
enc.Data = (*hexutil.Bytes)(&itx.Data)
87+
enc.V = (*hexutil.Big)(itx.V)
88+
enc.R = (*hexutil.Big)(itx.R)
89+
enc.S = (*hexutil.Big)(itx.S)
90+
7291
case *AccessListTx:
73-
enc.ChainID = (*hexutil.Big)(tx.ChainID)
74-
enc.AccessList = &tx.AccessList
75-
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
76-
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
77-
enc.GasPrice = (*hexutil.Big)(tx.GasPrice)
78-
enc.Value = (*hexutil.Big)(tx.Value)
79-
enc.Data = (*hexutil.Bytes)(&tx.Data)
80-
enc.To = t.To()
81-
enc.V = (*hexutil.Big)(tx.V)
82-
enc.R = (*hexutil.Big)(tx.R)
83-
enc.S = (*hexutil.Big)(tx.S)
92+
enc.ChainID = (*hexutil.Big)(itx.ChainID)
93+
enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
94+
enc.To = tx.To()
95+
enc.Gas = (*hexutil.Uint64)(&itx.Gas)
96+
enc.GasPrice = (*hexutil.Big)(itx.GasPrice)
97+
enc.Value = (*hexutil.Big)(itx.Value)
98+
enc.Data = (*hexutil.Bytes)(&itx.Data)
99+
enc.AccessList = &itx.AccessList
100+
enc.V = (*hexutil.Big)(itx.V)
101+
enc.R = (*hexutil.Big)(itx.R)
102+
enc.S = (*hexutil.Big)(itx.S)
103+
yparity := itx.V.Uint64()
104+
enc.YParity = (*hexutil.Uint64)(&yparity)
105+
84106
case *DynamicFeeTx:
85-
enc.ChainID = (*hexutil.Big)(tx.ChainID)
86-
enc.AccessList = &tx.AccessList
87-
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
88-
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
89-
enc.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap)
90-
enc.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap)
91-
enc.Value = (*hexutil.Big)(tx.Value)
92-
enc.Data = (*hexutil.Bytes)(&tx.Data)
93-
enc.To = t.To()
94-
enc.V = (*hexutil.Big)(tx.V)
95-
enc.R = (*hexutil.Big)(tx.R)
96-
enc.S = (*hexutil.Big)(tx.S)
107+
enc.ChainID = (*hexutil.Big)(itx.ChainID)
108+
enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
109+
enc.To = tx.To()
110+
enc.Gas = (*hexutil.Uint64)(&itx.Gas)
111+
enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap)
112+
enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap)
113+
enc.Value = (*hexutil.Big)(itx.Value)
114+
enc.Data = (*hexutil.Bytes)(&itx.Data)
115+
enc.AccessList = &itx.AccessList
116+
enc.V = (*hexutil.Big)(itx.V)
117+
enc.R = (*hexutil.Big)(itx.R)
118+
enc.S = (*hexutil.Big)(itx.S)
119+
yparity := itx.V.Uint64()
120+
enc.YParity = (*hexutil.Uint64)(&yparity)
97121
}
98122
return json.Marshal(&enc)
99123
}
100124

101125
// UnmarshalJSON unmarshals from JSON.
102-
func (t *Transaction) UnmarshalJSON(input []byte) error {
126+
func (tx *Transaction) UnmarshalJSON(input []byte) error {
103127
var dec txJSON
104-
if err := json.Unmarshal(input, &dec); err != nil {
128+
err := json.Unmarshal(input, &dec)
129+
if err != nil {
105130
return err
106131
}
107132

@@ -111,21 +136,21 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
111136
case LegacyTxType:
112137
var itx LegacyTx
113138
inner = &itx
114-
if dec.To != nil {
115-
itx.To = dec.To
116-
}
117139
if dec.Nonce == nil {
118140
return errors.New("missing required field 'nonce' in transaction")
119141
}
120142
itx.Nonce = uint64(*dec.Nonce)
121-
if dec.GasPrice == nil {
122-
return errors.New("missing required field 'gasPrice' in transaction")
143+
if dec.To != nil {
144+
itx.To = dec.To
123145
}
124-
itx.GasPrice = (*big.Int)(dec.GasPrice)
125146
if dec.Gas == nil {
126147
return errors.New("missing required field 'gas' in transaction")
127148
}
128149
itx.Gas = uint64(*dec.Gas)
150+
if dec.GasPrice == nil {
151+
return errors.New("missing required field 'gasPrice' in transaction")
152+
}
153+
itx.GasPrice = (*big.Int)(dec.GasPrice)
129154
if dec.Value == nil {
130155
return errors.New("missing required field 'value' in transaction")
131156
}
@@ -134,20 +159,23 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
134159
return errors.New("missing required field 'input' in transaction")
135160
}
136161
itx.Data = *dec.Data
137-
if dec.V == nil {
138-
return errors.New("missing required field 'v' in transaction")
139-
}
140-
itx.V = (*big.Int)(dec.V)
162+
163+
// signature R
141164
if dec.R == nil {
142165
return errors.New("missing required field 'r' in transaction")
143166
}
144167
itx.R = (*big.Int)(dec.R)
168+
// signature S
145169
if dec.S == nil {
146170
return errors.New("missing required field 's' in transaction")
147171
}
148172
itx.S = (*big.Int)(dec.S)
149-
withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
150-
if withSignature {
173+
// signature V
174+
if dec.V == nil {
175+
return errors.New("missing required field 'v' in transaction")
176+
}
177+
itx.V = (*big.Int)(dec.V)
178+
if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 {
151179
if err := sanityCheckSignature(itx.V, itx.R, itx.S, true); err != nil {
152180
return err
153181
}
@@ -156,29 +184,25 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
156184
case AccessListTxType:
157185
var itx AccessListTx
158186
inner = &itx
159-
// Access list is optional for now.
160-
if dec.AccessList != nil {
161-
itx.AccessList = *dec.AccessList
162-
}
163187
if dec.ChainID == nil {
164188
return errors.New("missing required field 'chainId' in transaction")
165189
}
166190
itx.ChainID = (*big.Int)(dec.ChainID)
167-
if dec.To != nil {
168-
itx.To = dec.To
169-
}
170191
if dec.Nonce == nil {
171192
return errors.New("missing required field 'nonce' in transaction")
172193
}
173194
itx.Nonce = uint64(*dec.Nonce)
174-
if dec.GasPrice == nil {
175-
return errors.New("missing required field 'gasPrice' in transaction")
195+
if dec.To != nil {
196+
itx.To = dec.To
176197
}
177-
itx.GasPrice = (*big.Int)(dec.GasPrice)
178198
if dec.Gas == nil {
179199
return errors.New("missing required field 'gas' in transaction")
180200
}
181201
itx.Gas = uint64(*dec.Gas)
202+
if dec.GasPrice == nil {
203+
return errors.New("missing required field 'gasPrice' in transaction")
204+
}
205+
itx.GasPrice = (*big.Int)(dec.GasPrice)
182206
if dec.Value == nil {
183207
return errors.New("missing required field 'value' in transaction")
184208
}
@@ -187,20 +211,26 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
187211
return errors.New("missing required field 'input' in transaction")
188212
}
189213
itx.Data = *dec.Data
190-
if dec.V == nil {
191-
return errors.New("missing required field 'v' in transaction")
214+
if dec.AccessList != nil {
215+
itx.AccessList = *dec.AccessList
192216
}
193-
itx.V = (*big.Int)(dec.V)
217+
218+
// signature R
194219
if dec.R == nil {
195220
return errors.New("missing required field 'r' in transaction")
196221
}
197222
itx.R = (*big.Int)(dec.R)
223+
// signature S
198224
if dec.S == nil {
199225
return errors.New("missing required field 's' in transaction")
200226
}
201227
itx.S = (*big.Int)(dec.S)
202-
withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
203-
if withSignature {
228+
// signature V
229+
itx.V, err = dec.yParityValue()
230+
if err != nil {
231+
return err
232+
}
233+
if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 {
204234
if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil {
205235
return err
206236
}
@@ -209,21 +239,21 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
209239
case DynamicFeeTxType:
210240
var itx DynamicFeeTx
211241
inner = &itx
212-
// Access list is optional for now.
213-
if dec.AccessList != nil {
214-
itx.AccessList = *dec.AccessList
215-
}
216242
if dec.ChainID == nil {
217243
return errors.New("missing required field 'chainId' in transaction")
218244
}
219245
itx.ChainID = (*big.Int)(dec.ChainID)
220-
if dec.To != nil {
221-
itx.To = dec.To
222-
}
223246
if dec.Nonce == nil {
224247
return errors.New("missing required field 'nonce' in transaction")
225248
}
226249
itx.Nonce = uint64(*dec.Nonce)
250+
if dec.To != nil {
251+
itx.To = dec.To
252+
}
253+
if dec.Gas == nil {
254+
return errors.New("missing required field 'gas' for txdata")
255+
}
256+
itx.Gas = uint64(*dec.Gas)
227257
if dec.MaxPriorityFeePerGas == nil {
228258
return errors.New("missing required field 'maxPriorityFeePerGas' for txdata")
229259
}
@@ -232,10 +262,6 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
232262
return errors.New("missing required field 'maxFeePerGas' for txdata")
233263
}
234264
itx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas)
235-
if dec.Gas == nil {
236-
return errors.New("missing required field 'gas' for txdata")
237-
}
238-
itx.Gas = uint64(*dec.Gas)
239265
if dec.Value == nil {
240266
return errors.New("missing required field 'value' in transaction")
241267
}
@@ -244,20 +270,26 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
244270
return errors.New("missing required field 'input' in transaction")
245271
}
246272
itx.Data = *dec.Data
247-
if dec.V == nil {
248-
return errors.New("missing required field 'v' in transaction")
273+
if dec.AccessList != nil {
274+
itx.AccessList = *dec.AccessList
249275
}
250-
itx.V = (*big.Int)(dec.V)
276+
277+
// signature R
251278
if dec.R == nil {
252279
return errors.New("missing required field 'r' in transaction")
253280
}
254281
itx.R = (*big.Int)(dec.R)
282+
// signature S
255283
if dec.S == nil {
256284
return errors.New("missing required field 's' in transaction")
257285
}
258286
itx.S = (*big.Int)(dec.S)
259-
withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
260-
if withSignature {
287+
// signature V
288+
itx.V, err = dec.yParityValue()
289+
if err != nil {
290+
return err
291+
}
292+
if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 {
261293
if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil {
262294
return err
263295
}
@@ -268,7 +300,7 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
268300
}
269301

270302
// Now set the inner transaction.
271-
t.setDecoded(inner, 0)
303+
tx.setDecoded(inner, 0)
272304

273305
// TODO: check hash here?
274306
return nil

internal/ethapi/api.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,6 +1670,7 @@ type RPCTransaction struct {
16701670
V *hexutil.Big `json:"v"`
16711671
R *hexutil.Big `json:"r"`
16721672
S *hexutil.Big `json:"s"`
1673+
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
16731674
}
16741675

16751676
// newRPCTransaction returns a transaction that will serialize to the RPC
@@ -1697,15 +1698,21 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
16971698
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
16981699
result.TransactionIndex = (*hexutil.Uint64)(&index)
16991700
}
1701+
17001702
switch tx.Type() {
17011703
case types.AccessListTxType:
17021704
al := tx.AccessList()
1705+
yparity := hexutil.Uint64(v.Sign())
17031706
result.Accesses = &al
17041707
result.ChainID = (*hexutil.Big)(tx.ChainId())
1708+
result.YParity = &yparity
1709+
17051710
case types.DynamicFeeTxType:
17061711
al := tx.AccessList()
1712+
yparity := hexutil.Uint64(v.Sign())
17071713
result.Accesses = &al
17081714
result.ChainID = (*hexutil.Big)(tx.ChainId())
1715+
result.YParity = &yparity
17091716
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
17101717
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
17111718
// if the transaction has been mined, compute the effective gas price

0 commit comments

Comments
 (0)