Skip to content

Commit

Permalink
core/types: add new sponsored transaction types
Browse files Browse the repository at this point in the history
This commit adds new sponsored transaction type 0x64 (100) following EIP-2718:
Typed Transaction Envelope. The new transaction type is the same as legacy with
additional expiredTime and payer's signature field.
  • Loading branch information
minh-bq committed Oct 26, 2023
1 parent 5dc08f1 commit 45c0638
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 39 deletions.
4 changes: 4 additions & 0 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,10 @@ func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
func (m callMsg) Data() []byte { return m.CallMsg.Data }
func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList }

// FIXME: support sponsored transaction in callMsg
func (m callMsg) Payer() common.Address { return m.CallMsg.From }
func (m callMsg) ExpiredTime() uint64 { return 0 }

// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
type filterBackend struct {
Expand Down
5 changes: 5 additions & 0 deletions core/types/access_list_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ func (tx *AccessListTx) gasFeeCap() *big.Int { return tx.GasPrice }
func (tx *AccessListTx) value() *big.Int { return tx.Value }
func (tx *AccessListTx) nonce() uint64 { return tx.Nonce }
func (tx *AccessListTx) to() *common.Address { return tx.To }
func (tx *AccessListTx) expiredTime() uint64 { return 0 }

func (tx *AccessListTx) rawPayerSignatureValues() (v, r, s *big.Int) {
return nil, nil, nil
}

func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) {
return tx.V, tx.R, tx.S
Expand Down
5 changes: 5 additions & 0 deletions core/types/dynamic_fee_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ 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) expiredTime() uint64 { return 0 }

func (tx *DynamicFeeTx) rawPayerSignatureValues() (v, r, s *big.Int) {
return nil, nil, nil
}

func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) {
return tx.V, tx.R, tx.S
Expand Down
5 changes: 5 additions & 0 deletions core/types/legacy_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ 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) expiredTime() uint64 { return 0 }

func (tx *LegacyTx) rawPayerSignatureValues() (v, r, s *big.Int) {
return nil, nil, nil
}

func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) {
return tx.V, tx.R, tx.S
Expand Down
7 changes: 5 additions & 2 deletions core/types/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
return errEmptyTypedReceipt
}
r.Type = b[0]
if r.Type == AccessListTxType || r.Type == DynamicFeeTxType {
if r.Type == AccessListTxType || r.Type == DynamicFeeTxType || r.Type == SponsoredTxType {
var dec receiptRLP
if err := rlp.DecodeBytes(b[1:], &dec); err != nil {
return err
Expand Down Expand Up @@ -228,7 +228,7 @@ func (r *Receipt) decodeTyped(b []byte) error {
return errEmptyTypedReceipt
}
switch b[0] {
case DynamicFeeTxType, AccessListTxType:
case DynamicFeeTxType, AccessListTxType, SponsoredTxType:
var data receiptRLP
err := rlp.DecodeBytes(b[1:], &data)
if err != nil {
Expand Down Expand Up @@ -397,6 +397,9 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
case DynamicFeeTxType:
w.WriteByte(DynamicFeeTxType)
rlp.Encode(w, data)
case SponsoredTxType:
w.WriteByte(SponsoredTxType)
rlp.Encode(w, data)
default:
// For unsupported types, write nothing. Since this is for
// DeriveSha, the error will be caught matching the derived hash
Expand Down
89 changes: 89 additions & 0 deletions core/types/sponsored_tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package types

import (
"math/big"

"github.com/ethereum/go-ethereum/common"
)

type SponsoredTx 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
ExpiredTime uint64 // the expired time of payer's signature
PayerV, PayerR, PayerS *big.Int // payer's signature values
V, R, S *big.Int // sender's signature values
}

func (tx *SponsoredTx) copy() TxData {
cpy := &SponsoredTx{
Nonce: tx.Nonce,
To: copyAddressPtr(tx.To),
Data: common.CopyBytes(tx.Data),
Gas: tx.Gas,
ExpiredTime: tx.ExpiredTime,
// These are initialized below.
Value: new(big.Int),
GasPrice: new(big.Int),
PayerV: new(big.Int),
PayerR: new(big.Int),
PayerS: new(big.Int),
V: new(big.Int),
R: new(big.Int),
S: new(big.Int),
}
if tx.Value != nil {
cpy.Value.Set(tx.Value)
}
if tx.GasPrice != nil {
cpy.GasPrice.Set(tx.GasPrice)
}
if tx.PayerV != nil {
cpy.PayerV.Set(tx.PayerV)
}
if tx.PayerR != nil {
cpy.PayerR.Set(tx.PayerR)
}
if tx.PayerS != nil {
cpy.PayerS.Set(tx.PayerS)
}
if tx.V != nil {
cpy.V.Set(tx.V)
}
if tx.R != nil {
cpy.R.Set(tx.R)
}
if tx.S != nil {
cpy.S.Set(tx.S)
}
return cpy
}

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

func (tx *SponsoredTx) rawPayerSignatureValues() (v, r, s *big.Int) {
return tx.PayerV, tx.PayerR, tx.PayerS
}

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

func (tx *SponsoredTx) setSignatureValues(chainID, v, r, s *big.Int) {
tx.V, tx.R, tx.S = v, r, s
}
88 changes: 63 additions & 25 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const (
LegacyTxType = iota
AccessListTxType
DynamicFeeTxType
SponsoredTxType = 100
)

// Transaction is an Ethereum transaction.
Expand All @@ -53,9 +54,10 @@ type Transaction struct {
time time.Time // Time first seen locally (spam avoidance)

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

// NewTx creates a new transaction.
Expand All @@ -82,7 +84,9 @@ type TxData interface {
value() *big.Int
nonce() uint64
to() *common.Address
expiredTime() uint64

rawPayerSignatureValues() (v, r, s *big.Int)
rawSignatureValues() (v, r, s *big.Int)
setSignatureValues(chainID, v, r, s *big.Int)
}
Expand Down Expand Up @@ -287,13 +291,24 @@ func (tx *Transaction) To() *common.Address {
return copyAddressPtr(tx.inner.to())
}

// ExpiredTime returns the expired time of the sponsored transaction
func (tx *Transaction) ExpiredTime() uint64 {
return tx.inner.expiredTime()
}

// Cost returns gas * gasPrice + value.
func (tx *Transaction) Cost() *big.Int {
total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
total.Add(total, tx.Value())
return total
}

// RawSignatureValues returns the V, R, S payer signature values of the transaction.
// The return values should not be modified by the caller.
func (tx *Transaction) RawPayerSignatureValues() (v, r, s *big.Int) {
return tx.inner.rawPayerSignatureValues()
}

// RawSignatureValues returns the V, R, S signature values of the transaction.
// The return values should not be modified by the caller.
func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) {
Expand Down Expand Up @@ -567,17 +582,19 @@ func (t *TransactionsByPriceAndNonce) Size() int {
//
// NOTE: In a future PR this will be removed.
type Message struct {
to *common.Address
from common.Address
nonce uint64
amount *big.Int
gasLimit uint64
gasPrice *big.Int
gasFeeCap *big.Int
gasTipCap *big.Int
data []byte
accessList AccessList
isFake bool
to *common.Address
from common.Address
nonce uint64
amount *big.Int
gasLimit uint64
gasPrice *big.Int
gasFeeCap *big.Int
gasTipCap *big.Int
data []byte
accessList AccessList
isFake bool
payer common.Address
expiredTime uint64
}

func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, isFake bool) Message {
Expand All @@ -599,24 +616,43 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
// AsMessage returns the transaction as a core.Message.
func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
msg := Message{
nonce: tx.Nonce(),
gasLimit: tx.Gas(),
gasPrice: new(big.Int).Set(tx.GasPrice()),
gasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
gasTipCap: new(big.Int).Set(tx.GasTipCap()),
to: tx.To(),
amount: tx.Value(),
data: tx.Data(),
accessList: tx.AccessList(),
isFake: false,
nonce: tx.Nonce(),
gasLimit: tx.Gas(),
gasPrice: new(big.Int).Set(tx.GasPrice()),
gasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
gasTipCap: new(big.Int).Set(tx.GasTipCap()),
to: tx.To(),
amount: tx.Value(),
data: tx.Data(),
accessList: tx.AccessList(),
isFake: false,
expiredTime: tx.ExpiredTime(),
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
msg.gasPrice = math.BigMin(msg.gasPrice.Add(msg.gasTipCap, baseFee), msg.gasFeeCap)
}
var err error
msg.from, err = Sender(s, tx)
return msg, err
if err != nil {
return Message{}, err
}

msg.payer, err = Payer(s, tx)
if err != nil && errors.Is(err, errMissingPayerField) {
if errors.Is(err, errMissingPayerField) {
// This is not a sponsored transaction, the payer is the same as from
msg.payer = msg.from
return msg, nil
}

return Message{}, err
} else if msg.payer == msg.from {
// Reject sponsored transaction with identical payer and sender
return Message{}, errors.New("payer = sender in sponsored transaction")
}

return msg, nil
}

func (m Message) From() common.Address { return m.from }
Expand All @@ -630,6 +666,8 @@ func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data }
func (m Message) AccessList() AccessList { return m.accessList }
func (m Message) IsFake() bool { return m.isFake }
func (m Message) Payer() common.Address { return m.payer }
func (m Message) ExpiredTime() uint64 { return m.expiredTime }

// copyAddressPtr copies an address.
func copyAddressPtr(a *common.Address) *common.Address {
Expand Down
Loading

0 comments on commit 45c0638

Please sign in to comment.