diff --git a/docs/json-rpc-endpoints.md b/docs/json-rpc-endpoints.md index ec68b7eb51..a497c43046 100644 --- a/docs/json-rpc-endpoints.md +++ b/docs/json-rpc-endpoints.md @@ -62,14 +62,17 @@ If the endpoint is not in the list below, it means this specific endpoint is not - `zkevm_batchNumber` - `zkevm_batchNumberByBlockNumber` - `zkevm_consolidatedBlockNumber` +- `zkevm_estimateFee` +- `zkevm_estimateGasPrice` - `zkevm_getBatchByNumber` +- `zkevm_getExitRootsByGER` - `zkevm_getFullBlockByHash` - `zkevm_getFullBlockByNumber` +- `zkevm_getLatestGlobalExitRoot` - `zkevm_getNativeBlockHashesInRange` +- `zkevm_getTransactionByL2Hash` +- `zkevm_getTransactionReceiptByL2Hash` - `zkevm_isBlockConsolidated` - `zkevm_isBlockVirtualized` - `zkevm_verifiedBatchNumber` - `zkevm_virtualBatchNumber` -- `zkevm_getTransactionByL2Hash` -- `zkevm_getTransactionReceiptByL2Hash` -- `zkevm_getExitRootsByGER` diff --git a/jsonrpc/endpoints_zkevm.go b/jsonrpc/endpoints_zkevm.go index ab82cc476f..73da646643 100644 --- a/jsonrpc/endpoints_zkevm.go +++ b/jsonrpc/endpoints_zkevm.go @@ -427,87 +427,107 @@ func (z *ZKEVMEndpoints) GetExitRootsByGER(globalExitRoot common.Hash) (interfac }) } +// EstimateGasPrice returns an estimate gas price for the transaction. +func (z *ZKEVMEndpoints) EstimateGasPrice(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) { + gasPrice, _, err := z.internalEstimateGasPriceAndFee(ctx, arg, blockArg, dbTx) + if err != nil { + return nil, err + } + return hex.EncodeBig(gasPrice), 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) + _, fee, err := z.internalEstimateGasPriceAndFee(ctx, arg, blockArg, dbTx) + if err != nil { + return nil, err } + return hex.EncodeBig(fee), nil + }) +} - block, respErr := z.getBlockByArg(ctx, blockArg, dbTx) - if respErr != nil { - return nil, respErr - } +// internalEstimateGasPriceAndFee computes the estimated gas price and the estimated fee for the transaction +func (z *ZKEVMEndpoints) internalEstimateGasPriceAndFee(ctx context.Context, arg *types.TxArgs, blockArg *types.BlockNumberOrHash, dbTx pgx.Tx) (*big.Int, *big.Int, types.Error) { + if arg == nil { + return nil, nil, types.NewRPCError(types.InvalidParamsErrorCode, "missing value for required argument 0") + } - 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 - } - } + block, respErr := z.getBlockByArg(ctx, blockArg, dbTx) + if respErr != nil { + return nil, nil, respErr + } - 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) + 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 } + } - 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) - } + defaultSenderAddress := common.HexToAddress(state.DefaultSenderAddress) + sender, tx, err := arg.ToTransaction(ctx, z.state, z.cfg.MaxCumulativeGasUsed, block.Root(), defaultSenderAddress, dbTx) + if err != nil { + return nil, nil, types.NewRPCError(types.DefaultErrorCode, "failed to convert arguments into an unsigned transaction") + } + + 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, nil, types.NewRPCErrorWithData(types.RevertedErrorCode, err.Error(), data) + } else if err != nil { + errMsg := fmt.Sprintf("failed to estimate gas: %v", err.Error()) + return nil, nil, types.NewRPCError(types.DefaultErrorCode, errMsg) + } + + gasPrices, err := z.pool.GetGasPrices(ctx) + if err != nil { + return nil, nil, types.NewRPCError(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 + txEGPPct := state.MaxEffectivePercentage + egpEnabled := z.pool.EffectiveGasPriceEnabled() - gasPrices, err := z.pool.GetGasPrices(ctx) + if egpEnabled { + rawTx, err := state.EncodeTransactionWithoutEffectivePercentage(*tx) if err != nil { - return RPCErrorResponse(types.DefaultErrorCode, "failed to get L2 gas price", err, false) + return nil, nil, types.NewRPCError(types.DefaultErrorCode, "failed to encode tx", err, false) } - txGasPrice := new(big.Int).SetUint64(gasPrices.L2GasPrice) // by default we assume the tx gas price is the current L2 gas price - txEGPPct := state.MaxEffectivePercentage - 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 nil, nil, types.NewRPCError(types.DefaultErrorCode, "failed to calculate effective gas price", err, false) + } - txEGP, err := z.pool.CalculateEffectiveGasPrice(rawTx, txGasPrice, gasEstimation, gasPrices.L1GasPrice, gasPrices.L2GasPrice) + if txEGP.Cmp(txGasPrice) == -1 { // txEGP < txGasPrice + // We need to "round" the final effectiveGasPrice to a 256 fraction of the txGasPrice + txEGPPct, err = z.pool.CalculateEffectiveGasPricePercentage(txGasPrice, txEGP) if err != nil { - return RPCErrorResponse(types.DefaultErrorCode, "failed to calculate effective gas price", err, false) - } - - if txEGP.Cmp(txGasPrice) == -1 { // txEGP < txGasPrice - // We need to "round" the final effectiveGasPrice to a 256 fraction of the txGasPrice - txEGPPct, err = z.pool.CalculateEffectiveGasPricePercentage(txGasPrice, txEGP) - if err != nil { - return RPCErrorResponse(types.DefaultErrorCode, "failed to calculate effective gas price percentage", err, false) - } - // txGasPriceFraction = txGasPrice/256 - txGasPriceFraction := new(big.Int).Div(txGasPrice, new(big.Int).SetUint64(256)) //nolint:gomnd - // txGasPrice = txGasPriceFraction*(txEGPPct+1) - txGasPrice = new(big.Int).Mul(txGasPriceFraction, new(big.Int).SetUint64(uint64(txEGPPct+1))) + return nil, nil, types.NewRPCError(types.DefaultErrorCode, "failed to calculate effective gas price percentage", err, false) } - - log.Infof("[EstimateFee] finalGasPrice: %d, effectiveGasPrice: %d, egpPct: %d, l2GasPrice: %d, len: %d, gas: %d, l1GasPrice: %d", - txGasPrice, txEGP, txEGPPct, gasPrices.L2GasPrice, len(rawTx), gasEstimation, gasPrices.L1GasPrice) + // txGasPriceFraction = txGasPrice/256 + txGasPriceFraction := new(big.Int).Div(txGasPrice, new(big.Int).SetUint64(256)) //nolint:gomnd + // txGasPrice = txGasPriceFraction*(txEGPPct+1) + txGasPrice = new(big.Int).Mul(txGasPriceFraction, new(big.Int).SetUint64(uint64(txEGPPct+1))) } - fee := new(big.Int).Mul(txGasPrice, new(big.Int).SetUint64(gasEstimation)) + log.Infof("[internalEstimateGasPriceAndFee] finalGasPrice: %d, effectiveGasPrice: %d, egpPct: %d, l2GasPrice: %d, len: %d, gas: %d, l1GasPrice: %d", + txGasPrice, txEGP, txEGPPct, gasPrices.L2GasPrice, len(rawTx), gasEstimation, gasPrices.L1GasPrice) + } - log.Infof("[EstimateFee] egpEnabled: %t, fee: %d, gasPrice: %d, gas: %d", egpEnabled, fee, txGasPrice, gasEstimation) + fee := new(big.Int).Mul(txGasPrice, new(big.Int).SetUint64(gasEstimation)) - return hex.EncodeBig(fee), nil - }) + log.Infof("[internalEstimateGasPriceAndFee] egpEnabled: %t, fee: %d, gasPrice: %d, gas: %d", egpEnabled, fee, txGasPrice, gasEstimation) + + return txGasPrice, fee, nil } func (z *ZKEVMEndpoints) getBlockByArg(ctx context.Context, blockArg *types.BlockNumberOrHash, dbTx pgx.Tx) (*state.L2Block, types.Error) { diff --git a/jsonrpc/endpoints_zkevm.openrpc.json b/jsonrpc/endpoints_zkevm.openrpc.json index 36216cc09d..c8eaa3481f 100644 --- a/jsonrpc/endpoints_zkevm.openrpc.json +++ b/jsonrpc/endpoints_zkevm.openrpc.json @@ -423,6 +423,38 @@ "$ref": "#/components/schemas/Keccak" } } + }, + { + "name": "zkevm_estimateFee", + "summary": "Estimates the transaction Fee following the effective gas price rules", + "params": [ + { + "$ref": "#/components/contentDescriptors/Transaction" + } + ], + "result": { + "name": "fee", + "description": "The amount of the fee", + "schema": { + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "name": "zkevm_estimateGasPrice", + "summary": "Estimates the transaction Gas Price following the effective gas price rules", + "params": [ + { + "$ref": "#/components/contentDescriptors/Transaction" + } + ], + "result": { + "name": "gasPrice", + "description": "The amount of gas price", + "schema": { + "$ref": "#/components/schemas/Integer" + } + } } ], "components": { @@ -472,6 +504,13 @@ "$ref": "#/components/schemas/Block" } }, + "Transaction": { + "required": true, + "name": "transaction", + "schema": { + "$ref": "#/components/schemas/Transaction" + } + }, "TransactionHash": { "name": "transactionHash", "required": true,