mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-08 21:48:11 -05:00
190 lines
6.8 KiB
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
|
|
}
|