Skip to content

Commit

Permalink
Handle migrated celo transactions (#150)
Browse files Browse the repository at this point in the history
Introduces support for encoding/decoding legacy celo transaction types.

That covers:

Type 0 - The legacy transaction has been updated and now has the celo specific fields added
Type 124 - The v1 of the dynamic fee tx.
Additionally the transaction code has been updated to be less invasive to the op-geth repo by moving celo code into celo specific files and by removing celo specific methods added to the TxData interface.

Additionally I reversed the EthCompatible boolean field since it's used to determine how to encode a legacy tx, and if unset (I.E. EthCompatible == false) then the legacy tx would be encoded as a celo legacy tx, which of course broke a lot of tests. So now the field is called CeloLegacy and if unset the tx will be encoded as an eth compatible transaction.
  • Loading branch information
piersy authored and karlb committed Jul 10, 2024
1 parent 004e586 commit 9461429
Show file tree
Hide file tree
Showing 25 changed files with 571 additions and 177 deletions.
2 changes: 1 addition & 1 deletion accounts/external/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
switch tx.Type() {
case types.LegacyTxType, types.AccessListTxType:
args.GasPrice = (*hexutil.Big)(tx.GasPrice())
case types.DynamicFeeTxType, types.CeloDynamicFeeTxType, types.CeloDenominatedTxType:
case types.DynamicFeeTxType, types.CeloDynamicFeeTxV2Type, types.CeloDenominatedTxType:
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
default:
Expand Down
2 changes: 1 addition & 1 deletion core/blockchain_celo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func testNativeTransferWithFeeCurrency(t *testing.T, scheme string, feeCurrencyA
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{1})

txdata := &types.CeloDynamicFeeTx{
txdata := &types.CeloDynamicFeeTxV2{
ChainID: gspec.Config.ChainID,
Nonce: 0,
To: &aa,
Expand Down
2 changes: 1 addition & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta
*receipt.DepositReceiptVersion = types.CanyonDepositReceiptVersion
}
}
if tx.Type() == types.CeloDynamicFeeTxType {
if tx.Type() == types.CeloDynamicFeeTxV2Type {
alternativeBaseFee := evm.Context.BaseFee
if msg.FeeCurrency != nil {
alternativeBaseFee, err = exchange.ConvertCeloToCurrency(evm.Context.ExchangeRates, msg.FeeCurrency, evm.Context.BaseFee)
Expand Down
2 changes: 1 addition & 1 deletion core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
if tx.Type() == types.CeloDynamicFeeTxType {
if tx.Type() == types.CeloDynamicFeeTxV2Type {
var err error
baseFee, err = exchange.ConvertCeloToCurrency(exchangeRates, msg.FeeCurrency, baseFee)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion core/txpool/legacypool/celo_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

func txC(nonce int, feeCap int, tipCap int, gas int, currency *common.Address) *types.Transaction {
return types.NewTx(&types.CeloDynamicFeeTx{
return types.NewTx(&types.CeloDynamicFeeTxV2{
GasFeeCap: big.NewInt(int64(feeCap)),
GasTipCap: big.NewInt(int64(tipCap)),
FeeCurrency: currency,
Expand Down
4 changes: 2 additions & 2 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ func New(config Config, chain BlockChain) *LegacyPool {
// pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction.
func (pool *LegacyPool) Filter(tx *types.Transaction) bool {
switch tx.Type() {
case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.CeloDynamicFeeTxType:
case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.CeloDynamicFeeTxV2Type:
return true
default:
return false
Expand Down Expand Up @@ -642,7 +642,7 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro
types.LegacyTxType,
types.AccessListTxType,
types.DynamicFeeTxType,
types.CeloDynamicFeeTxType),
types.CeloDynamicFeeTxV2Type),
MaxSize: txMaxSize,
MinTip: pool.gasTip.Load().ToBig(),
EffectiveGasCeil: pool.config.EffectiveGasCeil,
Expand Down
6 changes: 2 additions & 4 deletions core/types/celo_denominated_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)

const CeloDenominatedTxType = 0x7a

type CeloDenominatedTx struct {
ChainID *big.Int
Nonce uint64
Expand Down Expand Up @@ -115,7 +117,3 @@ func (tx *CeloDenominatedTx) encode(b *bytes.Buffer) error {
func (tx *CeloDenominatedTx) decode(input []byte) error {
return rlp.DecodeBytes(input, tx)
}

func (tx *CeloDenominatedTx) feeCurrency() *common.Address { return tx.FeeCurrency }

func (tx *CeloDenominatedTx) maxFeeInFeeCurrency() *big.Int { return tx.MaxFeeInFeeCurrency }
60 changes: 39 additions & 21 deletions core/types/celo_dynamic_fee_tx.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
// TODO: needs copyright header?
// Copyright 2024 The Celo Authors
// This file is part of the celo library.
//
// The celo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The celo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the celo library. If not, see <http://www.gnu.org/licenses/>.

package types

Expand All @@ -10,19 +24,21 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)

// CeloDynamicFeeTx represents a CIP-64 transaction.
type CeloDynamicFeeTx struct {
ChainID *big.Int
Nonce uint64
GasTipCap *big.Int
GasFeeCap *big.Int
Gas uint64
To *common.Address `rlp:"nil"` // nil means contract creation
Value *big.Int
Data []byte
AccessList AccessList
const CeloDynamicFeeTxType = 0x7c

FeeCurrency *common.Address `rlp:"nil"` // nil means native currency
type CeloDynamicFeeTx struct {
ChainID *big.Int
Nonce uint64
GasTipCap *big.Int
GasFeeCap *big.Int
Gas uint64
FeeCurrency *common.Address `rlp:"nil"` // nil means native currency
GatewayFeeRecipient *common.Address `rlp:"nil"` // nil means no gateway fee is paid
GatewayFee *big.Int `rlp:"nil"`
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"`
Expand All @@ -33,13 +49,15 @@ type CeloDynamicFeeTx struct {
// copy creates a deep copy of the transaction data and initializes all fields.
func (tx *CeloDynamicFeeTx) copy() TxData {
cpy := &CeloDynamicFeeTx{
Nonce: tx.Nonce,
To: copyAddressPtr(tx.To),
Data: common.CopyBytes(tx.Data),
Gas: tx.Gas,
FeeCurrency: copyAddressPtr(tx.FeeCurrency),
Nonce: tx.Nonce,
To: copyAddressPtr(tx.To),
Data: common.CopyBytes(tx.Data),
Gas: tx.Gas,
FeeCurrency: copyAddressPtr(tx.FeeCurrency),
GatewayFeeRecipient: copyAddressPtr(tx.GatewayFeeRecipient),
// These are copied below.
AccessList: make(AccessList, len(tx.AccessList)),
GatewayFee: new(big.Int),
Value: new(big.Int),
ChainID: new(big.Int),
GasTipCap: new(big.Int),
Expand All @@ -61,6 +79,9 @@ func (tx *CeloDynamicFeeTx) copy() TxData {
if tx.GasFeeCap != nil {
cpy.GasFeeCap.Set(tx.GasFeeCap)
}
if tx.GatewayFee != nil {
cpy.GatewayFee.Set(tx.GatewayFee)
}
if tx.V != nil {
cpy.V.Set(tx.V)
}
Expand Down Expand Up @@ -113,6 +134,3 @@ func (tx *CeloDynamicFeeTx) encode(b *bytes.Buffer) error {
func (tx *CeloDynamicFeeTx) decode(input []byte) error {
return rlp.DecodeBytes(input, tx)
}

func (tx *CeloDynamicFeeTx) feeCurrency() *common.Address { return tx.FeeCurrency }
func (tx *CeloDynamicFeeTx) maxFeeInFeeCurrency() *big.Int { return nil }
131 changes: 131 additions & 0 deletions core/types/celo_dynamic_fee_tx_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2024 The Celo Authors
// This file is part of the celo library.
//
// The celo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The celo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the celo library. If not, see <http://www.gnu.org/licenses/>.

package types

import (
"bytes"
"math/big"

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

const CeloDynamicFeeTxV2Type = 0x7b

// CeloDynamicFeeTxV2 represents a CIP-64 transaction.
type CeloDynamicFeeTxV2 struct {
ChainID *big.Int
Nonce uint64
GasTipCap *big.Int
GasFeeCap *big.Int
Gas uint64
To *common.Address `rlp:"nil"` // nil means contract creation
Value *big.Int
Data []byte
AccessList AccessList

FeeCurrency *common.Address `rlp:"nil"` // nil means native currency

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

// copy creates a deep copy of the transaction data and initializes all fields.
func (tx *CeloDynamicFeeTxV2) copy() TxData {
cpy := &CeloDynamicFeeTxV2{
Nonce: tx.Nonce,
To: copyAddressPtr(tx.To),
Data: common.CopyBytes(tx.Data),
Gas: tx.Gas,
FeeCurrency: copyAddressPtr(tx.FeeCurrency),
// These are copied below.
AccessList: make(AccessList, len(tx.AccessList)),
Value: new(big.Int),
ChainID: new(big.Int),
GasTipCap: new(big.Int),
GasFeeCap: new(big.Int),
V: new(big.Int),
R: new(big.Int),
S: new(big.Int),
}
copy(cpy.AccessList, tx.AccessList)
if tx.Value != nil {
cpy.Value.Set(tx.Value)
}
if tx.ChainID != nil {
cpy.ChainID.Set(tx.ChainID)
}
if tx.GasTipCap != nil {
cpy.GasTipCap.Set(tx.GasTipCap)
}
if tx.GasFeeCap != nil {
cpy.GasFeeCap.Set(tx.GasFeeCap)
}
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 *CeloDynamicFeeTxV2) txType() byte { return CeloDynamicFeeTxV2Type }
func (tx *CeloDynamicFeeTxV2) chainID() *big.Int { return tx.ChainID }
func (tx *CeloDynamicFeeTxV2) accessList() AccessList { return tx.AccessList }
func (tx *CeloDynamicFeeTxV2) data() []byte { return tx.Data }
func (tx *CeloDynamicFeeTxV2) gas() uint64 { return tx.Gas }
func (tx *CeloDynamicFeeTxV2) gasFeeCap() *big.Int { return tx.GasFeeCap }
func (tx *CeloDynamicFeeTxV2) gasTipCap() *big.Int { return tx.GasTipCap }
func (tx *CeloDynamicFeeTxV2) gasPrice() *big.Int { return tx.GasFeeCap }
func (tx *CeloDynamicFeeTxV2) value() *big.Int { return tx.Value }
func (tx *CeloDynamicFeeTxV2) nonce() uint64 { return tx.Nonce }
func (tx *CeloDynamicFeeTxV2) to() *common.Address { return tx.To }
func (tx *CeloDynamicFeeTxV2) isSystemTx() bool { return false }

func (tx *CeloDynamicFeeTxV2) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
if baseFee == nil {
return dst.Set(tx.GasFeeCap)
}
tip := dst.Sub(tx.GasFeeCap, baseFee)
if tip.Cmp(tx.GasTipCap) > 0 {
tip.Set(tx.GasTipCap)
}
return tip.Add(tip, baseFee)
}

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

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

func (tx *CeloDynamicFeeTxV2) encode(b *bytes.Buffer) error {
return rlp.Encode(b, tx)
}

func (tx *CeloDynamicFeeTxV2) decode(input []byte) error {
return rlp.DecodeBytes(input, tx)
}
49 changes: 44 additions & 5 deletions core/types/celo_transaction.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,48 @@
// Copyright 2024 The Celo Authors
// This file is part of the celo library.
//
// The celo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The celo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the celo library. If not, see <http://www.gnu.org/licenses/>.

package types

import (
"math/big"

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

// FeeCurrency returns the fee currency of the transaction if there is one.
func (tx *Transaction) FeeCurrency() *common.Address {
var feeCurrency *common.Address
switch t := tx.inner.(type) {
case *CeloDynamicFeeTxV2:
feeCurrency = t.FeeCurrency
}
return feeCurrency
}

// MaxFeeInFeeCurrency returns the maximum fee in the fee currency of the transaction if there is one.
func (tx *Transaction) MaxFeeInFeeCurrency() *big.Int {
var maxFeeInFeeCurrency *big.Int
switch t := tx.inner.(type) {
case *CeloDenominatedTx:
maxFeeInFeeCurrency = t.MaxFeeInFeeCurrency
}
return maxFeeInFeeCurrency
}

// CompareWithRates compares the effective gas price of two transactions according to the exchange rates and
// the base fees in the transactions currencies.
func CompareWithRates(a, b *Transaction, ratesAndFees *exchange.RatesAndFees) int {
Expand All @@ -17,23 +56,23 @@ func CompareWithRates(a, b *Transaction, ratesAndFees *exchange.RatesAndFees) in
}
rates := ratesAndFees.Rates
if ratesAndFees.HasBaseFee() {
tipA := a.EffectiveGasTipValue(ratesAndFees.GetBaseFeeIn(a.inner.feeCurrency()))
tipB := b.EffectiveGasTipValue(ratesAndFees.GetBaseFeeIn(b.inner.feeCurrency()))
c, _ := exchange.CompareValue(rates, tipA, a.inner.feeCurrency(), tipB, b.inner.feeCurrency())
tipA := a.EffectiveGasTipValue(ratesAndFees.GetBaseFeeIn(a.FeeCurrency()))
tipB := b.EffectiveGasTipValue(ratesAndFees.GetBaseFeeIn(b.FeeCurrency()))
c, _ := exchange.CompareValue(rates, tipA, a.FeeCurrency(), tipB, b.FeeCurrency())
return c
}

// Compare fee caps if baseFee is not specified or effective tips are equal
feeA := a.inner.gasFeeCap()
feeB := b.inner.gasFeeCap()
c, _ := exchange.CompareValue(rates, feeA, a.inner.feeCurrency(), feeB, b.inner.feeCurrency())
c, _ := exchange.CompareValue(rates, feeA, a.FeeCurrency(), feeB, b.FeeCurrency())
if c != 0 {
return c
}

// Compare tips if effective tips and fee caps are equal
tipCapA := a.inner.gasTipCap()
tipCapB := b.inner.gasTipCap()
c, _ = exchange.CompareValue(rates, tipCapA, a.inner.feeCurrency(), tipCapB, b.inner.feeCurrency())
c, _ = exchange.CompareValue(rates, tipCapA, a.FeeCurrency(), tipCapB, b.FeeCurrency())
return c
}
Loading

0 comments on commit 9461429

Please sign in to comment.