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  }