# Changes in transaction structure after EIP1559

By [Untitled](https://paragraph.com/@0xc14857a25c2b7b49480cc7ffc16cdd9a77c57de2) · 2022-02-22

---

Transaction
-----------

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

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

定义了三种交易类型:`LegacyTxType`、`AccessListTxType`、`DynamicFeeTxType`

同时对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](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md) 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**

块有效性操作在下面的参考实现中定义。（`GASPRICE`）`0x3a`码**返回**下面`effective_gas_price`参考实现中定义的。

从2开始`FORK_BLOCK_NUMBER`，引入了一个新的[EIP-2718](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md)事务`TransactionType`。

新交易的特殊成本[——是从EIP2930](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2930.md)继承而来的`21000 + 16 * non-zero calldata bytes + 4 * zero calldata bytes + 1900 * access list storage key count + 2400 * access list address count`。

此交易的[EIP-2718](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md) `TransactionPayload`是`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])`。

此交易的`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](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md) `ReceiptPayload`是`rlp([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))
        }
    }

---

*Originally published on [Untitled](https://paragraph.com/@0xc14857a25c2b7b49480cc7ffc16cdd9a77c57de2/changes-in-transaction-structure-after-eip1559)*
