Skip to content

Commit

Permalink
add EstimateGasOpt
Browse files Browse the repository at this point in the history
  • Loading branch information
chengzhinei committed Jun 24, 2024
1 parent b281613 commit d9b288b
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 2 deletions.
2 changes: 2 additions & 0 deletions jsonrpc/apollo_xlayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type ApolloConfig struct {
BatchRequestsEnabled bool
BatchRequestsLimit uint
GasLimitFactor float64
EnableEstimateGasOpt bool
DisableAPIs []string
RateLimit RateLimitConfig
DynamicGP DynamicGPConfig
Expand Down Expand Up @@ -63,6 +64,7 @@ func UpdateConfig(apolloConfig Config) {
getApolloConfig().BatchRequestsEnabled = apolloConfig.BatchRequestsEnabled
getApolloConfig().BatchRequestsLimit = apolloConfig.BatchRequestsLimit
getApolloConfig().GasLimitFactor = apolloConfig.GasLimitFactor
getApolloConfig().EnableEstimateGasOpt = apolloConfig.EnableEstimateGasOpt
getApolloConfig().setDisableAPIs(apolloConfig.DisableAPIs)
setRateLimit(apolloConfig.RateLimit)
setApiAuth(apolloConfig.ApiAuthentication)
Expand Down
3 changes: 2 additions & 1 deletion jsonrpc/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ type Config struct {
// suggested gas limit: 100
// GasLimitFactor: 1.1
// gas limit = 110
GasLimitFactor float64 `mapstructure:"GasLimitFactor"`
GasLimitFactor float64 `mapstructure:"GasLimitFactor"`
EnableEstimateGasOpt bool `mapstructure:"EnableEstimateGasOpt"`

// DisableAPIs disable some API
DisableAPIs []string `mapstructure:"DisableAPIs"`
Expand Down
8 changes: 7 additions & 1 deletion jsonrpc/endpoints_eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,13 @@ func (e *EthEndpoints) EstimateGas(arg *types.TxArgs, blockArg *types.BlockNumbe
t2 := time.Now()
toTxTime := t2.Sub(t1)

gasEstimation, returnValue, err := e.state.EstimateGas(tx, sender, isGasFreeSender, blockToProcess, dbTx)
var gasEstimation uint64
var returnValue []byte
if e.enableEstimateGasOpt() {
gasEstimation, returnValue, err = e.state.EstimateGasOpt(tx, sender, isGasFreeSender, blockToProcess, dbTx)
} else {
gasEstimation, returnValue, err = e.state.EstimateGas(tx, sender, isGasFreeSender, blockToProcess, dbTx)
}
if errors.Is(err, runtime.ErrExecutionReverted) {
data := make([]byte, len(returnValue))
copy(data, returnValue)
Expand Down
13 changes: 13 additions & 0 deletions jsonrpc/endpoints_eth_xlayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,19 @@ func (e *EthEndpoints) getGasEstimationWithFactorXLayer(gasEstimation uint64) ui
return gasEstimationWithFactor
}

func (e *EthEndpoints) enableEstimateGasOpt() bool {
res := false
if getApolloConfig().Enable() {
getApolloConfig().RLock()
res = getApolloConfig().EnableEstimateGasOpt
getApolloConfig().RUnlock()
} else {
res = e.cfg.EnableEstimateGasOpt
}

return res
}

// internal
func (e *EthEndpoints) newPendingTransactionFilterXLayer(wsConn *concurrentWsConn) (interface{}, types.Error) {
//XLayer handle
Expand Down
5 changes: 5 additions & 0 deletions jsonrpc/mocks/mock_state.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions jsonrpc/types/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type StateInterface interface {
BeginStateTransaction(ctx context.Context) (pgx.Tx, error)
DebugTransaction(ctx context.Context, transactionHash common.Hash, traceConfig state.TraceConfig, dbTx pgx.Tx) (*runtime.ExecutionResult, error)
EstimateGas(transaction *types.Transaction, senderAddress common.Address, isGasFreeSender bool, l2BlockNumber *uint64, dbTx pgx.Tx) (uint64, []byte, error)
EstimateGasOpt(transaction *types.Transaction, senderAddress common.Address, isGasFreeSender bool, l2BlockNumber *uint64, dbTx pgx.Tx) (uint64, []byte, error)
GetBalance(ctx context.Context, address common.Address, root common.Hash) (*big.Int, error)
GetCode(ctx context.Context, address common.Address, root common.Hash) ([]byte, error)
GetL2BlockByHash(ctx context.Context, hash common.Hash, dbTx pgx.Tx) (*state.L2Block, error)
Expand Down
153 changes: 153 additions & 0 deletions state/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,7 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common
if lowEnd < estimationResult.gasUsed {
lowEnd = estimationResult.gasUsed
}
firstGasued := estimationResult.gasUsed

optimisticGasLimit := (estimationResult.gasUsed + estimationResult.gasRefund + params.CallStipend) * 64 / 63 // nolint:gomnd
if optimisticGasLimit < highEnd {
Expand Down Expand Up @@ -927,10 +928,162 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common
}
log.Infof("state-EstimateGas time. getBlock:%vms, getBatch:%vms, getForkID:%vms, getNonce:%vms, getEnd:%vms, internalGas:%vms, exec:%vms",
getBlockTime.Milliseconds(), getBatchTime.Milliseconds(), getForkIDTime.Milliseconds(), getNonceTime.Milliseconds(), getEndTime.Milliseconds(), internalGasTime.Milliseconds(), time.Since(t6).Milliseconds())
log.Infof("state-EstimateGas value. first gasUsed:%d, final gas", firstGasued, highEnd)

return highEnd, nil, nil
}

// EstimateGasOpt for a transaction
func (s *State) EstimateGasOpt(transaction *types.Transaction, senderAddress common.Address, isGasFreeSender bool, l2BlockNumber *uint64, dbTx pgx.Tx) (uint64, []byte, error) {
const ethTransferGas = 21000

ctx := context.Background()

t0 := time.Now()

var l2Block *L2Block
var err error
if l2BlockNumber == nil {
l2Block, err = s.GetLastL2Block(ctx, dbTx)
} else {
l2Block, err = s.GetL2BlockByNumber(ctx, *l2BlockNumber, dbTx)
}
if err != nil {
return 0, nil, err
}

t1 := time.Now()
getBlockTime := t1.Sub(t0)

batch, err := s.GetBatchByL2BlockNumber(ctx, l2Block.NumberU64(), dbTx)
if err != nil {
return 0, nil, err
}

t2 := time.Now()
getBatchTime := t2.Sub(t1)

forkID := s.GetForkIDByBatchNumber(batch.BatchNumber)
latestL2BlockNumber, err := s.GetLastL2BlockNumber(ctx, dbTx)
if err != nil {
return 0, nil, err
}

t3 := time.Now()
getForkIDTime := t3.Sub(t2)

loadedNonce, err := s.tree.GetNonce(ctx, senderAddress, l2Block.Root().Bytes())
if err != nil {
return 0, nil, err
}
nonce := loadedNonce.Uint64()

t4 := time.Now()
getNonceTime := t4.Sub(t3)

highEnd := MaxTxGasLimit

// if gas price is set, set the highEnd to the max amount
// of the account afford
isGasPriceSet := !isGasFreeSender && transaction.GasPrice().BitLen() != 0
if isGasPriceSet {
senderBalance, err := s.tree.GetBalance(ctx, senderAddress, l2Block.Root().Bytes())
if errors.Is(err, ErrNotFound) {
senderBalance = big.NewInt(0)
} else if err != nil {
return 0, nil, err
}

availableBalance := new(big.Int).Set(senderBalance)
// check if the account has funds to pay the transfer value
if transaction.Value() != nil {
if transaction.Value().Cmp(availableBalance) > 0 {
return 0, nil, ErrInsufficientFundsForTransfer
}

// deduct the value from the available balance
availableBalance.Sub(availableBalance, transaction.Value())
}

// Check the gas allowance for this account, make sure high end is capped to it
gasAllowance := new(big.Int).Div(availableBalance, transaction.GasPrice())
if gasAllowance.IsUint64() && highEnd > gasAllowance.Uint64() {
log.Debugf("Gas estimation high-end capped by allowance [%d]", gasAllowance.Uint64())
highEnd = gasAllowance.Uint64()
}
}

// if the tx gas is set and it is smaller than the highEnd,
// limit the highEnd to the maximum allowed by the tx gas
if transaction.Gas() != 0 && transaction.Gas() < highEnd {
highEnd = transaction.Gas()
}

// set start values for lowEnd and highEnd:
lowEnd, err := core.IntrinsicGas(transaction.Data(), transaction.AccessList(), s.isContractCreation(transaction), true, false, false)
if err != nil {
return 0, nil, err
}

// if the intrinsic gas is the same as the constant value for eth transfer
// and the transaction has a receiver address
if lowEnd == ethTransferGas && transaction.To() != nil {
receiver := *transaction.To()
// check if the receiver address is not a smart contract
code, err := s.tree.GetCode(ctx, receiver, l2Block.Root().Bytes())
if err != nil {
log.Warnf("error while getting code for address %v: %v", receiver.String(), err)
} else if len(code) == 0 {
// in case it is just an account, we can avoid the execution and return
// the transfer constant amount
return lowEnd, nil, nil
}
}

t5 := time.Now()
getEndTime := t5.Sub(t4)

// testTransaction runs the transaction with the specified gas value.
// it returns a status indicating if the transaction has failed, if it
// was reverted and the accompanying error

// Check if the highEnd is a good value to make the transaction pass, if it fails we
// can return immediately.
log.Debugf("Estimate gas. Trying to execute TX with %v gas", highEnd)
var estimationResult *testGasEstimationResult
if forkID < FORKID_ETROG {
estimationResult, err = s.internalTestGasEstimationTransactionV1(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, isGasFreeSender, highEnd, nonce, false)
} else {
estimationResult, err = s.internalTestGasEstimationTransactionV2(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, isGasFreeSender, highEnd, nonce, false)
}
if err != nil {
return 0, nil, err
}
if estimationResult.failed {
if estimationResult.reverted {
return 0, estimationResult.returnValue, estimationResult.executionError
}

if estimationResult.ooc {
return 0, nil, estimationResult.executionError
}

// The transaction shouldn't fail, for whatever reason, at highEnd
return 0, nil, fmt.Errorf(
"gas required exceeds allowance (%d)",
highEnd,
)
}

t6 := time.Now()
internalGasTime := t6.Sub(t5)

log.Infof("state-EstimateGas time. getBlock:%vms, getBatch:%vms, getForkID:%vms, getNonce:%vms, getEnd:%vms, internalGas:%vms",
getBlockTime.Milliseconds(), getBatchTime.Milliseconds(), getForkIDTime.Milliseconds(), getNonceTime.Milliseconds(), getEndTime.Milliseconds(), internalGasTime.Milliseconds())

return estimationResult.gasUsed, nil, nil
}

// internalTestGasEstimationTransactionV1 is used by the EstimateGas to test the tx execution
// during the binary search process to define the gas estimation of a given tx for l2 blocks
// before ETROG
Expand Down

0 comments on commit d9b288b

Please sign in to comment.