Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ var (
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
ErrSamePayerSenderSponsoredTx = errors.New("payer = sender in sponsored transaction")
errEmptyTypedTx = errors.New("empty typed transaction bytes")
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")
)

// Transaction types.
Expand Down Expand Up @@ -197,6 +200,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
inner = new(SponsoredTx)
case BlobTxType:
inner = new(BlobTx)
case SetCodeTxType:
inner = new(SetCodeTx)
default:
return nil, ErrTxTypeNotSupported
}
Expand Down Expand Up @@ -457,6 +462,15 @@ func (tx *Transaction) WithBlobTxSidecar(sideCar *BlobTxSidecar) *Transaction {
return cpy
}

// AuthList returns the authorizations list of the transaction.
func (tx *Transaction) AuthList() []Authorization {
setcodetx, ok := tx.inner.(*SetCodeTx)
if !ok {
return nil
}
return setcodetx.AuthList
}

// BlobGasFeeCapCmp compares the blob fee cap of two transactions.
func (tx *Transaction) BlobGasFeeCapCmp(other *Transaction) int {
return tx.BlobGasFeeCap().Cmp(other.BlobGasFeeCap())
Expand Down Expand Up @@ -597,6 +611,7 @@ type Message struct {
expiredTime uint64
blobGasFeeCap *big.Int
blobHashes []common.Hash
authList []Authorization
}

// Create a new message with payer is the same as from, expired time = 0
Expand All @@ -612,6 +627,7 @@ func NewMessage(
isFake bool,
blobFeeCap *big.Int,
blobHashes []common.Hash,
authList []Authorization,
) Message {
return Message{
from: from,
Expand All @@ -629,6 +645,7 @@ func NewMessage(
expiredTime: 0,
blobGasFeeCap: blobFeeCap,
blobHashes: blobHashes,
authList: authList,
}
}

Expand All @@ -648,6 +665,7 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
expiredTime: tx.ExpiredTime(),
blobGasFeeCap: tx.BlobGasFeeCap(),
blobHashes: tx.BlobHashes(),
authList: tx.AuthList(),
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
Expand Down Expand Up @@ -692,6 +710,7 @@ func (m Message) ExpiredTime() uint64 { return m.expiredTime }

func (m Message) BlobHashes() []common.Hash { return m.blobHashes }
func (m Message) BlobGasFeeCap() *big.Int { return m.blobGasFeeCap }
func (m Message) AuthList() []Authorization { return m.authList }

// copyAddressPtr copies an address.
func copyAddressPtr(a *common.Address) *common.Address {
Expand Down
103 changes: 102 additions & 1 deletion core/types/transaction_marshalling.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ type txJSON struct {
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
To *common.Address `json:"to"`
AuthorizationList []Authorization `json:"authorizationList,omitempty"`
YParity *hexutil.Uint64 `json:"yParity,omitempty"`

// Access list transaction fields:
ChainID *hexutil.Big `json:"chainId,omitempty"`
Expand All @@ -65,6 +67,26 @@ type txJSON struct {
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 (t *Transaction) MarshalJSON() ([]byte, error) {
var enc txJSON
Expand Down Expand Up @@ -117,6 +139,15 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
enc.Commitments = tx.Sidecar.Commitments
enc.Proofs = tx.Sidecar.Proofs
}
case *SetCodeTx:
enc.ChainID = (*hexutil.Big)(new(big.Int).SetUint64(tx.ChainID))
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
enc.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap.ToBig())
enc.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap.ToBig())
enc.AccessList = &tx.AccessList
enc.AuthorizationList = tx.AuthList
yparity := tx.V.Uint64()
enc.YParity = (*hexutil.Uint64)(&yparity)
}
return json.Marshal(&enc)
}
Expand Down Expand Up @@ -316,7 +347,7 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
return errors.New("missing required field 'to' in transaction")
}
itx.To = *to

if dec.MaxFeePerBlobGas == nil {
return errors.New("missing required field 'maxFeePerBlobGas' in transaction")
}
Expand All @@ -328,6 +359,76 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
if dec.AccessList != nil {
itx.AccessList = *dec.AccessList
}
case SetCodeTxType:
var itx SetCodeTx
inner = &itx
if dec.ChainID == nil {
return errors.New("missing required field 'chainId' in transaction")
}
itx.ChainID = dec.ChainID.ToInt().Uint64()
if dec.Nonce == nil {
return errors.New("missing required field 'nonce' in transaction")
}
itx.Nonce = uint64(*dec.Nonce)
if dec.To == nil {
return errors.New("missing required field 'to' in transaction")
}
itx.To = *dec.To
if dec.Gas == nil {
return errors.New("missing required field 'gas' for txdata")
}
itx.Gas = uint64(*dec.Gas)
if dec.MaxPriorityFeePerGas == nil {
return errors.New("missing required field 'maxPriorityFeePerGas' for txdata")
}
itx.GasTipCap = uint256.MustFromBig((*big.Int)(dec.MaxPriorityFeePerGas))
if dec.MaxFeePerGas == nil {
return errors.New("missing required field 'maxFeePerGas' for txdata")
}
itx.GasFeeCap = uint256.MustFromBig((*big.Int)(dec.MaxFeePerGas))
if dec.Value == nil {
return errors.New("missing required field 'value' in transaction")
}
itx.Value = uint256.MustFromBig((*big.Int)(dec.Value))
if dec.AccessList != nil {
itx.AccessList = *dec.AccessList
}
if dec.AuthorizationList == nil {
return errors.New("missing required field 'authorizationList' in transaction")
}
itx.AuthList = dec.AuthorizationList

// signature R
var overflow bool
if dec.R == nil {
return errors.New("missing required field 'r' in transaction")
}
itx.R, overflow = uint256.FromBig((*big.Int)(dec.R))
if overflow {
return errors.New("'r' value overflows uint256")
}
// signature S
if dec.S == nil {
return errors.New("missing required field 's' in transaction")
}
itx.S, overflow = uint256.FromBig((*big.Int)(dec.S))
if overflow {
return errors.New("'s' value overflows uint256")
}
// signature V
vbig, err := dec.yParityValue()
if err != nil {
return err
}
itx.V, overflow = uint256.FromBig(vbig)
if overflow {
return errors.New("'v' value overflows uint256")
}
if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 {
if err := sanityCheckSignature(vbig, itx.R.ToBig(), itx.S.ToBig(), false); err != nil {
return err
}
}
default:
return ErrTxTypeNotSupported
}
Expand Down
92 changes: 92 additions & 0 deletions core/types/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"encoding/json"
"errors"
"fmt"
"maps"
"math/big"
"reflect"
"testing"
Expand Down Expand Up @@ -375,6 +376,97 @@ func TestTransactionCoding(t *testing.T) {
}
}

func TestYParityJSONUnmarshalling(t *testing.T) {
baseJson := map[string]interface{}{
// type is filled in by the test
"chainId": "0x7",
"nonce": "0x0",
"to": "0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425",
"gas": "0x124f8",
"gasPrice": "0x693d4ca8",
"maxPriorityFeePerGas": "0x3b9aca00",
"maxFeePerGas": "0x6fc23ac00",
"maxFeePerBlobGas": "0x3b9aca00",
"value": "0x0",
"input": "0x",
"accessList": []interface{}{},
"blobVersionedHashes": []string{
"0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014",
},
"authorizationList": []string{},

// v and yParity are filled in by the test
"r": "0x2a922afc784d07e98012da29f2f37cae1f73eda78aa8805d3df6ee5dbb41ec1",
"s": "0x4f1f75ae6bcdf4970b4f305da1a15d8c5ddb21f555444beab77c9af2baab14",
}

tests := []struct {
name string
v string
yParity string
wantErr error
}{
// Valid v and yParity
{"valid v and yParity, 0x0", "0x0", "0x0", nil},
{"valid v and yParity, 0x1", "0x1", "0x1", nil},

// Valid v, missing yParity
{"valid v, missing yParity, 0x0", "0x0", "", nil},
{"valid v, missing yParity, 0x1", "0x1", "", nil},

// Valid yParity, missing v
{"valid yParity, missing v, 0x0", "", "0x0", nil},
{"valid yParity, missing v, 0x1", "", "0x1", nil},

// Invalid yParity
{"invalid yParity, 0x2", "", "0x2", errInvalidYParity},

// Conflicting v and yParity
{"conflicting v and yParity", "0x1", "0x0", errVYParityMismatch},

// Missing v and yParity
{"missing v and yParity", "", "", errVYParityMissing},
}

// Run for all types that accept yParity
t.Parallel()
for _, txType := range []uint64{
AccessListTxType,
DynamicFeeTxType,
BlobTxType,
SetCodeTxType,
} {
for _, test := range tests {
t.Run(fmt.Sprintf("txType=%d: %s", txType, test.name), func(t *testing.T) {
// Copy the base json
testJson := maps.Clone(baseJson)

// Set v, yParity and type
if test.v != "" {
testJson["v"] = test.v
}
if test.yParity != "" {
testJson["yParity"] = test.yParity
}
testJson["type"] = fmt.Sprintf("0x%x", txType)

// Marshal the JSON
jsonBytes, err := json.Marshal(testJson)
if err != nil {
t.Fatal(err)
}

// Unmarshal the tx
var tx Transaction
err = tx.UnmarshalJSON(jsonBytes)
if err != test.wantErr {
t.Fatalf("wrong error: got %v, want %v", err, test.wantErr)
}
})
}
}
}

func encodeDecodeJSON(tx *Transaction) (*Transaction, error) {
data, err := json.Marshal(tx)
if err != nil {
Expand Down