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