github.com/iotexproject/iotex-core@v1.14.1-rc1/api/coreservice_test.go (about) 1 // Copyright (c) 2022 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 api 7 8 import ( 9 "context" 10 "encoding/hex" 11 "math/big" 12 "strconv" 13 "testing" 14 "time" 15 16 "github.com/ethereum/go-ethereum/eth/tracers" 17 "github.com/ethereum/go-ethereum/eth/tracers/logger" 18 "github.com/golang/mock/gomock" 19 "github.com/iotexproject/iotex-proto/golang/iotexapi" 20 "github.com/pkg/errors" 21 "github.com/stretchr/testify/require" 22 "google.golang.org/protobuf/proto" 23 24 "github.com/iotexproject/iotex-core/action" 25 "github.com/iotexproject/iotex-core/actpool" 26 "github.com/iotexproject/iotex-core/api/logfilter" 27 "github.com/iotexproject/iotex-core/blockchain" 28 "github.com/iotexproject/iotex-core/blockchain/blockdao" 29 "github.com/iotexproject/iotex-core/test/identityset" 30 "github.com/iotexproject/iotex-core/test/mock/mock_blockindex" 31 "github.com/iotexproject/iotex-core/testutil" 32 ) 33 34 func TestLogsInRange(t *testing.T) { 35 require := require.New(t) 36 svr, _, _, _, cleanCallback := setupTestCoreService() 37 defer cleanCallback() 38 39 t.Run("blocks with four logs", func(t *testing.T) { 40 testData := &filterObject{FromBlock: "1", ToBlock: "4"} 41 filter, err := getTopicsAddress(testData.Address, testData.Topics) 42 require.NoError(err) 43 from, err := strconv.ParseUint(testData.FromBlock, 10, 64) 44 require.NoError(err) 45 to, err := strconv.ParseUint(testData.ToBlock, 10, 64) 46 require.NoError(err) 47 48 logs, hashes, err := svr.LogsInRange(logfilter.NewLogFilter(filter), from, to, uint64(0)) 49 require.NoError(err) 50 require.Equal(4, len(logs)) 51 require.Equal(4, len(hashes)) 52 }) 53 t.Run("empty log", func(t *testing.T) { 54 testData := &filterObject{FromBlock: "1", ToBlock: "4", Address: []string{"0x8ce313ab12bf7aed8136ab36c623ff98c8eaad34"}} 55 filter, err := getTopicsAddress(testData.Address, testData.Topics) 56 require.NoError(err) 57 from, err := strconv.ParseUint(testData.FromBlock, 10, 64) 58 require.NoError(err) 59 to, err := strconv.ParseUint(testData.ToBlock, 10, 64) 60 require.NoError(err) 61 62 logs, hashes, err := svr.LogsInRange(logfilter.NewLogFilter(filter), from, to, uint64(0)) 63 require.NoError(err) 64 require.Equal(0, len(logs)) 65 require.Equal(0, len(hashes)) 66 }) 67 t.Run("over 5000 pagenation size", func(t *testing.T) { 68 testData := &filterObject{FromBlock: "1", ToBlock: "4"} 69 filter, err := getTopicsAddress(testData.Address, testData.Topics) 70 require.NoError(err) 71 from, err := strconv.ParseUint(testData.FromBlock, 10, 64) 72 require.NoError(err) 73 to, err := strconv.ParseUint(testData.ToBlock, 10, 64) 74 require.NoError(err) 75 76 logs, hashes, err := svr.LogsInRange(logfilter.NewLogFilter(filter), from, to, uint64(5001)) 77 require.NoError(err) 78 require.Equal(4, len(logs)) 79 require.Equal(4, len(hashes)) 80 }) 81 t.Run("invalid start and end height", func(t *testing.T) { 82 testData := &filterObject{FromBlock: "2", ToBlock: "1"} 83 filter, err := getTopicsAddress(testData.Address, testData.Topics) 84 require.NoError(err) 85 from, err := strconv.ParseUint(testData.FromBlock, 10, 64) 86 require.NoError(err) 87 to, err := strconv.ParseUint(testData.ToBlock, 10, 64) 88 require.NoError(err) 89 90 _, _, err = svr.LogsInRange(logfilter.NewLogFilter(filter), from, to, uint64(0)) 91 expectedErr := errors.New("invalid start or end height") 92 require.Error(err) 93 require.Equal(expectedErr.Error(), err.Error()) 94 }) 95 t.Run("start block > tip height", func(t *testing.T) { 96 testData := &filterObject{FromBlock: "5", ToBlock: "5"} 97 filter, err := getTopicsAddress(testData.Address, testData.Topics) 98 require.NoError(err) 99 from, err := strconv.ParseUint(testData.FromBlock, 10, 64) 100 require.NoError(err) 101 to, err := strconv.ParseUint(testData.ToBlock, 10, 64) 102 require.NoError(err) 103 104 _, _, err = svr.LogsInRange(logfilter.NewLogFilter(filter), from, to, uint64(0)) 105 expectedErr := errors.New("start block > tip height") 106 require.Error(err) 107 require.Equal(expectedErr.Error(), err.Error()) 108 }) 109 } 110 111 func BenchmarkLogsInRange(b *testing.B) { 112 svr, _, _, _, cleanCallback := setupTestCoreService() 113 defer cleanCallback() 114 115 ctrl := gomock.NewController(b) 116 defer ctrl.Finish() 117 blk := mock_blockindex.NewMockBloomFilterIndexer(ctrl) 118 119 testData := &filterObject{FromBlock: "0x1"} 120 filter, _ := getTopicsAddress(testData.Address, testData.Topics) 121 from, _ := strconv.ParseInt(testData.FromBlock, 10, 64) 122 to, _ := strconv.ParseInt(testData.ToBlock, 10, 64) 123 124 b.Run("five workers to extract logs", func(b *testing.B) { 125 blk.EXPECT().FilterBlocksInRange(logfilter.NewLogFilter(filter), uint64(from), uint64(to), 0).Return([]uint64{1, 2, 3, 4}, nil).AnyTimes() 126 for i := 0; i < b.N; i++ { 127 svr.LogsInRange(logfilter.NewLogFilter(filter), uint64(from), uint64(to), uint64(0)) 128 } 129 }) 130 } 131 132 func getTopicsAddress(addr []string, topics [][]string) (*iotexapi.LogsFilter, error) { 133 var filter iotexapi.LogsFilter 134 for _, ethAddr := range addr { 135 ioAddr, err := ethAddrToIoAddr(ethAddr) 136 if err != nil { 137 return nil, err 138 } 139 filter.Address = append(filter.Address, ioAddr.String()) 140 } 141 for _, tp := range topics { 142 var topic [][]byte 143 for _, str := range tp { 144 b, err := hexToBytes(str) 145 if err != nil { 146 return nil, err 147 } 148 topic = append(topic, b) 149 } 150 filter.Topics = append(filter.Topics, &iotexapi.Topics{Topic: topic}) 151 } 152 153 return &filter, nil 154 } 155 156 func setupTestCoreService() (CoreService, blockchain.Blockchain, blockdao.BlockDAO, actpool.ActPool, func()) { 157 cfg := newConfig() 158 159 // TODO (zhi): revise 160 bc, dao, indexer, bfIndexer, sf, ap, registry, bfIndexFile, err := setupChain(cfg) 161 if err != nil { 162 panic(err) 163 } 164 165 ctx := context.Background() 166 167 // Start blockchain 168 if err := bc.Start(ctx); err != nil { 169 panic(err) 170 } 171 // Add testing blocks 172 if err := addTestingBlocks(bc, ap); err != nil { 173 panic(err) 174 } 175 176 opts := []Option{WithBroadcastOutbound(func(ctx context.Context, chainID uint32, msg proto.Message) error { 177 return nil 178 })} 179 svr, err := newCoreService(cfg.api, bc, nil, sf, dao, indexer, bfIndexer, ap, registry, func(u uint64) (time.Time, error) { return time.Time{}, nil }, opts...) 180 if err != nil { 181 panic(err) 182 } 183 184 return svr, bc, dao, ap, func() { 185 testutil.CleanupPath(bfIndexFile) 186 } 187 } 188 189 func TestEstimateGasForAction(t *testing.T) { 190 require := require.New(t) 191 ctrl := gomock.NewController(t) 192 defer ctrl.Finish() 193 svr, _, _, _, cleanCallback := setupTestCoreService() 194 defer cleanCallback() 195 196 estimatedGas, err := svr.EstimateGasForAction(context.Background(), getAction()) 197 require.NoError(err) 198 require.Equal(uint64(10000), estimatedGas) 199 200 estimatedGas, err = svr.EstimateGasForAction(context.Background(), getActionWithPayload()) 201 require.NoError(err) 202 require.Equal(uint64(10000)+10*action.ExecutionDataGas, estimatedGas) 203 204 _, err = svr.EstimateGasForAction(context.Background(), nil) 205 require.Contains(err.Error(), action.ErrNilProto.Error()) 206 } 207 208 func TestEstimateExecutionGasConsumption(t *testing.T) { 209 require := require.New(t) 210 ctrl := gomock.NewController(t) 211 defer ctrl.Finish() 212 svr, _, _, _, cleanCallback := setupTestCoreService() 213 defer cleanCallback() 214 215 callAddr := identityset.Address(29) 216 sc, err := action.NewExecution("", 0, big.NewInt(0), 0, big.NewInt(0), []byte{}) 217 require.NoError(err) 218 219 //gasprice is zero 220 sc.SetGasPrice(big.NewInt(0)) 221 estimatedGas, err := svr.EstimateExecutionGasConsumption(context.Background(), sc, callAddr) 222 require.NoError(err) 223 require.Equal(uint64(10000), estimatedGas) 224 225 //gasprice no zero, should return error before fixed 226 sc.SetGasPrice(big.NewInt(100)) 227 estimatedGas, err = svr.EstimateExecutionGasConsumption(context.Background(), sc, callAddr) 228 require.NoError(err) 229 require.Equal(uint64(10000), estimatedGas) 230 231 } 232 233 func TestTraceTransaction(t *testing.T) { 234 require := require.New(t) 235 ctrl := gomock.NewController(t) 236 defer ctrl.Finish() 237 svr, bc, _, ap, cleanCallback := setupTestCoreService() 238 defer cleanCallback() 239 ctx := context.Background() 240 tsf, err := action.SignedExecution(identityset.Address(29).String(), 241 identityset.PrivateKey(29), 1, big.NewInt(0), testutil.TestGasLimit, 242 big.NewInt(testutil.TestGasPriceInt64), []byte{}) 243 require.NoError(err) 244 tsfhash, err := tsf.Hash() 245 246 blk1Time := testutil.TimestampNow() 247 require.NoError(ap.Add(ctx, tsf)) 248 blk, err := bc.MintNewBlock(blk1Time) 249 require.NoError(err) 250 require.NoError(bc.CommitBlock(blk)) 251 cfg := &tracers.TraceConfig{ 252 Config: &logger.Config{ 253 EnableMemory: true, 254 DisableStack: false, 255 DisableStorage: false, 256 EnableReturnData: true, 257 }, 258 } 259 retval, receipt, traces, err := svr.TraceTransaction(ctx, hex.EncodeToString(tsfhash[:]), cfg) 260 require.NoError(err) 261 require.Equal("0x", byteToHex(retval)) 262 require.Equal(uint64(1), receipt.Status) 263 require.Equal(uint64(0x2710), receipt.GasConsumed) 264 require.Empty(receipt.ExecutionRevertMsg()) 265 require.Equal(0, len(traces.(*logger.StructLogger).StructLogs())) 266 } 267 268 func TestTraceCall(t *testing.T) { 269 require := require.New(t) 270 ctrl := gomock.NewController(t) 271 defer ctrl.Finish() 272 svr, bc, _, ap, cleanCallback := setupTestCoreService() 273 defer cleanCallback() 274 ctx := context.Background() 275 tsf, err := action.SignedExecution(identityset.Address(29).String(), 276 identityset.PrivateKey(29), 1, big.NewInt(0), testutil.TestGasLimit, 277 big.NewInt(testutil.TestGasPriceInt64), []byte{}) 278 require.NoError(err) 279 280 blk1Time := testutil.TimestampNow() 281 require.NoError(ap.Add(ctx, tsf)) 282 blk, err := bc.MintNewBlock(blk1Time) 283 require.NoError(err) 284 require.NoError(bc.CommitBlock(blk)) 285 cfg := &tracers.TraceConfig{ 286 Config: &logger.Config{ 287 EnableMemory: true, 288 DisableStack: false, 289 DisableStorage: false, 290 EnableReturnData: true, 291 }, 292 } 293 retval, receipt, traces, err := svr.TraceCall(ctx, 294 identityset.Address(29), blk.Height(), 295 identityset.Address(29).String(), 296 0, big.NewInt(0), testutil.TestGasLimit, 297 []byte{}, cfg) 298 require.NoError(err) 299 require.Equal("0x", byteToHex(retval)) 300 require.Equal(uint64(1), receipt.Status) 301 require.Equal(uint64(0x2710), receipt.GasConsumed) 302 require.Empty(receipt.ExecutionRevertMsg()) 303 require.Equal(0, len(traces.(*logger.StructLogger).StructLogs())) 304 } 305 306 func TestProofAndCompareReverseActions(t *testing.T) { 307 sliceN := func(n uint64) (value []uint64) { 308 value = make([]uint64, 0, n) 309 for i := uint64(0); i < n; i++ { 310 value = append(value, i) 311 } 312 return 313 } 314 315 // previous algorithm: commit(06d202) 316 prev := func(slice []uint64, start, count uint64) (reserved []uint64) { 317 size := uint64(len(slice)) 318 for i := start; i < size && i < start+count; i++ { 319 ri := size - 1 - i 320 // do other validations 321 reserved = append([]uint64{slice[ri]}, reserved...) 322 } 323 return 324 } 325 // enhanced algorithm 326 curr := func(slice []uint64, start, count uint64) (reserved []uint64) { 327 size := uint64(len(slice)) 328 if start > size || count == 0 { 329 return nil 330 } 331 end := start + count 332 if end > size { 333 end = size 334 } 335 for i := end; i > start; i-- { 336 reserved = append(reserved, slice[size-i]) 337 } 338 return 339 } 340 slice10 := sliceN(10) 341 cases := []struct { 342 name string 343 slice []uint64 344 start uint64 345 count uint64 346 expect []uint64 347 }{ 348 { 349 name: "NoReverseDone_StartOutOfRange_EqualSliceLen", 350 slice: slice10, 351 start: 10, 352 count: 10, 353 expect: nil, 354 }, { 355 name: "NoReversedDone_StartOutOfRange_GreaterSliceLen", 356 slice: slice10, 357 start: 11, 358 count: 1, 359 expect: nil, 360 }, { 361 name: "NoReversedDone_CountIsZero", 362 slice: slice10, 363 start: 9, 364 count: 0, 365 expect: nil, 366 }, { 367 name: "StartInRangeAndEndOutOfRange", 368 slice: slice10, 369 start: 5, 370 count: 100, 371 expect: []uint64{0, 1, 2, 3, 4}, 372 }, { 373 name: "StartAndEndInRangeBoth", 374 slice: slice10, 375 start: 5, 376 count: 3, 377 expect: []uint64{2, 3, 4}, 378 }, 379 } 380 for _, c := range cases { 381 t.Run(c.name, func(t *testing.T) { 382 r := require.New(t) 383 prevExpect := prev(c.slice, c.start, c.count) 384 currExpect := curr(c.slice, c.start, c.count) 385 r.Equal(prevExpect, currExpect) 386 r.Equal(c.expect, prevExpect) 387 }) 388 } 389 }