Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply TxFeeCap conversion #321

Merged
merged 7 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ func (s *Ethereum) APIs() []rpc.API {
if s.config.RollupSequencerTxConditionalEnabled {
log.Info("Enabling eth_sendRawTransactionConditional endpoint support")
costRateLimit := rate.Limit(s.config.RollupSequencerTxConditionalCostRateLimit)
apis = append(apis, sequencerapi.GetSendRawTxConditionalAPI(s.APIBackend, s.seqRPCService, costRateLimit))
apis = append(apis, sequencerapi.GetSendRawTxConditionalAPI(celoBackend, s.seqRPCService, costRateLimit))
}

// Append all the local APIs and return
Expand Down
54 changes: 47 additions & 7 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/accounts/scwallet"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/exchange"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus"
Expand Down Expand Up @@ -527,7 +528,7 @@ func (api *PersonalAccountAPI) SignTransaction(ctx context.Context, args Transac
}
// Before actually signing the transaction, ensure the transaction fee is reasonable.
tx := args.ToTransaction(types.LegacyTxType)
if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
if err := CheckTxFee(ctx, api.b, tx.GasPrice(), tx.Gas(), tx.FeeCurrency()); err != nil {
return nil, err
}
signed, err := api.signTransaction(ctx, &args, passwd)
Expand Down Expand Up @@ -2218,10 +2219,10 @@ func (api *TransactionAPI) sign(addr common.Address, tx *types.Transaction) (*ty
}

// SubmitTransaction is a helper function that submits tx to txPool and logs a message.
func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
func SubmitTransaction(ctx context.Context, b CeloBackend, tx *types.Transaction) (common.Hash, error) {
// If the transaction fee cap is already specified, ensure the
// fee of the given transaction is _reasonable_.
if err := checkTxFee(tx.GasPrice(), tx.Gas(), b.RPCTxFeeCap()); err != nil {
if err := CheckTxFee(ctx, b, tx.GasPrice(), tx.Gas(), tx.FeeCurrency()); err != nil {
return common.Hash{}, err
}
if !b.UnprotectedAllowed() && !tx.Protected() {
Expand Down Expand Up @@ -2366,7 +2367,7 @@ func (api *TransactionAPI) SignTransaction(ctx context.Context, args Transaction
}
// Before actually sign the transaction, ensure the transaction fee is reasonable.
tx := args.ToTransaction(types.LegacyTxType)
if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
if err := CheckTxFee(ctx, api.b, tx.GasPrice(), tx.Gas(), tx.FeeCurrency()); err != nil {
return nil, err
}
signed, err := api.sign(args.from(), tx)
Expand Down Expand Up @@ -2434,7 +2435,7 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs,
if gasLimit != nil {
gas = uint64(*gasLimit)
}
if err := checkTxFee(price, gas, api.b.RPCTxFeeCap()); err != nil {
if err := CheckTxFee(ctx, api.b, price, gas, matchTx.FeeCurrency()); err != nil {
return common.Hash{}, err
}
// Iterate the pending list for replacement
Expand Down Expand Up @@ -2644,6 +2645,45 @@ func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
}

// CheckTxFee exports a helper function used to check whether the fee is reasonable
func CheckTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
return checkTxFee(gasPrice, gas, cap)
func CheckTxFee(ctx context.Context, backend CeloBackend, gasPrice *big.Int, gas uint64, feeCurrency *common.Address) error {
feeCap, err := ConvertTxFeeCapToCurrency(ctx, backend, feeCurrency)
ezdac marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

return checkTxFee(gasPrice, gas, feeCap)
}

// ConvertTxFeeCapToCurrency converts FeeCap in native currency into the specified currency
// If no fee currency is specified, it returns the native currency FeeCap as is
func ConvertTxFeeCapToCurrency(ctx context.Context, backend CeloBackend, feeCurrency *common.Address) (float64, error) {
txFeeCap := backend.RPCTxFeeCap()
if feeCurrency == nil {
return txFeeCap, nil
}

rates, err := backend.GetExchangeRates(ctx, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
if err != nil {
log.Warn("Failed to get exchange rates", "err", err)
return 0, err
}
rate, ok := rates[*feeCurrency]
if !ok {
return 0, fmt.Errorf("could not convert from native to fee currency (fee-currency=%s): %w ", feeCurrency, exchange.ErrUnregisteredFeeCurrency)
}

// NOTE: Avoiding exchange.ConvertCeloToCurrency to prevent precision loss when converting float64 to big.Int
// Using big.Rat instead to maintain fractional precision during conversion
txFeeCapInCurrency := new(big.Rat).Mul(
Kourin1996 marked this conversation as resolved.
Show resolved Hide resolved
new(big.Rat).SetFloat64(txFeeCap),
rate,
)

result, exact := txFeeCapInCurrency.Float64()
if !exact {
log.Warn("Failed to accurately convert fee currency to float64", "fee cap", txFeeCapInCurrency.String())
return 0, fmt.Errorf("failed to accurately convert fee currency to float64")
}

return result, nil
}
44 changes: 44 additions & 0 deletions internal/ethapi/celo_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/exchange"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/blocktest"
Expand Down Expand Up @@ -613,3 +615,45 @@ func TestRPCMarshalBlock_Celo1TotalDifficulty(t *testing.T) {
assert.Equal(t, nil, res["totalDifficulty"])
})
}

// TestConvertTxFeeCapToCurrency verifies the output is accurate for each currency
func TestConvertTxFeeCapToCurrency(t *testing.T) {
t.Parallel()

var (
txFeeCap = float64(1.5)
rates = common.ExchangeRates{
core.DevFeeCurrencyAddr: big.NewRat(2, 1),
core.DevFeeCurrencyAddr2: big.NewRat(1, 2),
}
config = allEnabledChainConfig()
)

backend := newCeloBackendMock(config)
backend.SetExchangeRates(rates)
backend.SetRPCTxFeeCap(txFeeCap)

t.Run("should return given fee cap when fee currency is not specified", func(t *testing.T) {
res, err := ConvertTxFeeCapToCurrency(context.Background(), backend, nil)
require.NoError(t, err)
assert.Equal(t, txFeeCap, res)
})

t.Run("should return the twice of given fee cap when a fee currency is specified", func(t *testing.T) {
Kourin1996 marked this conversation as resolved.
Show resolved Hide resolved
res, err := ConvertTxFeeCapToCurrency(context.Background(), backend, &core.DevFeeCurrencyAddr)
require.NoError(t, err)
assert.Equal(t, 2*txFeeCap, res)
})

t.Run("should return the half of given fee cap when a fee currency is specified", func(t *testing.T) {
Kourin1996 marked this conversation as resolved.
Show resolved Hide resolved
res, err := ConvertTxFeeCapToCurrency(context.Background(), backend, &core.DevFeeCurrencyAddr2)
require.NoError(t, err)
assert.Equal(t, txFeeCap/2, res)
})

t.Run("should return error when fee currency is not found in exchange rates", func(t *testing.T) {
res, err := ConvertTxFeeCapToCurrency(context.Background(), backend, &core.DevAddr)
require.Zero(t, res)
assert.ErrorIs(t, err, exchange.ErrUnregisteredFeeCurrency)
})
}
16 changes: 13 additions & 3 deletions internal/ethapi/transaction_args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ type celoBackendMock struct {
chainDb ethdb.Database
blockByNumber map[int64]*types.Block
receiptsByHash map[common.Hash]types.Receipts
rates common.ExchangeRates
rpcTxFeeCap float64
}

func newCeloBackendMock(config *params.ChainConfig) *celoBackendMock {
Expand All @@ -329,15 +331,21 @@ func newCeloBackendMock(config *params.ChainConfig) *celoBackendMock {
}
}

func (c *celoBackendMock) SetExchangeRates(rates common.ExchangeRates) {
c.rates = rates
}

func (c *celoBackendMock) SetRPCTxFeeCap(txFeeCap float64) {
c.rpcTxFeeCap = txFeeCap
}

func (c *celoBackendMock) GetFeeBalance(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, account common.Address, feeCurrency *common.Address) (*big.Int, error) {
// Celo specific backend features are currently not tested
return nil, errCeloNotImplemented
}

func (c *celoBackendMock) GetExchangeRates(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash) (common.ExchangeRates, error) {
var er common.ExchangeRates
// This Celo specific backend features are currently not tested
return er, errCeloNotImplemented
return c.rates, nil
}

func (c *celoBackendMock) ConvertToCurrency(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, value *big.Int, fromFeeCurrency *common.Address) (*big.Int, error) {
Expand Down Expand Up @@ -374,6 +382,8 @@ func (c *celoBackendMock) setReceipts(hash common.Hash, receipts types.Receipts)
c.receiptsByHash[hash] = receipts
}

func (c *celoBackendMock) RPCTxFeeCap() float64 { return c.rpcTxFeeCap }

type backendMock struct {
current *types.Header
config *params.ChainConfig
Expand Down
8 changes: 4 additions & 4 deletions internal/sequencerapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ var (
)

type sendRawTxCond struct {
b ethapi.Backend
b ethapi.CeloBackend
seqRPC *rpc.Client
costLimiter *rate.Limiter
}

func GetSendRawTxConditionalAPI(b ethapi.Backend, seqRPC *rpc.Client, costRateLimit rate.Limit) rpc.API {
func GetSendRawTxConditionalAPI(b ethapi.CeloBackend, seqRPC *rpc.Client, costRateLimit rate.Limit) rpc.API {
// Applying a manual bump to the burst to allow conditional txs to queue. Metrics will
// will inform of adjustments that may need to be made here.
costLimiter := rate.NewLimiter(costRateLimit, 3*params.TransactionConditionalMaxCost)
Expand Down Expand Up @@ -104,7 +104,7 @@ func (s *sendRawTxCond) SendRawTransactionConditional(ctx context.Context, txByt
// forward if seqRPC is set, otherwise submit the tx
if s.seqRPC != nil {
// Some precondition checks done by `ethapi.SubmitTransaction` that are good to also check here
if err := ethapi.CheckTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil {
if err := ethapi.CheckTxFee(ctx, s.b, tx.GasPrice(), tx.Gas(), tx.FeeCurrency()); err != nil {
return common.Hash{}, err
}
if !s.b.UnprotectedAllowed() && !tx.Protected() {
Expand All @@ -113,7 +113,7 @@ func (s *sendRawTxCond) SendRawTransactionConditional(ctx context.Context, txByt
}

var hash common.Hash
err := s.seqRPC.CallContext(ctx, &hash, "eth_sendRawTransactionConditional", txBytes, cond)
err = s.seqRPC.CallContext(ctx, &hash, "eth_sendRawTransactionConditional", txBytes, cond)
return hash, err
} else {
// Set out-of-consensus internal tx fields
Expand Down