package itests import ( "context" "encoding/json" "sort" "testing" "time" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/lib/result" "github.com/filecoin-project/lotus/node/impl/full" ) // calculateExpectations calculates the expected number of items to be included in the response // of eth_feeHistory. It takes care of null rounds by finding the closet tipset with height // smaller than startHeight, and then looks back at requestAmount of items. It also considers // scenarios where there are not enough items to look back. func calculateExpectations(tsHeights []int, requestAmount, startHeight int) (count, oldestHeight int) { latestIdx := sort.SearchInts(tsHeights, startHeight) // SearchInts returns the index of the number that's larger than the target if the target // doesn't exist. However, we're looking for the closet number that's smaller that the target for tsHeights[latestIdx] > startHeight { latestIdx-- } cnt := requestAmount oldestIdx := latestIdx - requestAmount + 1 if oldestIdx < 0 { cnt = latestIdx + 1 oldestIdx = 0 } return cnt, tsHeights[oldestIdx] } func TestEthFeeHistory(t *testing.T) { require := require.New(t) kit.QuietAllLogsExcept() blockTime := 100 * time.Millisecond client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() miner := ens.InterconnectAll().BeginMining(blockTime) client.WaitTillChain(ctx, kit.HeightAtLeast(7)) miner[0].InjectNulls(abi.ChainEpoch(5)) // Wait for the network to create at least 20 tipsets client.WaitTillChain(ctx, kit.HeightAtLeast(20)) for _, m := range miner { m.Pause() } ch, err := client.ChainNotify(ctx) require.NoError(err) // Wait for 5 seconds of inactivity func() { for { select { case <-ch: continue case <-time.After(5 * time.Second): return } } }() currTs, err := client.ChainHead(ctx) require.NoError(err) var tsHeights []int for currTs.Height() != 0 { tsHeights = append(tsHeights, int(currTs.Height())) currTs, err = client.ChainGetTipSet(ctx, currTs.Parents()) require.NoError(err) } sort.Ints(tsHeights) // because of the deferred execution, the last tipset is not executed yet, // and the one before the last one is the last executed tipset, // which corresponds to the "latest" tag in EthGetBlockByNumber latestBlk := ethtypes.EthUint64(tsHeights[len(tsHeights)-2]) blk, err := client.EthGetBlockByNumber(ctx, "latest", false) require.NoError(err) require.Equal(blk.Number, latestBlk) assertHistory := func(history *ethtypes.EthFeeHistory, requestAmount, startHeight int) { amount, oldest := calculateExpectations(tsHeights, requestAmount, startHeight) require.Equal(amount+1, len(history.BaseFeePerGas)) require.Equal(amount, len(history.GasUsedRatio)) require.Equal(ethtypes.EthUint64(oldest), history.OldestBlock) } history, err := client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( json.Marshal([]interface{}{5, "0x10"}), ).Assert(require.NoError)) require.NoError(err) assertHistory(&history, 5, 16) require.Nil(history.Reward) history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( json.Marshal([]interface{}{"5", "0x10"}), ).Assert(require.NoError)) require.NoError(err) assertHistory(&history, 5, 16) require.Nil(history.Reward) history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( json.Marshal([]interface{}{5, "latest"}), ).Assert(require.NoError)) require.NoError(err) assertHistory(&history, 5, int(latestBlk)) require.Nil(history.Reward) history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( json.Marshal([]interface{}{"0x10", "0x12"}), ).Assert(require.NoError)) require.NoError(err) assertHistory(&history, 16, 18) require.Nil(history.Reward) history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( json.Marshal([]interface{}{5, "0x10"}), ).Assert(require.NoError)) require.NoError(err) assertHistory(&history, 5, 16) require.Nil(history.Reward) history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( json.Marshal([]interface{}{5, "10"}), ).Assert(require.NoError)) require.NoError(err) assertHistory(&history, 5, 10) require.Nil(history.Reward) // test when the requested number of blocks is longer than chain length history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( json.Marshal([]interface{}{"0x30", "latest"}), ).Assert(require.NoError)) require.NoError(err) assertHistory(&history, 48, int(latestBlk)) require.Nil(history.Reward) // test when the requested number of blocks is longer than chain length history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( json.Marshal([]interface{}{"0x30", "10"}), ).Assert(require.NoError)) require.NoError(err) assertHistory(&history, 48, 10) require.Nil(history.Reward) history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( json.Marshal([]interface{}{5, "10", &[]float64{25, 50, 75}}), ).Assert(require.NoError)) require.NoError(err) assertHistory(&history, 5, 10) require.NotNil(history.Reward) require.Equal(5, len(*history.Reward)) for _, arr := range *history.Reward { require.Equal(3, len(arr)) for _, item := range arr { require.Equal(ethtypes.EthBigInt(types.NewInt(full.MinGasPremium)), item) } } history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( json.Marshal([]interface{}{1025, "10", &[]float64{25, 50, 75}}), ).Assert(require.NoError)) require.Error(err) history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( json.Marshal([]interface{}{5, "10", &[]float64{75, 50}}), ).Assert(require.NoError)) require.Error(err) history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( json.Marshal([]interface{}{5, "10", &[]float64{}}), ).Assert(require.NoError)) require.NoError(err) }