Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
45 changes: 45 additions & 0 deletions common/hexutil/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ import (
"math/big"
"reflect"
"strconv"

"github.com/holiman/uint256"
)

var (
bytesT = reflect.TypeOf(Bytes(nil))
bigT = reflect.TypeOf((*Big)(nil))
uintT = reflect.TypeOf(Uint(0))
uint64T = reflect.TypeOf(Uint64(0))
u256T = reflect.TypeOf((*uint256.Int)(nil))
)

// Bytes marshals/unmarshals as a JSON string with 0x prefix.
Expand Down Expand Up @@ -225,6 +228,48 @@ func (b *Big) UnmarshalGraphQL(input interface{}) error {
return err
}

// U256 marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0".
type U256 uint256.Int

// MarshalText implements encoding.TextMarshaler
func (b U256) MarshalText() ([]byte, error) {
u256 := (*uint256.Int)(&b)
return []byte(u256.Hex()), nil
}

// UnmarshalJSON implements json.Unmarshaler.
func (b *U256) UnmarshalJSON(input []byte) error {
// The uint256.Int.UnmarshalJSON method accepts "dec", "0xhex"; we must be
// more strict, hence we check string and invoke SetFromHex directly.
if !isString(input) {
return errNonString(u256T)
}
// The hex decoder needs to accept empty string ("") as '0', which uint256.Int
// would reject.
if len(input) == 2 {
(*uint256.Int)(b).Clear()
return nil
}
err := (*uint256.Int)(b).SetFromHex(string(input[1 : len(input)-1]))
if err != nil {
return &json.UnmarshalTypeError{Value: err.Error(), Type: u256T}
}
return nil
}

// UnmarshalText implements encoding.TextUnmarshaler
func (b *U256) UnmarshalText(input []byte) error {
// The uint256.Int.UnmarshalText method accepts "dec", "0xhex"; we must be
// more strict, hence we check string and invoke SetFromHex directly.
return (*uint256.Int)(b).SetFromHex(string(input))
}

// String returns the hex encoding of b.
func (b *U256) String() string {
return (*uint256.Int)(b).Hex()
}

// Uint64 marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0".
type Uint64 uint64
Expand Down
75 changes: 75 additions & 0 deletions core/types/gen_authorization.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const (
AccessListTxType = 0x01
DynamicFeeTxType = 0x02
BlobTxType = 0x03
SetCodeTxType = 0x04

L1MessageTxType = 0x7E
)
Expand Down Expand Up @@ -204,6 +205,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
inner = new(BlobTx)
case L1MessageTxType:
inner = new(L1MessageTx)
case SetCodeTxType:
inner = new(SetCodeTx)
default:
return nil, ErrTxTypeNotSupported
}
Expand Down Expand Up @@ -466,6 +469,38 @@ func (tx *Transaction) WithoutBlobTxSidecar() *Transaction {
return cpy
}

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

// SetCodeAuthorities returns a list of unique authorities from the
// authorization list.
func (tx *Transaction) SetCodeAuthorities() []common.Address {
setcodetx, ok := tx.inner.(*SetCodeTx)
if !ok {
return nil
}
var (
marks = make(map[common.Address]bool)
auths = make([]common.Address, 0, len(setcodetx.AuthList))
)
for _, auth := range setcodetx.AuthList {
if addr, err := auth.Authority(); err == nil {
if marks[addr] {
continue
}
marks[addr] = true
auths = append(auths, addr)
}
}
return auths
}

// Hash returns the transaction hash.
func (tx *Transaction) Hash() common.Hash {
if hash := tx.hash.Load(); hash != nil {
Expand Down
128 changes: 112 additions & 16 deletions core/types/transaction_marshalling.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,23 @@ import (
type txJSON struct {
Type hexutil.Uint64 `json:"type"`

ChainID *hexutil.Big `json:"chainId,omitempty"`
Nonce *hexutil.Uint64 `json:"nonce"`
To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
Value *hexutil.Big `json:"value"`
Input *hexutil.Bytes `json:"input"`
AccessList *AccessList `json:"accessList,omitempty"`
BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
V *hexutil.Big `json:"v"`
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
ChainID *hexutil.Big `json:"chainId,omitempty"`
Nonce *hexutil.Uint64 `json:"nonce"`
To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
Value *hexutil.Big `json:"value"`
Input *hexutil.Bytes `json:"input"`
AccessList *AccessList `json:"accessList,omitempty"`
BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
AuthorizationList []SetCodeAuthorization `json:"authorizationList,omitempty"`
V *hexutil.Big `json:"v"`
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
Comment on lines +47 to +51
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider pointer‑vs‑slice semantics for authorizationList

AuthorizationList []SetCodeAuthorization is a value slice.
During (un)marshal we rely on nil vs. “empty slice” to distinguish “field omitted” from “present but empty”.
Because Go serialises an empty slice as [], clients can still send {"authorizationList":[]} – our later nil check (l.512‑514) will pass even though the list is empty.

If an empty list is invalid you should:

if dec.AuthorizationList == nil || len(dec.AuthorizationList) == 0 {
    return errors.New("authorizationList must contain at least one entry")
}


// Blob transaction sidecar encoding:
Blobs []kzg4844.Blob `json:"blobs,omitempty"`
Expand Down Expand Up @@ -166,6 +167,23 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
enc.Commitments = itx.Sidecar.Commitments
enc.Proofs = itx.Sidecar.Proofs
}

case *SetCodeTx:
enc.ChainID = (*hexutil.Big)(itx.ChainID.ToBig())
enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
enc.To = tx.To()
enc.Gas = (*hexutil.Uint64)(&itx.Gas)
enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap.ToBig())
enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap.ToBig())
enc.Value = (*hexutil.Big)(itx.Value.ToBig())
enc.Input = (*hexutil.Bytes)(&itx.Data)
enc.AccessList = &itx.AccessList
enc.AuthorizationList = itx.AuthList
enc.V = (*hexutil.Big)(itx.V.ToBig())
enc.R = (*hexutil.Big)(itx.R.ToBig())
enc.S = (*hexutil.Big)(itx.S.ToBig())
yparity := itx.V.Uint64()
enc.YParity = (*hexutil.Uint64)(&yparity)
}
return json.Marshal(&enc)
}
Expand Down Expand Up @@ -449,6 +467,84 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
}
itx.Sender = *dec.Sender

case SetCodeTxType:
var itx SetCodeTx
inner = &itx
if dec.ChainID == nil {
return errors.New("missing required field 'chainId' in transaction")
}
var overflow bool
itx.ChainID, overflow = uint256.FromBig(dec.ChainID.ToInt())
if overflow {
return errors.New("'chainId' value overflows uint256")
}
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.Input == nil {
return errors.New("missing required field 'input' in transaction")
}
itx.Data = *dec.Input
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

Comment on lines +470 to +516
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Sanity‑check each authorisation entry during decoding

We trust every element in authorizationList but never validate:

  • auth.ChainID matches the tx ChainID
  • auth.Address is unique (no duplicates)
  • auth.Authority() succeeds and signature is valid
  • auth.Nonce monotonicity / replay rules

Adding a tight loop here prevents malformed txs from reaching deeper consensus code.

for i, a := range dec.AuthorizationList {
    if a.ChainID.Cmp(itx.ChainID) != 0 {
        return fmt.Errorf("authorizationList[%d].chainId mismatch", i)
    }
    if _, err := a.Authority(); err != nil {
        return fmt.Errorf("authorizationList[%d] invalid signature: %w", i, err)
    }
    // TODO: duplicate / nonce checks as per consensus rules
}

// signature R
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
Loading