diff --git a/eth/backend.go b/eth/backend.go index 98b45ea63e..6e7a3895ff 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -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 diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9912784119..82d7016f53 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -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" @@ -527,7 +528,11 @@ 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 { + txFeeCap, err := ConvertTxFeeCapToCurrency(ctx, api.b, tx.FeeCurrency()) + if err != nil { + return nil, err + } + if err := checkTxFee(tx.GasPrice(), tx.Gas(), txFeeCap); err != nil { return nil, err } signed, err := api.signTransaction(ctx, &args, passwd) @@ -2218,10 +2223,14 @@ 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 { + txFeeCap, err := ConvertTxFeeCapToCurrency(ctx, b, tx.FeeCurrency()) + if err != nil { + return common.Hash{}, err + } + if err := checkTxFee(tx.GasPrice(), tx.Gas(), txFeeCap); err != nil { return common.Hash{}, err } if !b.UnprotectedAllowed() && !tx.Protected() { @@ -2366,7 +2375,11 @@ 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 { + txFeeCap, err := ConvertTxFeeCapToCurrency(ctx, api.b, tx.FeeCurrency()) + if err != nil { + return nil, err + } + if err := checkTxFee(tx.GasPrice(), tx.Gas(), txFeeCap); err != nil { return nil, err } signed, err := api.sign(args.from(), tx) @@ -2434,7 +2447,11 @@ 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 { + txFeeCap, err := ConvertTxFeeCapToCurrency(ctx, api.b, matchTx.FeeCurrency()) + if err != nil { + return common.Hash{}, err + } + if err := checkTxFee(price, gas, txFeeCap); err != nil { return common.Hash{}, err } // Iterate the pending list for replacement @@ -2466,6 +2483,39 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash()) } +// 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", "current", backend.CurrentBlock().Number, "err", err) + return 0, err + } + + // Convert txFeeCap (float64) to big.Int with proper precision + // Since ConvertCeloToCurrency expects big.Int, directly converting to big.Int would lose decimal precision + scaledTxFeeCap := big.NewInt(int64(txFeeCap * 1e18)) + scaledTxFeeCapInCurrency, err := exchange.ConvertCeloToCurrency(rates, feeCurrency, scaledTxFeeCap) + if err != nil { + log.Warn("Failed to convert RPCTxFeeCap to the specified currency", "current", backend.CurrentBlock().Number, "currency", feeCurrency, "err", err) + return 0, err + } + txFeeCapInCurrency := scaledTxFeeCapInCurrency.Div(scaledTxFeeCapInCurrency, big.NewInt(1e18)) + + result, accuracy := txFeeCapInCurrency.Float64() + if accuracy != big.Exact { + log.Warn("Failed to accurately convert fee currency to float64", "fee cap", txFeeCapInCurrency.String(), "accuracy", accuracy.String()) + return 0, fmt.Errorf("failed to accurately convert fee currency to float64") + } + + return result, nil +} + // DebugAPI is the collection of Ethereum APIs exposed over the debugging // namespace. type DebugAPI struct { diff --git a/internal/sequencerapi/api.go b/internal/sequencerapi/api.go index 9a64258948..be58353df8 100644 --- a/internal/sequencerapi/api.go +++ b/internal/sequencerapi/api.go @@ -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) @@ -104,7 +104,11 @@ 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 { + txFeeCap, err := ethapi.ConvertTxFeeCapToCurrency(ctx, s.b, tx.FeeCurrency()) + if err != nil { + return common.Hash{}, err + } + if err := ethapi.CheckTxFee(tx.GasPrice(), tx.Gas(), txFeeCap); err != nil { return common.Hash{}, err } if !s.b.UnprotectedAllowed() && !tx.Protected() { @@ -113,7 +117,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