diff --git a/docker/common/start.sh b/docker/common/start.sh index aa9a25fa..6a72a4dc 100755 --- a/docker/common/start.sh +++ b/docker/common/start.sh @@ -174,7 +174,7 @@ if [[ "x${OASIS_DOCKER_NO_GATEWAY}" == "xyes" ]]; then notice "Skipping oasis-web3-gateway start-up...\n" else notice "Starting oasis-web3-gateway...\n" - ${OASIS_WEB3_GATEWAY_BINARY} --config ${OASIS_WEB3_GATEWAY_CONFIG_FILE} 2>1 &>/var/log/oasis-web3-gateway.log & + LOG__LEVEL=${OASIS_NODE_LOG_LEVEL} ${OASIS_WEB3_GATEWAY_BINARY} --config ${OASIS_WEB3_GATEWAY_CONFIG_FILE} 2>1 &>/var/log/oasis-web3-gateway.log & OASIS_WEB3_GATEWAY_PID=$! fi diff --git a/docker/sapphire-localnet/Dockerfile b/docker/sapphire-localnet/Dockerfile index b5a33012..eb700664 100644 --- a/docker/sapphire-localnet/Dockerfile +++ b/docker/sapphire-localnet/Dockerfile @@ -54,8 +54,8 @@ RUN wget https://github.com/oasisprotocol/oasis-web3-gateway/releases/download/v && strip -S -x /oasis_web3_gateway_${OASIS_WEB3_GATEWAY_VERSION}_linux_amd64/oasis-web3-gateway \ && mv /oasis_web3_gateway_${OASIS_WEB3_GATEWAY_VERSION}_linux_amd64/oasis-web3-gateway / \ && rm -rf /oasis_web3_gateway_${OASIS_WEB3_GATEWAY_VERSION}_linux_amd64* -#ENV OASIS_WEB3_GATEWAY_BRANCH=main -#RUN git clone https://github.com/oasisprotocol/oasis-web3-gateway.git oasis-web3-gateway-git --branch ${OASIS_WEB3_GATEWAY_BRANCH} --depth 1 \ +# ENV OASIS_WEB3_GATEWAY_BRANCH=main +# RUN git clone https://github.com/oasisprotocol/oasis-web3-gateway.git oasis-web3-gateway-git --branch ${OASIS_WEB3_GATEWAY_BRANCH} --depth 1 \ # && make -C oasis-web3-gateway-git \ # && strip -S -x /oasis-web3-gateway-git/oasis-web3-gateway \ # && mv /oasis-web3-gateway-git/oasis-web3-gateway / \ diff --git a/gas/backend.go b/gas/backend.go index e5b567c4..c35a661b 100644 --- a/gas/backend.go +++ b/gas/backend.go @@ -7,6 +7,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -21,7 +22,7 @@ import ( ) var ( - metricNodeMinPrice = promauto.NewGauge(prometheus.GaugeOpts{Name: "oasis_web3_gateway_gas_orcale_node_min_price", Help: "Min gas price periodically queried from the node."}) + metricNodeMinPrice = promauto.NewGauge(prometheus.GaugeOpts{Name: "oasis_web3_gateway_gas_oracle_node_min_price", Help: "Min gas price queried from the node."}) metricComputedPrice = promauto.NewGauge(prometheus.GaugeOpts{Name: "oasis_web3_gateway_gas_oracle_computed_price", Help: "Computed recommended gas price based on recent full blocks. -1 if none (no recent full blocks)."}) ) @@ -31,6 +32,9 @@ type Backend interface { // GasPrice returns the currently recommended minimum gas price. GasPrice() *hexutil.Big + + // FeeHistory returns the fee history of the last blocks and percentiles. + FeeHistory(blockCount uint64, lastBlock rpc.BlockNumber, percentiles []float64) *FeeHistoryResult } const ( @@ -94,6 +98,11 @@ type gasPriceOracle struct { // tracks the current index of the blockPrices rolling array.:w blockPricesCurrentIdx int + // protects feeHistoryData. + feeHistoryLock sync.RWMutex + // feeHistoryData contains the per block data needed to compute the fee history. + feeHistoryData []*feeHistoryBlockData + // Configuration parameters. windowSize uint64 fullBlockThreshold float64 @@ -130,7 +139,7 @@ func New(ctx context.Context, cfg *conf.GasConfig, blockWatcher indexer.BlockWat BaseBackgroundService: *service.NewBaseBackgroundService("gas-price-oracle"), ctx: ctxB, cancelCtx: cancelCtx, - blockPrices: make([]*quantity.Quantity, 0, windowSize), + feeHistoryData: make([]*feeHistoryBlockData, 0, feeHistoryWindowSize), windowSize: windowSize, fullBlockThreshold: blockFullThreshold, defaultGasPrice: minGasPrice, @@ -139,6 +148,11 @@ func New(ctx context.Context, cfg *conf.GasConfig, blockWatcher indexer.BlockWat coreClient: coreClient, } + g.blockPrices = make([]*quantity.Quantity, windowSize) + for i := range windowSize { + g.blockPrices[i] = quantity.NewQuantity() + } + return g } @@ -154,6 +168,7 @@ func (g *gasPriceOracle) Stop() { g.cancelCtx() } +// Implements Backend. func (g *gasPriceOracle) GasPrice() *hexutil.Big { g.priceLock.RLock() defer g.priceLock.RUnlock() @@ -231,6 +246,8 @@ func (g *gasPriceOracle) indexedBlockWatcher() { // Track price for the block. g.onBlock(blk.Block, blk.MedianTransactionGasPrice) + // Track fee history. + g.trackFeeHistory(blk.Block, blk.UniqueTxes, blk.Receipts) } } } @@ -240,58 +257,46 @@ func (g *gasPriceOracle) onBlock(b *model.Block, medTxPrice *quantity.Quantity) blockFull := (float64(b.Header.GasLimit) * g.fullBlockThreshold) <= float64(b.Header.GasUsed) if !blockFull { // Track 0 for non-full blocks. - g.trackPrice(quantity.NewFromUint64(0)) + g.trackPrice(quantity.NewQuantity()) return } + if medTxPrice == nil { g.Logger.Error("no med tx gas price for block", "block", b) return } - trackPrice := medTxPrice.Clone() if err := trackPrice.Add(&g.computedPriceMargin); err != nil { g.Logger.Error("failed to add minPriceEps to medTxPrice", "err", err) } - g.trackPrice(trackPrice) } func (g *gasPriceOracle) trackPrice(price *quantity.Quantity) { - // One item always gets added added to the prices array. - // Bump the current index for next iteration. - defer func() { - g.blockPricesCurrentIdx = (g.blockPricesCurrentIdx + 1) % int(g.windowSize) - }() - - // Recalculate the maximum median-price over the block window. - defer func() { - // Find maximum gas price. - maxPrice := quantity.NewFromUint64(0) - for _, price := range g.blockPrices { - if price.Cmp(maxPrice) > 0 { - maxPrice = price - } - } - - // No full blocks among last `windowSize` blocks. - if maxPrice.IsZero() { - g.priceLock.Lock() - g.computedGasPrice = nil - g.priceLock.Unlock() - metricComputedPrice.Set(float64(-1)) + g.blockPrices[g.blockPricesCurrentIdx] = price + g.blockPricesCurrentIdx = (g.blockPricesCurrentIdx + 1) % int(g.windowSize) - return + // Find maximum gas price. + maxPrice := quantity.NewQuantity() + for _, price := range g.blockPrices { + if price.Cmp(maxPrice) > 0 { + maxPrice = price } + } + + // No full blocks among last `windowSize` blocks. + if maxPrice.IsZero() { + maxPrice = nil + } - g.priceLock.Lock() - g.computedGasPrice = maxPrice - g.priceLock.Unlock() - metricComputedPrice.Set(float64(maxPrice.ToBigInt().Int64())) - }() + g.priceLock.Lock() + g.computedGasPrice = maxPrice + g.priceLock.Unlock() - if len(g.blockPrices) < int(g.windowSize) { - g.blockPrices = append(g.blockPrices, price) - return + // Report the computed price. + reportedPrice := float64(-1) + if maxPrice != nil { + reportedPrice = float64(maxPrice.ToBigInt().Int64()) } - g.blockPrices[g.blockPricesCurrentIdx] = price + metricComputedPrice.Set(reportedPrice) } diff --git a/gas/backend_test.go b/gas/backend_test.go index 70a299a7..22bafc55 100644 --- a/gas/backend_test.go +++ b/gas/backend_test.go @@ -126,6 +126,11 @@ func TestGasPriceOracle(t *testing.T) { // Default gas price should be returned by the oracle. require.EqualValues(defaultGasPrice.ToBigInt(), gasPriceOracle.GasPrice(), "oracle should return default gas price") + fh := gasPriceOracle.FeeHistory(10, 10, []float64{25, 50}) + require.EqualValues(0, fh.OldestBlock.ToInt().Int64(), "fee history should be empty") + require.Empty(0, fh.GasUsedRatio, "fee history should be empty") + require.Empty(0, fh.Reward, "fee history should be empty") + // Emit a non-full block. emitBlock(&emitter, false, nil) @@ -136,7 +141,7 @@ func TestGasPriceOracle(t *testing.T) { emitBlock(&emitter, true, quantity.NewFromUint64(1_000_000_000_000)) // 1000 gwei. // 1001 gwei should be returned. - require.EqualValues(big.NewInt(1_001_000_000_000), gasPriceOracle.GasPrice(), "oracle should return correct gas price") + require.EqualValues(big.NewInt(1_001_000_000_000), gasPriceOracle.GasPrice().ToInt(), "oracle should return correct gas price") // Emit a non-full block. emitBlock(&emitter, false, nil) diff --git a/gas/fee_history.go b/gas/fee_history.go new file mode 100644 index 00000000..bcf81d38 --- /dev/null +++ b/gas/fee_history.go @@ -0,0 +1,181 @@ +package gas + +import ( + "math/big" + "slices" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethMath "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/oasisprotocol/oasis-web3-gateway/db/model" +) + +// feeHistoryWindowSize is the number of recent blocks to store for the fee history query. +const feeHistoryWindowSize = 20 + +// FeeHistoryResult is the result of a fee history query. +type FeeHistoryResult struct { + OldestBlock *hexutil.Big `json:"oldestBlock"` + Reward [][]*hexutil.Big `json:"reward,omitempty"` + BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"` + GasUsedRatio []float64 `json:"gasUsedRatio"` +} + +type txGasAndReward struct { + gasUsed uint64 + reward *hexutil.Big +} + +type feeHistoryBlockData struct { + height uint64 + baseFee *hexutil.Big + gasUsedRatio float64 + gasUsed uint64 + + // Sorted list of transaction by reward. This is used to + // compute the queried percentiles in the fee history query. + sortedTxs []*txGasAndReward +} + +// rewards computes the queried reward percentiles for the given block data. +func (d *feeHistoryBlockData) rewards(percentiles []float64) []*hexutil.Big { + rewards := make([]*hexutil.Big, len(percentiles)) + + // No percentiles requested. + if len(percentiles) == 0 { + return rewards + } + + // No transactions in the block. + if len(d.sortedTxs) == 0 { + for i := range rewards { + rewards[i] = (*hexutil.Big)(common.Big0) + } + return rewards + } + + // Compute the requested percentiles. + var txIndex int + sumGasUsed := d.sortedTxs[0].gasUsed + for i, p := range percentiles { + thresholdGasUsed := uint64(float64(d.gasUsed) * p / 100) + for sumGasUsed < thresholdGasUsed && txIndex < len(d.sortedTxs)-1 { + txIndex++ + sumGasUsed += d.sortedTxs[txIndex].gasUsed + } + rewards[i] = d.sortedTxs[txIndex].reward + } + + return rewards +} + +// Implements Backend. +func (g *gasPriceOracle) FeeHistory(blockCount uint64, lastBlock rpc.BlockNumber, percentiles []float64) *FeeHistoryResult { + g.feeHistoryLock.RLock() + defer g.feeHistoryLock.RUnlock() + + // No history available. + if len(g.feeHistoryData) == 0 { + return &FeeHistoryResult{OldestBlock: (*hexutil.Big)(common.Big0)} + } + + // Find the latest block index. + var lastBlockIdx int + switch lastBlock { + case rpc.PendingBlockNumber, rpc.LatestBlockNumber, rpc.FinalizedBlockNumber, rpc.SafeBlockNumber: + // Latest available block. + lastBlockIdx = len(g.feeHistoryData) - 1 + case rpc.EarliestBlockNumber: + // Doesn't make sense to start at earliest block. + return &FeeHistoryResult{OldestBlock: (*hexutil.Big)(common.Big0)} + default: + // Check if the requested block number is available. + var found bool + for i, d := range g.feeHistoryData { + if d.height == uint64(lastBlock) { + lastBlockIdx = i + found = true + break + } + } + // Data for requested block number not available. + if !found { + return &FeeHistoryResult{OldestBlock: (*hexutil.Big)(common.Big0)} + } + } + + // Find the oldest block index. + var oldestBlockIdx int + if blockCount > uint64(lastBlockIdx) { + // Not enough blocks available, return all available blocks. + oldestBlockIdx = 0 + } else { + oldestBlockIdx = lastBlockIdx + 1 - int(blockCount) + } + + // Return the requested fee history. + result := &FeeHistoryResult{ + OldestBlock: (*hexutil.Big)(big.NewInt(int64(g.feeHistoryData[oldestBlockIdx].height))), + Reward: make([][]*hexutil.Big, lastBlockIdx-oldestBlockIdx+1), + BaseFee: make([]*hexutil.Big, lastBlockIdx-oldestBlockIdx+1), + GasUsedRatio: make([]float64, lastBlockIdx-oldestBlockIdx+1), + } + for i := oldestBlockIdx; i <= lastBlockIdx; i++ { + result.Reward[i-oldestBlockIdx] = g.feeHistoryData[i].rewards(percentiles) + result.BaseFee[i-oldestBlockIdx] = g.feeHistoryData[i].baseFee + result.GasUsedRatio[i-oldestBlockIdx] = g.feeHistoryData[i].gasUsedRatio + } + + return result +} + +func (g *gasPriceOracle) trackFeeHistory(block *model.Block, txs []*model.Transaction, receipts []*model.Receipt) { + // TODO: could populate old blocks on first received block (if available). + + d := &feeHistoryBlockData{ + height: block.Round, + gasUsed: block.Header.GasUsed, + gasUsedRatio: float64(block.Header.GasUsed) / float64(block.Header.GasLimit), + sortedTxs: make([]*txGasAndReward, len(receipts)), + } + + // Base fee. + var baseFee big.Int + if err := baseFee.UnmarshalText([]byte(block.Header.BaseFee)); err != nil { + g.Logger.Error("unmarshal base fee", "base_fee", block.Header.BaseFee, "block", block, "err", err) + return + } + d.baseFee = (*hexutil.Big)(&baseFee) + + // Transactions. + for i, tx := range txs { + var tipGas, feeCap big.Int + if err := feeCap.UnmarshalText([]byte(tx.GasFeeCap)); err != nil { + g.Logger.Error("unmarshal gas fee cap", "fee_cap", tx.GasFeeCap, "block", block, "tx", tx, "err", err) + return + } + if err := tipGas.UnmarshalText([]byte(tx.GasTipCap)); err != nil { + g.Logger.Error("unmarshal gas tip cap", "tip_cap", tx.GasTipCap, "block", block, "tx", tx, "err", err) + return + } + + d.sortedTxs[i] = &txGasAndReward{ + gasUsed: receipts[i].GasUsed, + reward: (*hexutil.Big)(ethMath.BigMin(&tipGas, &feeCap)), + } + } + slices.SortStableFunc(d.sortedTxs, func(a, b *txGasAndReward) int { + return a.reward.ToInt().Cmp(b.reward.ToInt()) + }) + + // Add new data to the history. + g.feeHistoryLock.Lock() + defer g.feeHistoryLock.Unlock() + // Delete oldest entry if we are at capacity. + if len(g.feeHistoryData) == feeHistoryWindowSize { + g.feeHistoryData = g.feeHistoryData[1:] + } + g.feeHistoryData = append(g.feeHistoryData, d) +} diff --git a/rpc/eth/api.go b/rpc/eth/api.go index 2eed2fb9..d69e56b1 100644 --- a/rpc/eth/api.go +++ b/rpc/eth/api.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" ethrpc "github.com/ethereum/go-ethereum/rpc" @@ -42,6 +43,7 @@ var ( ErrMalformedTransaction = errors.New("malformed transaction") ErrMalformedBlockNumber = errors.New("malformed blocknumber") ErrInvalidRequest = errors.New("invalid request") + ErrInvalidPercentile = errors.New("invalid reward percentile") // estimateGasSigSpec is a dummy signature spec used by the estimate gas method, as // otherwise transactions without signature would be underestimated. @@ -62,6 +64,8 @@ type API interface { ChainId() (*hexutil.Big, error) // GasPrice returns a suggestion for a gas price for legacy transactions. GasPrice(ctx context.Context) (*hexutil.Big, error) + // FeeHistory returns the transaction base fee per gas and effective priority fee per gas for the requested/supported block range. + FeeHistory(ctx context.Context, blockCount math.HexOrDecimal64, lastBlock ethrpc.BlockNumber, rewardPercentiles []float64) (*gas.FeeHistoryResult, error) // GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash. GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (hexutil.Uint, error) // GetTransactionCount returns the number of transactions the given address has sent for the given block number. @@ -290,6 +294,29 @@ func (api *publicAPI) GasPrice(_ context.Context) (*hexutil.Big, error) { return api.gasPriceOracle.GasPrice(), nil } +func (api *publicAPI) FeeHistory(_ context.Context, blockCount math.HexOrDecimal64, lastBlock ethrpc.BlockNumber, rewardPercentiles []float64) (*gas.FeeHistoryResult, error) { + logger := api.Logger.With("method", "eth_feeHistory", "block_count", blockCount, "last_block", lastBlock, "reward_percentiles", rewardPercentiles) + logger.Debug("request") + + // Validate blockCount. + if blockCount < 1 { + // Returning with no data and no error means there are no retrievable blocks. + return &gas.FeeHistoryResult{OldestBlock: (*hexutil.Big)(common.Big0)}, nil + } + + // Validate reward percentiles. + for i, p := range rewardPercentiles { + if p < 0 || p > 100 { + return nil, fmt.Errorf("%w: %f", ErrInvalidPercentile, p) + } + if i > 0 && p < rewardPercentiles[i-1] { + return nil, fmt.Errorf("%w: #%d:%f > #%d:%f", ErrInvalidPercentile, i-1, rewardPercentiles[i-1], i, p) + } + } + + return api.gasPriceOracle.FeeHistory(uint64(blockCount), lastBlock, rewardPercentiles), nil +} + func (api *publicAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (hexutil.Uint, error) { logger := api.Logger.With("method", "eth_getBlockTransactionCountByHash", "block_hash", blockHash.Hex()) logger.Debug("request") @@ -377,6 +404,11 @@ func (api *publicAPI) Call(ctx context.Context, args utils.TransactionArgs, bloc if args.GasPrice != nil { gasPrice = args.GasPrice.ToInt().Bytes() } + + if args.GasPrice == nil && args.MaxFeePerGas != nil { + // If EIP-1559 transaction, use the max fee per gas as the gas price. + gasPrice = args.MaxFeePerGas.ToInt().Bytes() + } if args.Gas != nil { gas = uint64(*args.Gas) } diff --git a/rpc/eth/metrics/api.go b/rpc/eth/metrics/api.go index 1d0930be..508dc118 100644 --- a/rpc/eth/metrics/api.go +++ b/rpc/eth/metrics/api.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" ethrpc "github.com/ethereum/go-ethereum/rpc" @@ -14,6 +15,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/logging" + "github.com/oasisprotocol/oasis-web3-gateway/gas" "github.com/oasisprotocol/oasis-web3-gateway/indexer" "github.com/oasisprotocol/oasis-web3-gateway/rpc/eth" "github.com/oasisprotocol/oasis-web3-gateway/rpc/metrics" @@ -146,6 +148,15 @@ func (m *metricsWrapper) GasPrice(ctx context.Context) (res *hexutil.Big, err er return } +// FeeHistory implements eth.API. +func (m *metricsWrapper) FeeHistory(ctx context.Context, blockCount math.HexOrDecimal64, lastBlock ethrpc.BlockNumber, rewardPercentiles []float64) (res *gas.FeeHistoryResult, err error) { + r, s, f, i, d := metrics.GetAPIMethodMetrics("eth_feeHistory") + defer metrics.InstrumentCaller(r, s, f, i, d, &err)() + + res, err = m.api.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) + return +} + // GetBalance implements eth.API. func (m *metricsWrapper) GetBalance(ctx context.Context, address common.Address, blockNrOrHash ethrpc.BlockNumberOrHash) (res *hexutil.Big, err error) { r, s, f, i, d := metrics.GetAPIMethodMetrics("eth_getBalance") diff --git a/rpc/metrics/metrics.go b/rpc/metrics/metrics.go index 1e2a1dc5..33363f32 100644 --- a/rpc/metrics/metrics.go +++ b/rpc/metrics/metrics.go @@ -24,7 +24,7 @@ func GetAPIMethodMetrics(method string) (prometheus.Counter, prometheus.Counter, // InstrumentCaller instruments the caller method. // -// Use InstrumentCaller is usually used the following way: +// The InstrumentCaller should be used the following way: // // func InstrumentMe() (err error) { // r, s, f, i, d := metrics.GetAPIMethodMetrics("method") diff --git a/rpc/utils/utils.go b/rpc/utils/utils.go index 8037baf6..17bd05c2 100644 --- a/rpc/utils/utils.go +++ b/rpc/utils/utils.go @@ -105,6 +105,7 @@ func ConvertToEthBlock(block *model.Block, fullTx bool) map[string]interface{} { "hash": common.HexToHash(block.Hash), "size": hexutil.Uint64(len(serialized)), "totalDifficulty": (*hexutil.Big)(big.NewInt(0)), + "baseFeePerGas": (*hexutil.Big)(header.BaseFee), } if len(header.Extra) == 0 { diff --git a/tests/rpc/rpc_test.go b/tests/rpc/rpc_test.go index a5b9e927..d608817c 100644 --- a/tests/rpc/rpc_test.go +++ b/tests/rpc/rpc_test.go @@ -146,6 +146,26 @@ func TestEth_GasPrice(t *testing.T) { t.Logf("gas price: %v", price) } +func TestEth_FeeHistory(t *testing.T) { + ec := localClient(t, false) + + ctx, cancel := context.WithTimeout(context.Background(), OasisBlockTimeout) + defer cancel() + + // Submit some test transactions. + for i := 0; i < 5; i++ { + receipt := submitTestTransaction(ctx, t) + require.EqualValues(t, 1, receipt.Status) + require.NotNil(t, receipt) + } + + // Query fee history. + feeHistory, err := ec.FeeHistory(context.Background(), 10, nil, []float64{0.25, 0.5, 0.75, 1}) + require.NoError(t, err, "get fee history") + + t.Logf("fee history: %v", feeHistory) +} + // TestEth_SendRawTransaction post eth raw transaction with ethclient from go-ethereum. func TestEth_SendRawTransaction(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), OasisBlockTimeout) diff --git a/tests/rpc/tx_test.go b/tests/rpc/tx_test.go index b81d0e26..e64dded2 100644 --- a/tests/rpc/tx_test.go +++ b/tests/rpc/tx_test.go @@ -298,6 +298,15 @@ func testContractCreation(t *testing.T, value *big.Int, txEIP int) uint64 { require.Positive(t, balanceAfter.Cmp(minBalanceAfter)) } + // Query fee history. + feeHistory, err := ec.FeeHistory(context.Background(), 1, receipt.BlockNumber, []float64{50, 100}) + require.NoError(t, err) + require.Len(t, feeHistory.BaseFee, 1, "fee history should have one base fee") + require.Len(t, feeHistory.GasUsedRatio, 1, "fee history should have one gas used ratio") + require.Len(t, feeHistory.Reward, 1, "fee history should have one rewards array") + require.EqualValues(t, feeHistory.OldestBlock, receipt.BlockNumber.Uint64(), "fee history for correct block should be returned") + require.EqualValues(t, 10, feeHistory.BaseFee[0].Int64(), "correct base fee should be returned") + return receipt.Status }