github.com/iotexproject/iotex-core@v1.14.1-rc1/blockindex/indexer_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 blockindex
     7  
     8  import (
     9  	"context"
    10  	"hash/fnv"
    11  	"math/big"
    12  	"testing"
    13  
    14  	"github.com/pkg/errors"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/iotexproject/go-pkgs/hash"
    18  
    19  	"github.com/iotexproject/iotex-core/action"
    20  	"github.com/iotexproject/iotex-core/blockchain/block"
    21  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    22  	"github.com/iotexproject/iotex-core/db"
    23  	"github.com/iotexproject/iotex-core/test/identityset"
    24  	"github.com/iotexproject/iotex-core/testutil"
    25  )
    26  
    27  func getTestBlocks(t *testing.T) []*block.Block {
    28  	amount := uint64(50 << 22)
    29  	tsf1, err := action.SignedTransfer(identityset.Address(28).String(), identityset.PrivateKey(28), 1, big.NewInt(int64(amount)), nil, testutil.TestGasLimit, big.NewInt(0))
    30  	require.NoError(t, err)
    31  
    32  	tsf2, err := action.SignedTransfer(identityset.Address(29).String(), identityset.PrivateKey(29), 2, big.NewInt(int64(amount)), nil, testutil.TestGasLimit, big.NewInt(0))
    33  	require.NoError(t, err)
    34  
    35  	tsf3, err := action.SignedTransfer(identityset.Address(30).String(), identityset.PrivateKey(30), 3, big.NewInt(int64(amount)), nil, testutil.TestGasLimit, big.NewInt(0))
    36  	require.NoError(t, err)
    37  
    38  	tsf4, err := action.SignedTransfer(identityset.Address(29).String(), identityset.PrivateKey(28), 2, big.NewInt(int64(amount)), nil, testutil.TestGasLimit, big.NewInt(0))
    39  	require.NoError(t, err)
    40  
    41  	tsf5, err := action.SignedTransfer(identityset.Address(30).String(), identityset.PrivateKey(29), 3, big.NewInt(int64(amount)), nil, testutil.TestGasLimit, big.NewInt(0))
    42  	require.NoError(t, err)
    43  
    44  	tsf6, err := action.SignedTransfer(identityset.Address(28).String(), identityset.PrivateKey(30), 4, big.NewInt(int64(amount)), nil, testutil.TestGasLimit, big.NewInt(0))
    45  	require.NoError(t, err)
    46  
    47  	// create testing executions
    48  	execution1, err := action.SignedExecution(identityset.Address(31).String(), identityset.PrivateKey(28), 1, big.NewInt(1), 0, big.NewInt(0), nil)
    49  	require.NoError(t, err)
    50  	execution2, err := action.SignedExecution(identityset.Address(31).String(), identityset.PrivateKey(29), 2, big.NewInt(0), 0, big.NewInt(0), nil)
    51  	require.NoError(t, err)
    52  	execution3, err := action.SignedExecution(identityset.Address(31).String(), identityset.PrivateKey(30), 3, big.NewInt(2), 0, big.NewInt(0), nil)
    53  	require.NoError(t, err)
    54  
    55  	hash1 := hash.Hash256{}
    56  	fnv.New32().Sum(hash1[:])
    57  	blk1, err := block.NewTestingBuilder().
    58  		SetHeight(1).
    59  		SetPrevBlockHash(hash1).
    60  		SetTimeStamp(testutil.TimestampNow()).
    61  		AddActions(tsf1, tsf4, execution1).
    62  		SignAndBuild(identityset.PrivateKey(27))
    63  	require.NoError(t, err)
    64  
    65  	hash2 := hash.Hash256{}
    66  	fnv.New32().Sum(hash2[:])
    67  	blk2, err := block.NewTestingBuilder().
    68  		SetHeight(2).
    69  		SetPrevBlockHash(hash2).
    70  		SetTimeStamp(testutil.TimestampNow()).
    71  		AddActions(tsf2, tsf5, execution2).
    72  		SignAndBuild(identityset.PrivateKey(27))
    73  	require.NoError(t, err)
    74  
    75  	hash3 := hash.Hash256{}
    76  	fnv.New32().Sum(hash3[:])
    77  	blk3, err := block.NewTestingBuilder().
    78  		SetHeight(3).
    79  		SetPrevBlockHash(hash3).
    80  		SetTimeStamp(testutil.TimestampNow()).
    81  		AddActions(tsf3, tsf6, execution3).
    82  		SignAndBuild(identityset.PrivateKey(27))
    83  	require.NoError(t, err)
    84  	return []*block.Block{&blk1, &blk2, &blk3}
    85  }
    86  
    87  func TestIndexer(t *testing.T) {
    88  	require := require.New(t)
    89  
    90  	blks := getTestBlocks(t)
    91  	t1Hash, _ := blks[0].Actions[0].Hash()
    92  	t4Hash, _ := blks[0].Actions[1].Hash()
    93  	e1Hash, _ := blks[0].Actions[2].Hash()
    94  	t2Hash, _ := blks[1].Actions[0].Hash()
    95  	t5Hash, _ := blks[1].Actions[1].Hash()
    96  	e2Hash, _ := blks[1].Actions[2].Hash()
    97  	t3Hash, _ := blks[2].Actions[0].Hash()
    98  	t6Hash, _ := blks[2].Actions[1].Hash()
    99  	e3Hash, _ := blks[2].Actions[2].Hash()
   100  
   101  	addr28 := hash.BytesToHash160(identityset.Address(28).Bytes())
   102  	addr29 := hash.BytesToHash160(identityset.Address(29).Bytes())
   103  	addr30 := hash.BytesToHash160(identityset.Address(30).Bytes())
   104  	addr31 := hash.BytesToHash160(identityset.Address(31).Bytes())
   105  
   106  	type index struct {
   107  		addr   hash.Hash160
   108  		hashes [][]byte
   109  	}
   110  
   111  	indexTests := []struct {
   112  		total     uint64
   113  		hashTotal [][]byte
   114  		actions   [4]index
   115  	}{
   116  		{
   117  			9,
   118  			[][]byte{t1Hash[:], t4Hash[:], e1Hash[:], t2Hash[:], t5Hash[:], e2Hash[:], t3Hash[:], t6Hash[:], e3Hash[:]},
   119  			[4]index{
   120  				{addr28, [][]byte{t1Hash[:], t4Hash[:], e1Hash[:], t6Hash[:]}},
   121  				{addr29, [][]byte{t4Hash[:], t2Hash[:], t5Hash[:], e2Hash[:]}},
   122  				{addr30, [][]byte{t5Hash[:], t3Hash[:], t6Hash[:], e3Hash[:]}},
   123  				{addr31, [][]byte{e1Hash[:], e2Hash[:], e3Hash[:]}},
   124  			},
   125  		},
   126  		{
   127  			6,
   128  			[][]byte{t1Hash[:], t4Hash[:], e1Hash[:], t2Hash[:], t5Hash[:], e2Hash[:]},
   129  			[4]index{
   130  				{addr28, [][]byte{t1Hash[:], t4Hash[:], e1Hash[:]}},
   131  				{addr29, [][]byte{t4Hash[:], t2Hash[:], t5Hash[:], e2Hash[:]}},
   132  				{addr30, [][]byte{t5Hash[:]}},
   133  				{addr31, [][]byte{e1Hash[:], e2Hash[:]}},
   134  			},
   135  		},
   136  		{
   137  			3,
   138  			[][]byte{t1Hash[:], t4Hash[:], e1Hash[:]},
   139  			[4]index{
   140  				{addr28, [][]byte{t1Hash[:], t4Hash[:], e1Hash[:]}},
   141  				{addr29, [][]byte{t4Hash[:]}},
   142  				{addr30, nil},
   143  				{addr31, [][]byte{e1Hash[:]}},
   144  			},
   145  		},
   146  		{
   147  			0,
   148  			nil,
   149  			[4]index{
   150  				{addr28, nil},
   151  				{addr29, nil},
   152  				{addr30, nil},
   153  				{addr31, nil},
   154  			},
   155  		},
   156  	}
   157  
   158  	testIndexer := func(kvStore db.KVStore, t *testing.T) {
   159  		ctx := genesis.WithGenesisContext(context.Background(), genesis.Default)
   160  		indexer, err := NewIndexer(kvStore, hash.ZeroHash256)
   161  		require.NoError(err)
   162  		require.NoError(indexer.Start(ctx))
   163  		defer func() {
   164  			require.NoError(indexer.Stop(ctx))
   165  		}()
   166  
   167  		height, err := indexer.Height()
   168  		require.NoError(err)
   169  		require.EqualValues(0, height)
   170  
   171  		require.NoError(indexer.PutBlock(ctx, blks[0]))
   172  		// cannot skip block when indexing
   173  		err = indexer.PutBlock(context.Background(), blks[2])
   174  		require.Equal(db.ErrInvalid, errors.Cause(err))
   175  		require.NoError(indexer.PutBlock(ctx, blks[1]))
   176  		height, err = indexer.Height()
   177  		require.NoError(err)
   178  		require.EqualValues(2, height)
   179  		total, err := indexer.GetTotalActions()
   180  		require.NoError(err)
   181  		require.EqualValues(6, total)
   182  
   183  		require.NoError(indexer.PutBlock(ctx, blks[2]))
   184  		height, err = indexer.Height()
   185  		require.NoError(err)
   186  		require.EqualValues(3, height)
   187  
   188  		// test block index
   189  		for i := 0; i < 3; i++ {
   190  			h, err := indexer.GetBlockHash(blks[i].Height())
   191  			require.NoError(err)
   192  			require.Equal(blks[i].HashBlock(), h)
   193  			height, err := indexer.GetBlockHeight(h)
   194  			require.NoError(err)
   195  			require.Equal(blks[i].Height(), height)
   196  			bd, err := indexer.GetBlockIndex(blks[i].Height())
   197  			require.NoError(err)
   198  			require.Equal(h[:], bd.Hash())
   199  			require.EqualValues(len(blks[i].Actions), bd.NumAction())
   200  
   201  			// test amount
   202  			amount := big.NewInt(0)
   203  			tsfs, _ := classifyActions(blks[i].Actions)
   204  			for _, tsf := range tsfs {
   205  				amount.Add(amount, tsf.Amount())
   206  			}
   207  			require.Equal(amount, bd.TsfAmount())
   208  
   209  			// Test GetActionIndex
   210  			for j := 0; j < 3; j++ {
   211  				actIndex, err := indexer.GetActionIndex(indexTests[0].hashTotal[i*3+j])
   212  				require.NoError(err)
   213  				require.Equal(blks[i].Height(), actIndex.blkHeight)
   214  			}
   215  		}
   216  
   217  		// non-existing address has 0 actions
   218  		actionCount, err := indexer.GetActionCountByAddress(hash.BytesToHash160(identityset.Address(13).Bytes()))
   219  		require.NoError(err)
   220  		require.EqualValues(0, actionCount)
   221  
   222  		// Test get actions
   223  		total, err = indexer.GetTotalActions()
   224  		require.NoError(err)
   225  		require.EqualValues(indexTests[0].total, total)
   226  		_, err = indexer.GetActionHashFromIndex(1, total)
   227  		require.Equal(db.ErrInvalid, errors.Cause(err))
   228  		actions, err := indexer.GetActionHashFromIndex(0, total)
   229  		require.NoError(err)
   230  		require.Equal(actions, indexTests[0].hashTotal)
   231  		for i := range indexTests[0].actions {
   232  			actionCount, err := indexer.GetActionCountByAddress(indexTests[0].actions[i].addr)
   233  			require.NoError(err)
   234  			require.EqualValues(len(indexTests[0].actions[i].hashes), actionCount)
   235  			if actionCount > 0 {
   236  				actions, err := indexer.GetActionsByAddress(indexTests[0].actions[i].addr, 0, actionCount)
   237  				require.NoError(err)
   238  				require.Equal(actions, indexTests[0].actions[i].hashes)
   239  			}
   240  		}
   241  	}
   242  
   243  	testDelete := func(kvStore db.KVStore, t *testing.T) {
   244  		ctx := genesis.WithGenesisContext(context.Background(), genesis.Default)
   245  		indexer, err := NewIndexer(kvStore, hash.ZeroHash256)
   246  		require.NoError(err)
   247  		require.NoError(indexer.Start(ctx))
   248  		defer func() {
   249  			require.NoError(indexer.Stop(ctx))
   250  		}()
   251  
   252  		for i := 0; i < 3; i++ {
   253  			require.NoError(indexer.PutBlock(ctx, blks[i]))
   254  		}
   255  
   256  		for i := range indexTests[0].actions {
   257  			actionCount, err := indexer.GetActionCountByAddress(indexTests[0].actions[i].addr)
   258  			require.NoError(err)
   259  			require.EqualValues(len(indexTests[0].actions[i].hashes), actionCount)
   260  		}
   261  
   262  		// delete tip block one by one, verify address/action after each deletion
   263  		for i := range indexTests {
   264  			if i == 0 {
   265  				// tests[0] is the whole address/action data at block height 3
   266  				continue
   267  			}
   268  
   269  			require.NoError(indexer.DeleteTipBlock(ctx, blks[3-i]))
   270  			tipHeight, err := indexer.Height()
   271  			require.NoError(err)
   272  			require.EqualValues(uint64(3-i), tipHeight)
   273  			h, err := indexer.GetBlockHash(tipHeight)
   274  			require.NoError(err)
   275  			if i <= 2 {
   276  				require.Equal(blks[2-i].HashBlock(), h)
   277  			} else {
   278  				require.Equal(hash.ZeroHash256, h)
   279  			}
   280  
   281  			total, err := indexer.GetTotalActions()
   282  			require.NoError(err)
   283  			require.EqualValues(indexTests[i].total, total)
   284  			if total > 0 {
   285  				_, err = indexer.GetActionHashFromIndex(1, total)
   286  				require.Equal(db.ErrInvalid, errors.Cause(err))
   287  				actions, err := indexer.GetActionHashFromIndex(0, total)
   288  				require.NoError(err)
   289  				require.Equal(actions, indexTests[i].hashTotal)
   290  			}
   291  			for j := range indexTests[i].actions {
   292  				actionCount, err := indexer.GetActionCountByAddress(indexTests[i].actions[j].addr)
   293  				require.NoError(err)
   294  				require.EqualValues(len(indexTests[i].actions[j].hashes), actionCount)
   295  				if actionCount > 0 {
   296  					actions, err := indexer.GetActionsByAddress(indexTests[i].actions[j].addr, 0, actionCount)
   297  					require.NoError(err)
   298  					require.Equal(actions, indexTests[i].actions[j].hashes)
   299  				}
   300  			}
   301  		}
   302  
   303  		tipHeight, err := indexer.Height()
   304  		require.NoError(err)
   305  		require.EqualValues(0, tipHeight)
   306  		total, err := indexer.GetTotalActions()
   307  		require.NoError(err)
   308  		require.EqualValues(0, total)
   309  	}
   310  
   311  	t.Run("In-memory KV indexer", func(t *testing.T) {
   312  		testIndexer(db.NewMemKVStore(), t)
   313  	})
   314  	path := "test-indexer"
   315  	testPath, err := testutil.PathOfTempFile(path)
   316  	require.NoError(err)
   317  	defer testutil.CleanupPath(testPath)
   318  	cfg := db.DefaultConfig
   319  	cfg.DbPath = testPath
   320  
   321  	t.Run("Bolt DB indexer", func(t *testing.T) {
   322  		testutil.CleanupPath(testPath)
   323  		defer testutil.CleanupPath(testPath)
   324  		testIndexer(db.NewBoltDB(cfg), t)
   325  	})
   326  
   327  	t.Run("In-memory KV delete", func(t *testing.T) {
   328  		testDelete(db.NewMemKVStore(), t)
   329  	})
   330  	t.Run("Bolt DB delete", func(t *testing.T) {
   331  		testutil.CleanupPath(testPath)
   332  		defer testutil.CleanupPath(testPath)
   333  		testDelete(db.NewBoltDB(cfg), t)
   334  	})
   335  }