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 }