github.com/evdatsion/aphelion-dpos-bft@v0.32.1/state/txindex/kv/kv_test.go (about)

     1  package kv
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  	abci "github.com/evdatsion/aphelion-dpos-bft/abci/types"
    12  	cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common"
    13  	db "github.com/evdatsion/aphelion-dpos-bft/libs/db"
    14  
    15  	"github.com/evdatsion/aphelion-dpos-bft/libs/pubsub/query"
    16  	"github.com/evdatsion/aphelion-dpos-bft/state/txindex"
    17  	"github.com/evdatsion/aphelion-dpos-bft/types"
    18  )
    19  
    20  func TestTxIndex(t *testing.T) {
    21  	indexer := NewTxIndex(db.NewMemDB())
    22  
    23  	tx := types.Tx("HELLO WORLD")
    24  	txResult := &types.TxResult{
    25  		Height: 1,
    26  		Index:  0,
    27  		Tx:     tx,
    28  		Result: abci.ResponseDeliverTx{
    29  			Data: []byte{0},
    30  			Code: abci.CodeTypeOK, Log: "", Events: nil,
    31  		},
    32  	}
    33  	hash := tx.Hash()
    34  
    35  	batch := txindex.NewBatch(1)
    36  	if err := batch.Add(txResult); err != nil {
    37  		t.Error(err)
    38  	}
    39  	err := indexer.AddBatch(batch)
    40  	require.NoError(t, err)
    41  
    42  	loadedTxResult, err := indexer.Get(hash)
    43  	require.NoError(t, err)
    44  	assert.Equal(t, txResult, loadedTxResult)
    45  
    46  	tx2 := types.Tx("BYE BYE WORLD")
    47  	txResult2 := &types.TxResult{
    48  		Height: 1,
    49  		Index:  0,
    50  		Tx:     tx2,
    51  		Result: abci.ResponseDeliverTx{
    52  			Data: []byte{0},
    53  			Code: abci.CodeTypeOK, Log: "", Events: nil,
    54  		},
    55  	}
    56  	hash2 := tx2.Hash()
    57  
    58  	err = indexer.Index(txResult2)
    59  	require.NoError(t, err)
    60  
    61  	loadedTxResult2, err := indexer.Get(hash2)
    62  	require.NoError(t, err)
    63  	assert.Equal(t, txResult2, loadedTxResult2)
    64  }
    65  
    66  func TestTxSearch(t *testing.T) {
    67  	allowedTags := []string{"account.number", "account.owner", "account.date"}
    68  	indexer := NewTxIndex(db.NewMemDB(), IndexTags(allowedTags))
    69  
    70  	txResult := txResultWithEvents([]abci.Event{
    71  		{Type: "account", Attributes: []cmn.KVPair{{Key: []byte("number"), Value: []byte("1")}}},
    72  		{Type: "account", Attributes: []cmn.KVPair{{Key: []byte("owner"), Value: []byte("Ivan")}}},
    73  		{Type: "", Attributes: []cmn.KVPair{{Key: []byte("not_allowed"), Value: []byte("Vlad")}}},
    74  	})
    75  	hash := txResult.Tx.Hash()
    76  
    77  	err := indexer.Index(txResult)
    78  	require.NoError(t, err)
    79  
    80  	testCases := []struct {
    81  		q             string
    82  		resultsLength int
    83  	}{
    84  		// search by hash
    85  		{fmt.Sprintf("tx.hash = '%X'", hash), 1},
    86  		// search by exact match (one tag)
    87  		{"account.number = 1", 1},
    88  		// search by exact match (two tags)
    89  		{"account.number = 1 AND account.owner = 'Ivan'", 1},
    90  		// search by exact match (two tags)
    91  		{"account.number = 1 AND account.owner = 'Vlad'", 0},
    92  		// search using a prefix of the stored value
    93  		{"account.owner = 'Iv'", 0},
    94  		// search by range
    95  		{"account.number >= 1 AND account.number <= 5", 1},
    96  		// search by range (lower bound)
    97  		{"account.number >= 1", 1},
    98  		// search by range (upper bound)
    99  		{"account.number <= 5", 1},
   100  		// search using not allowed tag
   101  		{"not_allowed = 'boom'", 0},
   102  		// search for not existing tx result
   103  		{"account.number >= 2 AND account.number <= 5", 0},
   104  		// search using not existing tag
   105  		{"account.date >= TIME 2013-05-03T14:45:00Z", 0},
   106  		// search using CONTAINS
   107  		{"account.owner CONTAINS 'an'", 1},
   108  		// search for non existing value using CONTAINS
   109  		{"account.owner CONTAINS 'Vlad'", 0},
   110  		// search using the wrong tag (of numeric type) using CONTAINS
   111  		{"account.number CONTAINS 'Iv'", 0},
   112  	}
   113  
   114  	for _, tc := range testCases {
   115  		t.Run(tc.q, func(t *testing.T) {
   116  			results, err := indexer.Search(query.MustParse(tc.q))
   117  			assert.NoError(t, err)
   118  
   119  			assert.Len(t, results, tc.resultsLength)
   120  			if tc.resultsLength > 0 {
   121  				assert.Equal(t, []*types.TxResult{txResult}, results)
   122  			}
   123  		})
   124  	}
   125  }
   126  
   127  func TestTxSearchDeprecatedIndexing(t *testing.T) {
   128  	allowedTags := []string{"account.number", "sender"}
   129  	indexer := NewTxIndex(db.NewMemDB(), IndexTags(allowedTags))
   130  
   131  	// index tx using events indexing (composite key)
   132  	txResult1 := txResultWithEvents([]abci.Event{
   133  		{Type: "account", Attributes: []cmn.KVPair{{Key: []byte("number"), Value: []byte("1")}}},
   134  	})
   135  	hash1 := txResult1.Tx.Hash()
   136  
   137  	err := indexer.Index(txResult1)
   138  	require.NoError(t, err)
   139  
   140  	// index tx also using deprecated indexing (tag as key)
   141  	txResult2 := txResultWithEvents(nil)
   142  	txResult2.Tx = types.Tx("HELLO WORLD 2")
   143  
   144  	hash2 := txResult2.Tx.Hash()
   145  	b := indexer.store.NewBatch()
   146  
   147  	rawBytes, err := cdc.MarshalBinaryBare(txResult2)
   148  	require.NoError(t, err)
   149  
   150  	depKey := []byte(fmt.Sprintf("%s/%s/%d/%d",
   151  		"sender",
   152  		"addr1",
   153  		txResult2.Height,
   154  		txResult2.Index,
   155  	))
   156  
   157  	b.Set(depKey, hash2)
   158  	b.Set(keyForHeight(txResult2), hash2)
   159  	b.Set(hash2, rawBytes)
   160  	b.Write()
   161  
   162  	testCases := []struct {
   163  		q       string
   164  		results []*types.TxResult
   165  	}{
   166  		// search by hash
   167  		{fmt.Sprintf("tx.hash = '%X'", hash1), []*types.TxResult{txResult1}},
   168  		// search by hash
   169  		{fmt.Sprintf("tx.hash = '%X'", hash2), []*types.TxResult{txResult2}},
   170  		// search by exact match (one tag)
   171  		{"account.number = 1", []*types.TxResult{txResult1}},
   172  		{"account.number >= 1 AND account.number <= 5", []*types.TxResult{txResult1}},
   173  		// search by range (lower bound)
   174  		{"account.number >= 1", []*types.TxResult{txResult1}},
   175  		// search by range (upper bound)
   176  		{"account.number <= 5", []*types.TxResult{txResult1}},
   177  		// search using not allowed tag
   178  		{"not_allowed = 'boom'", []*types.TxResult{}},
   179  		// search for not existing tx result
   180  		{"account.number >= 2 AND account.number <= 5", []*types.TxResult{}},
   181  		// search using not existing tag
   182  		{"account.date >= TIME 2013-05-03T14:45:00Z", []*types.TxResult{}},
   183  		// search by deprecated tag
   184  		{"sender = 'addr1'", []*types.TxResult{txResult2}},
   185  	}
   186  
   187  	for _, tc := range testCases {
   188  		t.Run(tc.q, func(t *testing.T) {
   189  			results, err := indexer.Search(query.MustParse(tc.q))
   190  			require.NoError(t, err)
   191  			require.Equal(t, results, tc.results)
   192  		})
   193  	}
   194  }
   195  
   196  func TestTxSearchOneTxWithMultipleSameTagsButDifferentValues(t *testing.T) {
   197  	allowedTags := []string{"account.number"}
   198  	indexer := NewTxIndex(db.NewMemDB(), IndexTags(allowedTags))
   199  
   200  	txResult := txResultWithEvents([]abci.Event{
   201  		{Type: "account", Attributes: []cmn.KVPair{{Key: []byte("number"), Value: []byte("1")}}},
   202  		{Type: "account", Attributes: []cmn.KVPair{{Key: []byte("number"), Value: []byte("2")}}},
   203  	})
   204  
   205  	err := indexer.Index(txResult)
   206  	require.NoError(t, err)
   207  
   208  	results, err := indexer.Search(query.MustParse("account.number >= 1"))
   209  	assert.NoError(t, err)
   210  
   211  	assert.Len(t, results, 1)
   212  	assert.Equal(t, []*types.TxResult{txResult}, results)
   213  }
   214  
   215  func TestTxSearchMultipleTxs(t *testing.T) {
   216  	allowedTags := []string{"account.number", "account.number.id"}
   217  	indexer := NewTxIndex(db.NewMemDB(), IndexTags(allowedTags))
   218  
   219  	// indexed first, but bigger height (to test the order of transactions)
   220  	txResult := txResultWithEvents([]abci.Event{
   221  		{Type: "account", Attributes: []cmn.KVPair{{Key: []byte("number"), Value: []byte("1")}}},
   222  	})
   223  
   224  	txResult.Tx = types.Tx("Bob's account")
   225  	txResult.Height = 2
   226  	txResult.Index = 1
   227  	err := indexer.Index(txResult)
   228  	require.NoError(t, err)
   229  
   230  	// indexed second, but smaller height (to test the order of transactions)
   231  	txResult2 := txResultWithEvents([]abci.Event{
   232  		{Type: "account", Attributes: []cmn.KVPair{{Key: []byte("number"), Value: []byte("2")}}},
   233  	})
   234  	txResult2.Tx = types.Tx("Alice's account")
   235  	txResult2.Height = 1
   236  	txResult2.Index = 2
   237  
   238  	err = indexer.Index(txResult2)
   239  	require.NoError(t, err)
   240  
   241  	// indexed third (to test the order of transactions)
   242  	txResult3 := txResultWithEvents([]abci.Event{
   243  		{Type: "account", Attributes: []cmn.KVPair{{Key: []byte("number"), Value: []byte("3")}}},
   244  	})
   245  	txResult3.Tx = types.Tx("Jack's account")
   246  	txResult3.Height = 1
   247  	txResult3.Index = 1
   248  	err = indexer.Index(txResult3)
   249  	require.NoError(t, err)
   250  
   251  	// indexed fourth (to test we don't include txs with similar tags)
   252  	// https://github.com/evdatsion/aphelion-dpos-bft/issues/2908
   253  	txResult4 := txResultWithEvents([]abci.Event{
   254  		{Type: "account", Attributes: []cmn.KVPair{{Key: []byte("number.id"), Value: []byte("1")}}},
   255  	})
   256  	txResult4.Tx = types.Tx("Mike's account")
   257  	txResult4.Height = 2
   258  	txResult4.Index = 2
   259  	err = indexer.Index(txResult4)
   260  	require.NoError(t, err)
   261  
   262  	results, err := indexer.Search(query.MustParse("account.number >= 1"))
   263  	assert.NoError(t, err)
   264  
   265  	require.Len(t, results, 3)
   266  	assert.Equal(t, []*types.TxResult{txResult3, txResult2, txResult}, results)
   267  }
   268  
   269  func TestIndexAllTags(t *testing.T) {
   270  	indexer := NewTxIndex(db.NewMemDB(), IndexAllTags())
   271  
   272  	txResult := txResultWithEvents([]abci.Event{
   273  		{Type: "account", Attributes: []cmn.KVPair{{Key: []byte("owner"), Value: []byte("Ivan")}}},
   274  		{Type: "account", Attributes: []cmn.KVPair{{Key: []byte("number"), Value: []byte("1")}}},
   275  	})
   276  
   277  	err := indexer.Index(txResult)
   278  	require.NoError(t, err)
   279  
   280  	results, err := indexer.Search(query.MustParse("account.number >= 1"))
   281  	assert.NoError(t, err)
   282  	assert.Len(t, results, 1)
   283  	assert.Equal(t, []*types.TxResult{txResult}, results)
   284  
   285  	results, err = indexer.Search(query.MustParse("account.owner = 'Ivan'"))
   286  	assert.NoError(t, err)
   287  	assert.Len(t, results, 1)
   288  	assert.Equal(t, []*types.TxResult{txResult}, results)
   289  }
   290  
   291  func txResultWithEvents(events []abci.Event) *types.TxResult {
   292  	tx := types.Tx("HELLO WORLD")
   293  	return &types.TxResult{
   294  		Height: 1,
   295  		Index:  0,
   296  		Tx:     tx,
   297  		Result: abci.ResponseDeliverTx{
   298  			Data:   []byte{0},
   299  			Code:   abci.CodeTypeOK,
   300  			Log:    "",
   301  			Events: events,
   302  		},
   303  	}
   304  }
   305  
   306  func benchmarkTxIndex(txsCount int64, b *testing.B) {
   307  	dir, err := ioutil.TempDir("", "tx_index_db")
   308  	if err != nil {
   309  		b.Fatal(err)
   310  	}
   311  	defer os.RemoveAll(dir) // nolint: errcheck
   312  
   313  	store := db.NewDB("tx_index", "leveldb", dir)
   314  	indexer := NewTxIndex(store)
   315  
   316  	batch := txindex.NewBatch(txsCount)
   317  	txIndex := uint32(0)
   318  	for i := int64(0); i < txsCount; i++ {
   319  		tx := cmn.RandBytes(250)
   320  		txResult := &types.TxResult{
   321  			Height: 1,
   322  			Index:  txIndex,
   323  			Tx:     tx,
   324  			Result: abci.ResponseDeliverTx{
   325  				Data:   []byte{0},
   326  				Code:   abci.CodeTypeOK,
   327  				Log:    "",
   328  				Events: []abci.Event{},
   329  			},
   330  		}
   331  		if err := batch.Add(txResult); err != nil {
   332  			b.Fatal(err)
   333  		}
   334  		txIndex++
   335  	}
   336  
   337  	b.ResetTimer()
   338  
   339  	for n := 0; n < b.N; n++ {
   340  		err = indexer.AddBatch(batch)
   341  	}
   342  	if err != nil {
   343  		b.Fatal(err)
   344  	}
   345  }
   346  
   347  func BenchmarkTxIndex1(b *testing.B)     { benchmarkTxIndex(1, b) }
   348  func BenchmarkTxIndex500(b *testing.B)   { benchmarkTxIndex(500, b) }
   349  func BenchmarkTxIndex1000(b *testing.B)  { benchmarkTxIndex(1000, b) }
   350  func BenchmarkTxIndex2000(b *testing.B)  { benchmarkTxIndex(2000, b) }
   351  func BenchmarkTxIndex10000(b *testing.B) { benchmarkTxIndex(10000, b) }