From c287f9eddd19c868a8f810ca164720d3d056550b Mon Sep 17 00:00:00 2001 From: Daniel Liu <139250065@qq.com> Date: Tue, 9 Dec 2025 13:57:37 +0800 Subject: [PATCH] core/types: support yParity field in JSON transactions #27744 (#1816) --- core/types/transaction.go | 3 ++ core/types/transaction_marshalling.go | 70 +++++++++++++++++++-------- internal/ethapi/api.go | 7 +++ internal/ethapi/api_test.go | 14 +++--- 4 files changed, 69 insertions(+), 25 deletions(-) diff --git a/core/types/transaction.go b/core/types/transaction.go index d774375fc7..c9e3c28beb 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -38,6 +38,9 @@ var ( ErrTxTypeNotSupported = errors.New("transaction type not supported") ErrGasFeeCapTooLow = errors.New("fee cap less than base fee") errShortTypedTx = errors.New("typed transaction too short") + errInvalidYParity = errors.New("'yParity' field must be 0 or 1") + errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match") + errVYParityMissing = errors.New("missing 'yParity' or 'v' field in transaction") skipNonceDestinationAddress = map[common.Address]bool{ common.XDCXAddrBinary: true, diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index 668dd906c5..b965ce36e1 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -42,11 +42,32 @@ type txJSON struct { V *hexutil.Big `json:"v"` R *hexutil.Big `json:"r"` S *hexutil.Big `json:"s"` + YParity *hexutil.Uint64 `json:"yParity,omitempty"` // Only used for encoding: Hash common.Hash `json:"hash"` } +// yParityValue returns the YParity value from JSON. For backwards-compatibility reasons, +// this can be given in the 'v' field or the 'yParity' field. If both exist, they must match. +func (tx *txJSON) yParityValue() (*big.Int, error) { + if tx.YParity != nil { + val := uint64(*tx.YParity) + if val != 0 && val != 1 { + return nil, errInvalidYParity + } + bigval := new(big.Int).SetUint64(val) + if tx.V != nil && tx.V.ToInt().Cmp(bigval) != 0 { + return nil, errVYParityMismatch + } + return bigval, nil + } + if tx.V != nil { + return tx.V.ToInt(), nil + } + return nil, errVYParityMissing +} + // MarshalJSON marshals as JSON with a hash. func (tx *Transaction) MarshalJSON() ([]byte, error) { var enc txJSON @@ -82,6 +103,8 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(itx.V) enc.R = (*hexutil.Big)(itx.R) enc.S = (*hexutil.Big)(itx.S) + yparity := itx.V.Uint64() + enc.YParity = (*hexutil.Uint64)(&yparity) case *DynamicFeeTx: enc.ChainID = (*hexutil.Big)(itx.ChainID) @@ -96,6 +119,8 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(itx.V) enc.R = (*hexutil.Big)(itx.R) enc.S = (*hexutil.Big)(itx.S) + yparity := itx.V.Uint64() + enc.YParity = (*hexutil.Uint64)(&yparity) } return json.Marshal(&enc) @@ -104,7 +129,8 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (tx *Transaction) UnmarshalJSON(input []byte) error { var dec txJSON - if err := json.Unmarshal(input, &dec); err != nil { + err := json.Unmarshal(input, &dec) + if err != nil { return err } @@ -138,20 +164,22 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { } itx.Data = *dec.Input - if dec.V == nil { - return errors.New("missing required field 'v' in transaction") - } - itx.V = (*big.Int)(dec.V) + // signature R if dec.R == nil { return errors.New("missing required field 'r' in transaction") } itx.R = (*big.Int)(dec.R) + // signature S if dec.S == nil { return errors.New("missing required field 's' in transaction") } itx.S = (*big.Int)(dec.S) - withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 - if withSignature { + // signature V + if dec.V == nil { + return errors.New("missing required field 'v' in transaction") + } + itx.V = (*big.Int)(dec.V) + if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 { if err := sanityCheckSignature(itx.V, itx.R, itx.S, true); err != nil { return err } @@ -191,20 +219,22 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { itx.AccessList = *dec.AccessList } - if dec.V == nil { - return errors.New("missing required field 'v' in transaction") - } - itx.V = (*big.Int)(dec.V) + // signature R if dec.R == nil { return errors.New("missing required field 'r' in transaction") } itx.R = (*big.Int)(dec.R) + // signature S if dec.S == nil { return errors.New("missing required field 's' in transaction") } itx.S = (*big.Int)(dec.S) - withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 - if withSignature { + // signature V + itx.V, err = dec.yParityValue() + if err != nil { + return err + } + if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 { if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { return err } @@ -248,20 +278,22 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { itx.AccessList = *dec.AccessList } - if dec.V == nil { - return errors.New("missing required field 'v' in transaction") - } - itx.V = (*big.Int)(dec.V) + // signature R if dec.R == nil { return errors.New("missing required field 'r' in transaction") } itx.R = (*big.Int)(dec.R) + // signature S if dec.S == nil { return errors.New("missing required field 's' in transaction") } itx.S = (*big.Int)(dec.S) - withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 - if withSignature { + // signature V + itx.V, err = dec.yParityValue() + if err != nil { + return err + } + if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 { if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { return err } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index a0dfac6320..02ea5866d9 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1670,6 +1670,7 @@ type RPCTransaction struct { V *hexutil.Big `json:"v"` R *hexutil.Big `json:"r"` S *hexutil.Big `json:"s"` + YParity *hexutil.Uint64 `json:"yParity,omitempty"` } // newRPCTransaction returns a transaction that will serialize to the RPC @@ -1697,6 +1698,7 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.TransactionIndex = (*hexutil.Uint64)(&index) } + switch tx.Type() { case types.LegacyTxType: // if a legacy transaction has an EIP-155 chain id, include it explicitly @@ -1705,12 +1707,17 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber } case types.AccessListTxType: al := tx.AccessList() + yparity := hexutil.Uint64(v.Sign()) result.Accesses = &al result.ChainID = (*hexutil.Big)(tx.ChainId()) + result.YParity = &yparity + case types.DynamicFeeTxType: al := tx.AccessList() + yparity := hexutil.Uint64(v.Sign()) result.Accesses = &al result.ChainID = (*hexutil.Big)(tx.ChainId()) + result.YParity = &yparity result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap()) result.GasTipCap = (*hexutil.Big)(tx.GasTipCap()) // if the transaction has been mined, compute the effective gas price diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 239342a106..ea61bf05ca 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -189,25 +189,26 @@ func TestRPCMarshalBlock(t *testing.T) { "value":"0x6f", "type":"0x1", "accessList":[], - "chainId": "0x539", "chainId":"0x539", "v":"0x0", "r":"0x0", - "s":"0x0" + "s":"0x0", + "yParity":"0x0" }, { "blockHash":"0x2cb4e4b5b5be5a2520377e87e8d7d2cf83fc0783fa6518d67b9606d3c5317b50", "blockNumber":"0x64", "from":"0x0000000000000000000000000000000000000000", "gas":"0x457", - "gasPrice":"0x2b67","hash":"0x9bba4c34e57c875ff57ac8d172805a26ae912006985395dc1bdf8f44140a7bf4", + "gasPrice":"0x2b67", + "hash":"0x9bba4c34e57c875ff57ac8d172805a26ae912006985395dc1bdf8f44140a7bf4", "input":"0x111111", "nonce":"0x2", "to":"0x0000000000000000000000000000000000000011", "transactionIndex":"0x1", "value":"0x6f", "type":"0x0", - "chainId": "0x7fffffffffffffee", + "chainId":"0x7fffffffffffffee", "v":"0x0", "r":"0x0", "s":"0x0" @@ -229,7 +230,8 @@ func TestRPCMarshalBlock(t *testing.T) { "chainId":"0x539", "v":"0x0", "r":"0x0", - "s":"0x0" + "s":"0x0", + "yParity":"0x0" }, { "blockHash":"0x2cb4e4b5b5be5a2520377e87e8d7d2cf83fc0783fa6518d67b9606d3c5317b50", @@ -244,7 +246,7 @@ func TestRPCMarshalBlock(t *testing.T) { "transactionIndex":"0x3", "value":"0x6f", "type":"0x0", - "chainId": "0x7fffffffffffffee", + "chainId":"0x7fffffffffffffee", "v":"0x0", "r":"0x0", "s":"0x0"