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) }