bitbucket.org/number571/tendermint@v0.8.14/state/indexer/tx/kv/kv_test.go (about)

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