github.com/iotexproject/iotex-core@v1.14.1-rc1/gasstation/gasstattion_test.go (about)

     1  // Copyright (c) 2019 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package gasstation
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"math/big"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/golang/mock/gomock"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	"github.com/iotexproject/iotex-core/action"
    19  	"github.com/iotexproject/iotex-core/action/protocol"
    20  	"github.com/iotexproject/iotex-core/action/protocol/account"
    21  	accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util"
    22  	"github.com/iotexproject/iotex-core/action/protocol/execution"
    23  	"github.com/iotexproject/iotex-core/action/protocol/rewarding"
    24  	"github.com/iotexproject/iotex-core/action/protocol/rolldpos"
    25  	"github.com/iotexproject/iotex-core/actpool"
    26  	"github.com/iotexproject/iotex-core/blockchain"
    27  	"github.com/iotexproject/iotex-core/blockchain/block"
    28  	"github.com/iotexproject/iotex-core/blockchain/blockdao"
    29  	"github.com/iotexproject/iotex-core/blockchain/filedao"
    30  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    31  	"github.com/iotexproject/iotex-core/db"
    32  	"github.com/iotexproject/iotex-core/pkg/unit"
    33  	"github.com/iotexproject/iotex-core/state/factory"
    34  	"github.com/iotexproject/iotex-core/test/identityset"
    35  	"github.com/iotexproject/iotex-core/test/mock/mock_blockchain"
    36  	"github.com/iotexproject/iotex-core/test/mock/mock_blockdao"
    37  	"github.com/iotexproject/iotex-core/testutil"
    38  )
    39  
    40  type (
    41  	testConfig struct {
    42  		Genesis    genesis.Genesis
    43  		Chain      blockchain.Config
    44  		ActPool    actpool.Config
    45  		GasStation Config
    46  	}
    47  	testActionGas []struct {
    48  		gasPrice    uint64
    49  		gasConsumed uint64
    50  	}
    51  	testCase struct {
    52  		name           string
    53  		blocks         []testActionGas
    54  		expectGasPrice uint64
    55  	}
    56  )
    57  
    58  func TestNewGasStation(t *testing.T) {
    59  	require := require.New(t)
    60  	require.NotNil(NewGasStation(nil, nil, DefaultConfig))
    61  }
    62  
    63  func newTestConfig() testConfig {
    64  	cfg := testConfig{
    65  		Genesis:    genesis.Default,
    66  		Chain:      blockchain.DefaultConfig,
    67  		ActPool:    actpool.DefaultConfig,
    68  		GasStation: DefaultConfig,
    69  	}
    70  	cfg.Genesis.BlockGasLimit = uint64(100000)
    71  	cfg.Genesis.EnableGravityChainVoting = false
    72  
    73  	return cfg
    74  }
    75  
    76  func TestSuggestGasPriceForUserAction(t *testing.T) {
    77  	ctx := context.Background()
    78  	cfg := newTestConfig()
    79  	registry := protocol.NewRegistry()
    80  	acc := account.NewProtocol(rewarding.DepositGas)
    81  	require.NoError(t, acc.Register(registry))
    82  	rp := rolldpos.NewProtocol(cfg.Genesis.NumCandidateDelegates, cfg.Genesis.NumDelegates, cfg.Genesis.NumSubEpochs)
    83  	require.NoError(t, rp.Register(registry))
    84  	factoryCfg := factory.GenerateConfig(cfg.Chain, cfg.Genesis)
    85  	sf, err := factory.NewFactory(factoryCfg, db.NewMemKVStore(), factory.RegistryOption(registry))
    86  	require.NoError(t, err)
    87  	ap, err := actpool.NewActPool(cfg.Genesis, sf, cfg.ActPool)
    88  	require.NoError(t, err)
    89  	store, err := filedao.NewFileDAOInMemForTest()
    90  	require.NoError(t, err)
    91  	blkMemDao := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf}, 16)
    92  	bc := blockchain.NewBlockchain(
    93  		cfg.Chain,
    94  		cfg.Genesis,
    95  		blkMemDao,
    96  		factory.NewMinter(sf, ap),
    97  		blockchain.BlockValidatorOption(block.NewValidator(
    98  			sf,
    99  			protocol.NewGenericValidator(sf, accountutil.AccountState),
   100  		)),
   101  	)
   102  	ep := execution.NewProtocol(blkMemDao.GetBlockHash, rewarding.DepositGasWithSGD, nil, func(u uint64) (time.Time, error) { return time.Time{}, nil })
   103  	require.NoError(t, ep.Register(registry))
   104  	rewardingProtocol := rewarding.NewProtocol(cfg.Genesis.Rewarding)
   105  	require.NoError(t, rewardingProtocol.Register(registry))
   106  	require.NoError(t, bc.Start(ctx))
   107  	defer func() {
   108  		require.NoError(t, bc.Stop(ctx))
   109  	}()
   110  
   111  	for i := 0; i < 30; i++ {
   112  		tsf, err := action.NewTransfer(
   113  			uint64(i)+1,
   114  			big.NewInt(100),
   115  			identityset.Address(27).String(),
   116  			[]byte{}, uint64(100000),
   117  			big.NewInt(1).Mul(big.NewInt(int64(i)+10), big.NewInt(unit.Qev)),
   118  		)
   119  		require.NoError(t, err)
   120  
   121  		bd := &action.EnvelopeBuilder{}
   122  		elp1 := bd.SetAction(tsf).
   123  			SetNonce(uint64(i) + 1).
   124  			SetGasLimit(100000).
   125  			SetGasPrice(big.NewInt(1).Mul(big.NewInt(int64(i)+10), big.NewInt(unit.Qev))).Build()
   126  		selp1, err := action.Sign(elp1, identityset.PrivateKey(0))
   127  		require.NoError(t, err)
   128  
   129  		require.NoError(t, ap.Add(context.Background(), selp1))
   130  
   131  		blk, err := bc.MintNewBlock(testutil.TimestampNow())
   132  		require.NoError(t, err)
   133  		require.Equal(t, 2, len(blk.Actions))
   134  		require.Equal(t, 2, len(blk.Receipts))
   135  		var gasConsumed uint64
   136  		for _, receipt := range blk.Receipts {
   137  			gasConsumed += receipt.GasConsumed
   138  		}
   139  		require.True(t, gasConsumed <= cfg.Genesis.BlockGasLimit)
   140  		require.NoError(t, bc.CommitBlock(blk))
   141  	}
   142  	height := bc.TipHeight()
   143  	fmt.Printf("Open blockchain pass, height = %d\n", height)
   144  
   145  	gs := NewGasStation(bc, blkMemDao, cfg.GasStation)
   146  	require.NotNil(t, gs)
   147  
   148  	gp, err := gs.SuggestGasPrice()
   149  	require.NoError(t, err)
   150  	// i from 10 to 29,gasprice for 20 to 39,60%*20+20=31
   151  	require.Equal(
   152  		t,
   153  		big.NewInt(1).Mul(big.NewInt(int64(31)), big.NewInt(unit.Qev)).Uint64()*9/10,
   154  		gp,
   155  	)
   156  }
   157  
   158  func TestSuggestGasPriceForSystemAction(t *testing.T) {
   159  	ctx := context.Background()
   160  	cfg := newTestConfig()
   161  	registry := protocol.NewRegistry()
   162  	acc := account.NewProtocol(rewarding.DepositGas)
   163  	require.NoError(t, acc.Register(registry))
   164  	rp := rolldpos.NewProtocol(cfg.Genesis.NumCandidateDelegates, cfg.Genesis.NumDelegates, cfg.Genesis.NumSubEpochs)
   165  	require.NoError(t, rp.Register(registry))
   166  	factoryCfg := factory.GenerateConfig(cfg.Chain, cfg.Genesis)
   167  	sf, err := factory.NewFactory(factoryCfg, db.NewMemKVStore(), factory.RegistryOption(registry))
   168  	require.NoError(t, err)
   169  	ap, err := actpool.NewActPool(cfg.Genesis, sf, cfg.ActPool)
   170  	require.NoError(t, err)
   171  	store, err := filedao.NewFileDAOInMemForTest()
   172  	require.NoError(t, err)
   173  	blkMemDao := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf}, 16)
   174  	bc := blockchain.NewBlockchain(
   175  		cfg.Chain,
   176  		cfg.Genesis,
   177  		blkMemDao,
   178  		factory.NewMinter(sf, ap),
   179  		blockchain.BlockValidatorOption(block.NewValidator(
   180  			sf,
   181  			protocol.NewGenericValidator(sf, accountutil.AccountState),
   182  		)),
   183  	)
   184  	ep := execution.NewProtocol(blkMemDao.GetBlockHash, rewarding.DepositGasWithSGD, nil, func(u uint64) (time.Time, error) { return time.Time{}, nil })
   185  	require.NoError(t, ep.Register(registry))
   186  	rewardingProtocol := rewarding.NewProtocol(cfg.Genesis.Rewarding)
   187  	require.NoError(t, rewardingProtocol.Register(registry))
   188  	require.NoError(t, bc.Start(ctx))
   189  	defer func() {
   190  		require.NoError(t, bc.Stop(ctx))
   191  	}()
   192  
   193  	for i := 0; i < 30; i++ {
   194  		blk, err := bc.MintNewBlock(testutil.TimestampNow())
   195  		require.NoError(t, err)
   196  		require.Equal(t, 1, len(blk.Actions))
   197  		require.Equal(t, 1, len(blk.Receipts))
   198  		var gasConsumed uint64
   199  		for _, receipt := range blk.Receipts {
   200  			gasConsumed += receipt.GasConsumed
   201  		}
   202  		require.True(t, gasConsumed <= cfg.Genesis.BlockGasLimit)
   203  		require.NoError(t, bc.CommitBlock(blk))
   204  	}
   205  	height := bc.TipHeight()
   206  	fmt.Printf("Open blockchain pass, height = %d\n", height)
   207  
   208  	gs := NewGasStation(bc, blkMemDao, cfg.GasStation)
   209  	require.NotNil(t, gs)
   210  
   211  	gp, err := gs.SuggestGasPrice()
   212  	fmt.Println(gp)
   213  	require.NoError(t, err)
   214  	// i from 10 to 29,gasprice for 20 to 39,60%*20+20=31
   215  	require.Equal(t, gs.cfg.DefaultGas, gp)
   216  }
   217  
   218  func TestSuggestGasPrice_GasConsumed(t *testing.T) {
   219  	cases := []testCase{
   220  		{
   221  			name: "gas consumed > maxGas/2",
   222  			blocks: []testActionGas{
   223  				{{uint64(unit.Qev) * 2, 100000000}},
   224  				{{uint64(unit.Qev) * 2, 100000000}},
   225  				{{uint64(unit.Qev) * 2, 100000000}},
   226  				{{uint64(unit.Qev) * 2, 100000000}},
   227  				{{uint64(unit.Qev) * 2, 100000000}},
   228  				{{uint64(unit.Qev) * 2, 200000000}},
   229  			},
   230  			expectGasPrice: 2200000000000,
   231  		},
   232  		{
   233  			name: "gas consumed < maxGas/5",
   234  			blocks: []testActionGas{
   235  				{{uint64(unit.Qev) * 2, 1000000}},
   236  				{{uint64(unit.Qev) * 2, 1000000}},
   237  				{{uint64(unit.Qev) * 2, 1000000}},
   238  				{{uint64(unit.Qev) * 2, 1000000}},
   239  				{{uint64(unit.Qev) * 2, 1000000}},
   240  				{{uint64(unit.Qev) * 2, 2000000}},
   241  			},
   242  			expectGasPrice: 1800000000000,
   243  		},
   244  		{
   245  			name: "gas consumed between maxGas/5 - maxGas/2",
   246  			blocks: []testActionGas{
   247  				{{uint64(unit.Qev) * 2, 10000000}},
   248  				{{uint64(unit.Qev) * 2, 10000000}},
   249  				{{uint64(unit.Qev) * 2, 10000000}},
   250  				{{uint64(unit.Qev) * 2, 10000000}},
   251  				{{uint64(unit.Qev) * 2, 10000000}},
   252  				{{uint64(unit.Qev) * 2, 5000000}},
   253  			},
   254  			expectGasPrice: 2000000000000,
   255  		},
   256  	}
   257  	for _, c := range cases {
   258  		t.Run(c.name, func(t *testing.T) {
   259  			r := require.New(t)
   260  			blocks := prepareBlocks(r, c.blocks)
   261  			ctrl := gomock.NewController(t)
   262  			bc := mock_blockchain.NewMockBlockchain(ctrl)
   263  			dao := mock_blockdao.NewMockBlockDAO(ctrl)
   264  			gs := NewGasStation(bc, dao, DefaultConfig)
   265  			bc.EXPECT().TipHeight().Return(uint64(len(blocks) - 1)).Times(1)
   266  			bc.EXPECT().Genesis().Return(genesis.Default).Times(1)
   267  			dao.EXPECT().GetBlockByHeight(gomock.Any()).DoAndReturn(
   268  				func(height uint64) (*block.Block, error) {
   269  					return blocks[height], nil
   270  				},
   271  			).AnyTimes()
   272  			gp, err := gs.SuggestGasPrice()
   273  			r.NoError(err)
   274  			r.Equal(c.expectGasPrice, gp)
   275  		})
   276  	}
   277  }
   278  
   279  func prepareBlocks(r *require.Assertions, cases []testActionGas) map[uint64]*block.Block {
   280  	blocks := map[uint64]*block.Block{}
   281  	for i := range cases {
   282  		actions := []*action.SealedEnvelope{}
   283  		receipts := []*action.Receipt{}
   284  		for _, gas := range cases[i] {
   285  			seale, err := action.SignedTransfer(identityset.Address(1).String(), identityset.PrivateKey(1), 1, big.NewInt(0), []byte{}, 1000, big.NewInt(int64(gas.gasPrice)))
   286  			r.NoError(err)
   287  			actions = append(actions, seale)
   288  			receipts = append(receipts, &action.Receipt{GasConsumed: gas.gasConsumed})
   289  		}
   290  		blocks[uint64(i)] = &block.Block{
   291  			Body:     block.Body{Actions: actions},
   292  			Receipts: receipts,
   293  		}
   294  	}
   295  	return blocks
   296  }