Files
scroll/rollup/internal/controller/sender/estimategas.go

190 lines
6.8 KiB
Go

package sender
import (
"errors"
"fmt"
"math/big"
"github.com/scroll-tech/go-ethereum"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/log"
)
func (s *Sender) estimateLegacyGas(to *common.Address, data []byte) (*FeeData, error) {
gasPrice, err := s.client.SuggestGasPrice(s.ctx)
if err != nil {
log.Error("estimateLegacyGas SuggestGasPrice failure", "error", err)
return nil, err
}
minGasTip := new(big.Int).SetUint64(s.config.MinGasTip)
if gasPrice.Cmp(minGasTip) < 0 {
gasPrice = minGasTip
}
gasLimit, _, err := s.estimateGasLimit(to, data, nil, gasPrice, nil, nil, nil)
if err != nil {
log.Error("estimateLegacyGas estimateGasLimit failure", "gas price", gasPrice, "from", s.transactionSigner.GetAddr().String(),
"nonce", s.transactionSigner.GetNonce(), "to address", to.String(), "error", err)
return nil, err
}
return &FeeData{
gasPrice: gasPrice,
gasLimit: gasLimit * 12 / 10, // 20% extra gas to avoid out of gas error
}, nil
}
func (s *Sender) estimateDynamicGas(to *common.Address, data []byte, baseFee uint64) (*FeeData, error) {
gasTipCap, err := s.client.SuggestGasTipCap(s.ctx)
if err != nil {
log.Error("estimateDynamicGas SuggestGasTipCap failure", "error", err)
return nil, err
}
minGasTip := new(big.Int).SetUint64(s.config.MinGasTip)
if gasTipCap.Cmp(minGasTip) < 0 {
gasTipCap = minGasTip
}
gasFeeCap := getGasFeeCap(new(big.Int).SetUint64(baseFee), gasTipCap)
gasLimit, accessList, err := s.estimateGasLimit(to, data, nil, nil, gasTipCap, gasFeeCap, nil)
if err != nil {
log.Error("estimateDynamicGas estimateGasLimit failure",
"from", s.transactionSigner.GetAddr().String(), "nonce", s.transactionSigner.GetNonce(), "to address", to.String(),
"error", err)
return nil, err
}
feeData := &FeeData{
gasLimit: gasLimit * 12 / 10, // 20% extra gas to avoid out of gas error,
gasTipCap: gasTipCap,
gasFeeCap: gasFeeCap,
}
if accessList != nil {
feeData.accessList = *accessList
}
return feeData, nil
}
func (s *Sender) estimateBlobGas(to *common.Address, data []byte, sidecar *types.BlobTxSidecar, baseFee, blobBaseFee uint64) (*FeeData, error) {
gasTipCap, err := s.client.SuggestGasTipCap(s.ctx)
if err != nil {
log.Error("estimateBlobGas SuggestGasTipCap failure", "error", err)
return nil, err
}
minGasTip := new(big.Int).SetUint64(s.config.MinGasTip)
if gasTipCap.Cmp(minGasTip) < 0 {
gasTipCap = minGasTip
}
gasFeeCap := getGasFeeCap(new(big.Int).SetUint64(baseFee), gasTipCap)
blobGasFeeCap := getBlobGasFeeCap(new(big.Int).SetUint64(blobBaseFee))
gasLimit, accessList, err := s.estimateGasLimit(to, data, sidecar, nil, gasTipCap, gasFeeCap, blobGasFeeCap)
if err != nil {
log.Error("estimateBlobGas estimateGasLimit failure",
"from", s.transactionSigner.GetAddr().String(), "nonce", s.transactionSigner.GetNonce(), "to address", to.String(), "error", err)
return nil, err
}
feeData := &FeeData{
gasLimit: gasLimit * 12 / 10, // 20% extra gas to avoid out of gas error
gasTipCap: gasTipCap,
gasFeeCap: gasFeeCap,
blobGasFeeCap: blobGasFeeCap,
}
if accessList != nil {
feeData.accessList = *accessList
}
return feeData, nil
}
func (s *Sender) estimateGasLimit(to *common.Address, data []byte, sidecar *types.BlobTxSidecar, gasPrice, gasTipCap, gasFeeCap, blobGasFeeCap *big.Int) (uint64, *types.AccessList, error) {
msg := ethereum.CallMsg{
From: s.transactionSigner.GetAddr(),
To: to,
GasPrice: gasPrice,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Data: data,
}
if sidecar != nil {
msg.BlobHashes = sidecar.BlobHashes()
msg.BlobGasFeeCap = blobGasFeeCap
}
gasLimitWithoutAccessList, err := s.client.EstimateGas(s.ctx, msg)
if err != nil {
log.Error("estimateGasLimit EstimateGas failure without access list", "error", err, "msg", fmt.Sprintf("%+v", msg))
return 0, nil, err
}
if s.config.TxType == LegacyTxType ||
s.transactionSigner.GetType() == RemoteSignerType { // web3signer doesn't support access list
return gasLimitWithoutAccessList, nil, nil
}
// Explicitly set a gas limit to prevent the "insufficient funds for gas * price + value" error.
// Because if msg.Gas remains unset, CreateAccessList defaults to using RPCGasCap(), which can be excessively high.
msg.Gas = gasLimitWithoutAccessList * 3
accessList, gasLimitWithAccessList, errStr, rpcErr := s.gethClient.CreateAccessList(s.ctx, msg)
if rpcErr != nil {
log.Error("CreateAccessList RPC error", "error", rpcErr)
return gasLimitWithoutAccessList, nil, rpcErr
}
if errStr != "" {
log.Error("CreateAccessList reported error", "error", errStr)
return gasLimitWithoutAccessList, nil, errors.New(errStr)
}
// Fine-tune accessList because 'to' address is automatically included in the access list by the Ethereum protocol: https://github.com/ethereum/go-ethereum/blob/v1.13.10/core/state/statedb.go#L1322
// This function returns a gas estimation because GO SDK does not support access list: https://github.com/ethereum/go-ethereum/blob/v1.13.10/ethclient/ethclient.go#L642
accessList, gasLimitWithAccessList = finetuneAccessList(accessList, gasLimitWithAccessList, msg.To)
log.Info("gas", "senderName", s.name, "senderService", s.service, "gasLimitWithAccessList", gasLimitWithAccessList, "gasLimitWithoutAccessList", gasLimitWithoutAccessList, "accessList", accessList)
if gasLimitWithAccessList < gasLimitWithoutAccessList {
return gasLimitWithAccessList, accessList, nil
}
return gasLimitWithoutAccessList, nil, nil
}
func finetuneAccessList(accessList *types.AccessList, gasLimitWithAccessList uint64, to *common.Address) (*types.AccessList, uint64) {
if accessList == nil || to == nil {
return accessList, gasLimitWithAccessList
}
var newAccessList types.AccessList
for _, entry := range *accessList {
if entry.Address == *to && len(entry.StorageKeys) < 24 {
// Based on: https://arxiv.org/pdf/2312.06574.pdf
// We remove the address and respective storage keys as long as the number of storage keys < 24.
// This removal helps in preventing double-counting of the 'to' address in access list calculations.
gasLimitWithAccessList -= 2400
// Each storage key saves 100 gas units.
gasLimitWithAccessList += uint64(100 * len(entry.StorageKeys))
} else {
// Otherwise, keep the entry in the new access list.
newAccessList = append(newAccessList, entry)
}
}
return &newAccessList, gasLimitWithAccessList
}
func getGasFeeCap(baseFee, gasTipCap *big.Int) *big.Int {
// gasFeeCap = baseFee * 2 + gasTipCap
gasFeeCap := new(big.Int).Mul(baseFee, big.NewInt(2))
gasFeeCap = new(big.Int).Add(gasFeeCap, gasTipCap)
return gasFeeCap
}
func getBlobGasFeeCap(blobBaseFee *big.Int) *big.Int {
// blobGasFeeCap = blobBaseFee * 2
blobGasFeeCap := new(big.Int).Mul(blobBaseFee, big.NewInt(2))
return blobGasFeeCap
}