Changes in transaction structure after EIP1559

Transaction

go-ethereum.go/core/types/transaction.go

在该文件中对交易的定义如下:

定义了三种交易类型:LegacyTxTypeAccessListTxTypeDynamicFeeTxType

同时对Transaction.inner 实现了一个TxData的接口,用于兼容以上的交易类型


// Transaction types.
const (
    LegacyTxType = iota
    AccessListTxType
    DynamicFeeTxType
)

// Transaction is an Ethereum transaction.
type Transaction struct {
    inner TxData    // Consensus contents of a transaction
    time  time.Time // Time first seen locally (spam avoidance)

    // caches
    hash atomic.Value
    size atomic.Value
    from atomic.Value
}

// NewTx creates a new transaction.
func NewTx(inner TxData) *Transaction {
    tx := new(Transaction)
    tx.setDecoded(inner.copy(), 0)
    return tx
}

// TxData is the underlying data of a transaction.
//
// This is implemented by DynamicFeeTx, LegacyTx and AccessListTx.
type TxData interface {
    txType() byte // returns the type ID
    copy() TxData // creates a deep copy and initializes all fields

    chainID() *big.Int
    accessList() AccessList
    data() []byte
    gas() uint64
    gasPrice() *big.Int
    gasTipCap() *big.Int
    gasFeeCap() *big.Int
    value() *big.Int
    nonce() uint64
    to() *common.Address

    rawSignatureValues() (v, r, s *big.Int)
    setSignatureValues(chainID, v, r, s *big.Int)
}

LeagcyTxType

LegacyTxType是以太坊最早的交易类型,其定义如下:

// LegacyTx is the transaction data of regular Ethereum transactions.
type LegacyTx struct {
   Nonce    uint64          // nonce of sender account
   GasPrice *big.Int        // wei per gas
   Gas      uint64          // gas limit
   To       *common.Address `rlp:"nil"` // nil means contract creation
   Value    *big.Int        // wei amount
   Data     []byte          // contract invocation input data
   V, R, S  *big.Int        // signature values
}

// NewTransaction creates an unsigned legacy transaction.
// Deprecated: use NewTx instead.
func NewTransaction(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction {
   return NewTx(&LegacyTx{
      Nonce:    nonce,
      To:       &to,
      Value:    amount,
      Gas:      gasLimit,
      GasPrice: gasPrice,
      Data:     data,
   })
}

// NewContractCreation creates an unsigned legacy transaction.
// Deprecated: use NewTx instead.
func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction {
   return NewTx(&LegacyTx{
      Nonce:    nonce,
      Value:    amount,
      Gas:      gasLimit,
      GasPrice: gasPrice,
      Data:     data,
   })
}

// accessors for innerTx.
func (tx *LegacyTx) txType() byte           { return LegacyTxType }
func (tx *LegacyTx) chainID() *big.Int      { return deriveChainId(tx.V) }
func (tx *LegacyTx) accessList() AccessList { return nil }
func (tx *LegacyTx) data() []byte           { return tx.Data }
func (tx *LegacyTx) gas() uint64            { return tx.Gas }
func (tx *LegacyTx) gasPrice() *big.Int     { return tx.GasPrice }
func (tx *LegacyTx) gasTipCap() *big.Int    { return tx.GasPrice }
func (tx *LegacyTx) gasFeeCap() *big.Int    { return tx.GasPrice }
func (tx *LegacyTx) value() *big.Int        { return tx.Value }
func (tx *LegacyTx) nonce() uint64          { return tx.Nonce }
func (tx *LegacyTx) to() *common.Address    { return tx.To }

func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) {
   return tx.V, tx.R, tx.S
}

func (tx *LegacyTx) setSignatureValues(chainID, v, r, s *big.Int) {
   tx.V, tx.R, tx.S = v, r, s
}

我们可以发现,它的这三个函数比较特殊:

func (tx *LegacyTx) accessList() AccessList { return nil }
func (tx *LegacyTx) gasPrice() *big.Int     { return tx.GasPrice }
func (tx *LegacyTx) gasTipCap() *big.Int    { return tx.GasPrice }
func (tx *LegacyTx) gasFeeCap() *big.Int    { return tx.GasPrice }

DynamicFeeTxType

dynamicFeeTxType 是由EIP1559定义的:

Format

We introduce a new EIP-2718 transaction type, with the format 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s]).

Specification

块有效性操作在下面的参考实现中定义。(GASPRICE0x3a返回下面effective_gas_price参考实现中定义的。

从2开始FORK_BLOCK_NUMBER,引入了一个新的EIP-2718事务TransactionType

新交易的特殊成本——是从EIP2930继承而来的21000 + 16 * non-zero calldata bytes + 4 * zero calldata bytes + 1900 * access list storage key count + 2400 * access list address count

此交易的EIP-2718 TransactionPayloadrlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s])

此交易的signature_y_parity, signature_r, signature_s元素代表 secp256k1 签名keccak256(0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list]))

此交易的EIP-2718 ReceiptPayloadrlp([status, cumulative_transaction_gas_used, logs_bloom, logs])

实际支付的gasPrice

其实际执行过程的gasPrice = min(baseFee + GasTipCap , GasFeeCap)

type DynamicFeeTx struct {
    ChainID    *big.Int
    Nonce      uint64
    GasTipCap  *big.Int // a.k.a. maxPriorityFeePerGas
    GasFeeCap  *big.Int // a.k.a. maxFeePerGas
    Gas        uint64
    To         *common.Address `rlp:"nil"` // nil means contract creation
    Value      *big.Int
    Data       []byte
    AccessList AccessList

    // Signature values
    V *big.Int `json:"v" gencodec:"required"`
    R *big.Int `json:"r" gencodec:"required"`
    S *big.Int `json:"s" gencodec:"required"`
}

// accessors for innerTx.
func (tx *DynamicFeeTx) txType() byte           { return DynamicFeeTxType }
func (tx *DynamicFeeTx) chainID() *big.Int      { return tx.ChainID }
func (tx *DynamicFeeTx) accessList() AccessList { return tx.AccessList }
func (tx *DynamicFeeTx) data() []byte           { return tx.Data }
func (tx *DynamicFeeTx) gas() uint64            { return tx.Gas }
func (tx *DynamicFeeTx) gasFeeCap() *big.Int    { return tx.GasFeeCap }
func (tx *DynamicFeeTx) gasTipCap() *big.Int    { return tx.GasTipCap }
func (tx *DynamicFeeTx) gasPrice() *big.Int     { return tx.GasFeeCap }
func (tx *DynamicFeeTx) value() *big.Int        { return tx.Value }
func (tx *DynamicFeeTx) nonce() uint64          { return tx.Nonce }
func (tx *DynamicFeeTx) to() *common.Address    { return tx.To }

func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) {
    return tx.V, tx.R, tx.S
}

func (tx *DynamicFeeTx) setSignatureValues(chainID, v, r, s *big.Int) {
    tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
}

交易Encode与Decode

Encode

transaction提供了两种编码方式EncodeRLP()和MarshalBinary()

EncodeRLP()流程如下:

  • 先检查tx.type是否为LeagcyTxType

    • 如果是直接进行 rlp.Encode(w, tx.inner)

    • 如果不是继续往下执行

  • 执行tx.encodeTyped(buf)(实际上是对AccessListTxType、DynamicFeeTxType的处理)

    • 执行结果 result_format = 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s])

  • RLP编码上一步的结果rlpEncode(result_format)

// EncodeRLP implements rlp.Encoder
func (tx *Transaction) EncodeRLP(w io.Writer) error {
    if tx.Type() == LegacyTxType {
        return rlp.Encode(w, tx.inner)
    }
    // It's an EIP-2718 typed TX envelope.
    buf := encodeBufferPool.Get().(*bytes.Buffer)
    defer encodeBufferPool.Put(buf)
    buf.Reset()
    if err := tx.encodeTyped(buf); err != nil {
        return err
    }
    return rlp.Encode(w, buf.Bytes())
}

// encodeTyped writes the canonical encoding of a typed transaction to w.
func (tx *Transaction) encodeTyped(w *bytes.Buffer) error {
    w.WriteByte(tx.Type())
    return rlp.Encode(w, tx.inner)
}

MarshalBinary()流程如下:

  • 先检查tx.type是否为LeagcyTxType

    • return rlp.EncodeToBytes(w, tx.inner)

    • 如果不是继续往下执行

  • 执行tx.encodeTyped(buf)(实际上是对AccessListTxType、DynamicFeeTxType的处理)

    • return []byte(0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s]))

// MarshalBinary returns the canonical encoding of the transaction.
// For legacy transactions, it returns the RLP encoding. For EIP-2718 typed
// transactions, it returns the type and payload.
func (tx *Transaction) MarshalBinary() ([]byte, error) {
   if tx.Type() == LegacyTxType {
      return rlp.EncodeToBytes(tx.inner)
   }
   var buf bytes.Buffer
   err := tx.encodeTyped(&buf)
   return buf.Bytes(), err
}

Decode

decode函数也有DecodeRLP(s *rlp.Stream)UnmarshalBinary(b []byte)

// DecodeRLP implements rlp.Decoder
func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
    kind, size, err := s.Kind()
    switch {
    case err != nil:
        return err
    case kind == rlp.List:
        // It's a legacy transaction.
        var inner LegacyTx
        err := s.Decode(&inner)
        if err == nil {
            tx.setDecoded(&inner, int(rlp.ListSize(size)))
        }
        return err
    case kind == rlp.String:
        // It's an EIP-2718 typed TX envelope.
        var b []byte
        if b, err = s.Bytes(); err != nil {
            return err
        }
        inner, err := tx.decodeTyped(b)
        if err == nil {
            tx.setDecoded(inner, len(b))
        }
        return err
    default:
        return rlp.ErrExpectedList
    }
}

// UnmarshalBinary decodes the canonical encoding of transactions.
// It supports legacy RLP transactions and EIP2718 typed transactions.
func (tx *Transaction) UnmarshalBinary(b []byte) error {
    if len(b) > 0 && b[0] > 0x7f {
        // It's a legacy transaction.
        var data LegacyTx
        err := rlp.DecodeBytes(b, &data)
        if err != nil {
            return err
        }
        tx.setDecoded(&data, len(b))
        return nil
    }
    // It's an EIP2718 typed transaction envelope.
    inner, err := tx.decodeTyped(b)
    if err != nil {
        return err
    }
    tx.setDecoded(inner, len(b))
    return nil
}

// decodeTyped decodes a typed transaction from the canonical format.
func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
    if len(b) == 0 {
        return nil, errEmptyTypedTx
    }
    switch b[0] {
    case AccessListTxType:
        var inner AccessListTx
        err := rlp.DecodeBytes(b[1:], &inner)
        return &inner, err
    case DynamicFeeTxType:
        var inner DynamicFeeTx
        err := rlp.DecodeBytes(b[1:], &inner)
        return &inner, err
    default:
        return nil, ErrTxTypeNotSupported
    }
}

// setDecoded sets the inner transaction and size after decoding.
func (tx *Transaction) setDecoded(inner TxData, size int) {
    tx.inner = inner
    tx.time = time.Now()
    if size > 0 {
        tx.size.Store(common.StorageSize(size))
    }
}