Skip to content

Commit

Permalink
add zkevm_EstimateFee endpoint (#3000)
Browse files Browse the repository at this point in the history
  • Loading branch information
agnusmor authored Dec 29, 2023
1 parent 26136d9 commit 59b9e31
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 3 deletions.
2 changes: 1 addition & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ func runJSONRPCServer(c config.Config, etherman *etherman.Client, chainID uint64
if _, ok := apis[jsonrpc.APIZKEVM]; ok {
services = append(services, jsonrpc.Service{
Name: jsonrpc.APIZKEVM,
Service: jsonrpc.NewZKEVMEndpoints(c.RPC, st, etherman),
Service: jsonrpc.NewZKEVMEndpoints(c.RPC, pool, st, etherman),
})
}

Expand Down
116 changes: 115 additions & 1 deletion jsonrpc/endpoints_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,26 @@ import (
"github.com/0xPolygonHermez/zkevm-node/jsonrpc/types"
"github.com/0xPolygonHermez/zkevm-node/log"
"github.com/0xPolygonHermez/zkevm-node/state"
"github.com/0xPolygonHermez/zkevm-node/state/runtime"
"github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/jackc/pgx/v4"
)

// ZKEVMEndpoints contains implementations for the "zkevm" RPC endpoints
type ZKEVMEndpoints struct {
cfg Config
pool types.PoolInterface
state types.StateInterface
etherman types.EthermanInterface
txMan DBTxManager
}

// NewZKEVMEndpoints returns ZKEVMEndpoints
func NewZKEVMEndpoints(cfg Config, state types.StateInterface, etherman types.EthermanInterface) *ZKEVMEndpoints {
func NewZKEVMEndpoints(cfg Config, pool types.PoolInterface, state types.StateInterface, etherman types.EthermanInterface) *ZKEVMEndpoints {
return &ZKEVMEndpoints{
cfg: cfg,
pool: pool,
state: state,
etherman: etherman,
}
Expand Down Expand Up @@ -288,3 +292,113 @@ func (z *ZKEVMEndpoints) GetNativeBlockHashesInRange(filter NativeBlockHashBlock
return nativeBlockHashes, nil
})
}

// EstimateFee returns an estimate fee for the transaction.
func (z *ZKEVMEndpoints) EstimateFee(arg *types.TxArgs, blockArg *types.BlockNumberOrHash) (interface{}, types.Error) {
return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, types.Error) {
if arg == nil {
return RPCErrorResponse(types.InvalidParamsErrorCode, "missing value for required argument 0", nil, false)
}

block, respErr := z.getBlockByArg(ctx, blockArg, dbTx)
if respErr != nil {
return nil, respErr
}

var blockToProcess *uint64
if blockArg != nil {
blockNumArg := blockArg.Number()
if blockNumArg != nil && (*blockArg.Number() == types.LatestBlockNumber || *blockArg.Number() == types.PendingBlockNumber) {
blockToProcess = nil
} else {
n := block.NumberU64()
blockToProcess = &n
}
}

defaultSenderAddress := common.HexToAddress(state.DefaultSenderAddress)
sender, tx, err := arg.ToTransaction(ctx, z.state, z.cfg.MaxCumulativeGasUsed, block.Root(), defaultSenderAddress, dbTx)
if err != nil {
return RPCErrorResponse(types.DefaultErrorCode, "failed to convert arguments into an unsigned transaction", err, false)
}

gasEstimation, returnValue, err := z.state.EstimateGas(tx, sender, blockToProcess, dbTx)
if errors.Is(err, runtime.ErrExecutionReverted) {
data := make([]byte, len(returnValue))
copy(data, returnValue)
return nil, types.NewRPCErrorWithData(types.RevertedErrorCode, err.Error(), &data)
} else if err != nil {
errMsg := fmt.Sprintf("failed to estimate gas: %v", err.Error())
return nil, types.NewRPCError(types.DefaultErrorCode, errMsg)
}

gasPrices, err := z.pool.GetGasPrices(ctx)
if err != nil {
return RPCErrorResponse(types.DefaultErrorCode, "failed to get L2 gas price", err, false)
}

txGasPrice := new(big.Int).SetUint64(gasPrices.L2GasPrice) // by default we assume the tx gas price is the current L2 gas price
egpEnabled := z.pool.EffectiveGasPriceEnabled()

if egpEnabled {
rawTx, err := state.EncodeTransactionWithoutEffectivePercentage(*tx)
if err != nil {
return RPCErrorResponse(types.DefaultErrorCode, "failed to encode tx", err, false)
}

txEGP, err := z.pool.CalculateEffectiveGasPrice(rawTx, txGasPrice, gasEstimation, gasPrices.L1GasPrice, gasPrices.L2GasPrice)
if err != nil {
return RPCErrorResponse(types.DefaultErrorCode, "failed to calculate effective gas price", err, false)
}

if txEGP.Cmp(txGasPrice) == -1 { // txEGP < txGasPrice
txGasPrice = txEGP
}

log.Infof("[EstimateFee] gasPrice: %d, effectiveGasPrice: %d, l2GasPrice: %d, len: %d, gas: %d, l1GasPrice: %d",
txGasPrice, txEGP, gasPrices.L2GasPrice, len(rawTx), gasEstimation, gasPrices.L1GasPrice)
}

fee := new(big.Int).Mul(txGasPrice, new(big.Int).SetUint64(gasEstimation))

log.Infof("[EstimateFee] egpEnabled: %t, fee: %d, gasPrice: %d, gas: %d", egpEnabled, fee, txGasPrice, gasEstimation)

return hex.EncodeBig(fee), nil
})
}

func (z *ZKEVMEndpoints) getBlockByArg(ctx context.Context, blockArg *types.BlockNumberOrHash, dbTx pgx.Tx) (*ethTypes.Block, types.Error) {
// If no block argument is provided, return the latest block
if blockArg == nil {
block, err := z.state.GetLastL2Block(ctx, dbTx)
if err != nil {
return nil, types.NewRPCError(types.DefaultErrorCode, "failed to get the last block number from state")
}
return block, nil
}

// If we have a block hash, try to get the block by hash
if blockArg.IsHash() {
block, err := z.state.GetL2BlockByHash(ctx, blockArg.Hash().Hash(), dbTx)
if errors.Is(err, state.ErrNotFound) {
return nil, types.NewRPCError(types.DefaultErrorCode, "header for hash not found")
} else if err != nil {
return nil, types.NewRPCError(types.DefaultErrorCode, fmt.Sprintf("failed to get block by hash %v", blockArg.Hash().Hash()))
}
return block, nil
}

// Otherwise, try to get the block by number
blockNum, rpcErr := blockArg.Number().GetNumericBlockNumber(ctx, z.state, z.etherman, dbTx)
if rpcErr != nil {
return nil, rpcErr
}
block, err := z.state.GetL2BlockByNumber(context.Background(), blockNum, dbTx)
if errors.Is(err, state.ErrNotFound) || block == nil {
return nil, types.NewRPCError(types.DefaultErrorCode, "header not found")
} else if err != nil {
return nil, types.NewRPCError(types.DefaultErrorCode, fmt.Sprintf("failed to get block by number %v", blockNum))
}

return block, nil
}
41 changes: 41 additions & 0 deletions jsonrpc/mocks/mock_pool.go

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

2 changes: 1 addition & 1 deletion jsonrpc/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func newMockedServer(t *testing.T, cfg Config) (*mockedServer, *mocksWrapper, *e
if _, ok := apis[APIZKEVM]; ok {
services = append(services, Service{
Name: APIZKEVM,
Service: NewZKEVMEndpoints(cfg, st, etherman),
Service: NewZKEVMEndpoints(cfg, pool, st, etherman),
})
}

Expand Down
2 changes: 2 additions & 0 deletions jsonrpc/types/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type PoolInterface interface {
GetPendingTxs(ctx context.Context, limit uint64) ([]pool.Transaction, error)
CountPendingTransactions(ctx context.Context) (uint64, error)
GetTxByHash(ctx context.Context, hash common.Hash) (*pool.Transaction, error)
CalculateEffectiveGasPrice(rawTx []byte, txGasPrice *big.Int, txGasUsed uint64, l1GasPrice uint64, l2GasPrice uint64) (*big.Int, error)
EffectiveGasPriceEnabled() bool
}

// StateInterface gathers the methods required to interact with the state.
Expand Down
10 changes: 10 additions & 0 deletions pool/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,16 @@ const (
txDataZeroGas uint64 = 4
)

// CalculateEffectiveGasPrice calculates the final effective gas price for a tx
func (p *Pool) CalculateEffectiveGasPrice(rawTx []byte, txGasPrice *big.Int, txGasUsed uint64, l1GasPrice uint64, l2GasPrice uint64) (*big.Int, error) {
return p.effectiveGasPrice.CalculateEffectiveGasPrice(rawTx, txGasPrice, txGasUsed, l1GasPrice, l2GasPrice)
}

// EffectiveGasPriceEnabled returns if effective gas price calculation is enabled or not
func (p *Pool) EffectiveGasPriceEnabled() bool {
return p.effectiveGasPrice.IsEnabled()
}

// IntrinsicGas computes the 'intrinsic gas' for a given transaction.
func IntrinsicGas(tx types.Transaction) (uint64, error) {
// Set the starting gas for the raw transaction
Expand Down

0 comments on commit 59b9e31

Please sign in to comment.