github.com/KYVENetwork/cometbft/v38@v38.0.3/state/txindex/kv/kv_test.go (about) 1 package kv 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "testing" 8 9 "github.com/cosmos/gogoproto/proto" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 13 db "github.com/cometbft/cometbft-db" 14 15 abci "github.com/KYVENetwork/cometbft/v38/abci/types" 16 "github.com/KYVENetwork/cometbft/v38/libs/pubsub/query" 17 cmtrand "github.com/KYVENetwork/cometbft/v38/libs/rand" 18 "github.com/KYVENetwork/cometbft/v38/state/txindex" 19 "github.com/KYVENetwork/cometbft/v38/types" 20 ) 21 22 func TestTxIndex(t *testing.T) { 23 indexer := NewTxIndex(db.NewMemDB()) 24 25 tx := types.Tx("HELLO WORLD") 26 txResult := &abci.TxResult{ 27 Height: 1, 28 Index: 0, 29 Tx: tx, 30 Result: abci.ExecTxResult{ 31 Data: []byte{0}, 32 Code: abci.CodeTypeOK, Log: "", Events: nil, 33 }, 34 } 35 hash := tx.Hash() 36 37 batch := txindex.NewBatch(1) 38 if err := batch.Add(txResult); err != nil { 39 t.Error(err) 40 } 41 err := indexer.AddBatch(batch) 42 require.NoError(t, err) 43 44 loadedTxResult, err := indexer.Get(hash) 45 require.NoError(t, err) 46 assert.True(t, proto.Equal(txResult, loadedTxResult)) 47 48 tx2 := types.Tx("BYE BYE WORLD") 49 txResult2 := &abci.TxResult{ 50 Height: 1, 51 Index: 0, 52 Tx: tx2, 53 Result: abci.ExecTxResult{ 54 Data: []byte{0}, 55 Code: abci.CodeTypeOK, Log: "", Events: nil, 56 }, 57 } 58 hash2 := tx2.Hash() 59 60 err = indexer.Index(txResult2) 61 require.NoError(t, err) 62 63 loadedTxResult2, err := indexer.Get(hash2) 64 require.NoError(t, err) 65 assert.True(t, proto.Equal(txResult2, loadedTxResult2)) 66 } 67 68 func TestTxSearch(t *testing.T) { 69 indexer := NewTxIndex(db.NewMemDB()) 70 71 txResult := txResultWithEvents([]abci.Event{ 72 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}}, 73 {Type: "account", Attributes: []abci.EventAttribute{{Key: "owner", Value: "/Ivan/", Index: true}}}, 74 {Type: "", Attributes: []abci.EventAttribute{{Key: "not_allowed", Value: "Vlad", Index: true}}}, 75 }) 76 hash := types.Tx(txResult.Tx).Hash() 77 78 err := indexer.Index(txResult) 79 require.NoError(t, err) 80 81 testCases := []struct { 82 q string 83 resultsLength int 84 }{ 85 // search by hash 86 {fmt.Sprintf("tx.hash = '%X'", hash), 1}, 87 // search by hash (lower) 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'", 0}, 93 {"account.owner = 'Ivan' AND account.number = 1", 0}, 94 {"account.owner = '/Ivan/'", 1}, 95 // search by exact match (two keys) 96 {"account.number = 1 AND account.owner = 'Vlad'", 0}, 97 {"account.owner = 'Vlad' AND account.number = 1", 0}, 98 {"account.number >= 1 AND account.owner = 'Vlad'", 0}, 99 {"account.owner = 'Vlad' AND account.number >= 1", 0}, 100 {"account.number <= 0", 0}, 101 {"account.number <= 0 AND account.owner = 'Ivan'", 0}, 102 {"account.number < 10000 AND account.owner = 'Ivan'", 0}, 103 // search using a prefix of the stored value 104 {"account.owner = 'Iv'", 0}, 105 // search by range 106 {"account.number >= 1 AND account.number <= 5", 1}, 107 // search by range and another key 108 {"account.number >= 1 AND account.owner = 'Ivan' AND account.number <= 5", 0}, 109 // search by range (lower bound) 110 {"account.number >= 1", 1}, 111 // search by range (upper bound) 112 {"account.number <= 5", 1}, 113 {"account.number <= 1", 1}, 114 // search using not allowed key 115 {"not_allowed = 'boom'", 0}, 116 {"not_allowed = 'Vlad'", 0}, 117 // search for not existing tx result 118 {"account.number >= 2 AND account.number <= 5 AND tx.height > 0", 0}, 119 // search using not existing key 120 {"account.date >= TIME 2013-05-03T14:45:00Z", 0}, 121 // search using CONTAINS 122 {"account.owner CONTAINS 'an'", 1}, 123 // search for non existing value using CONTAINS 124 {"account.owner CONTAINS 'Vlad'", 0}, 125 {"account.owner CONTAINS 'Ivann'", 0}, 126 {"account.owner CONTAINS 'IIvan'", 0}, 127 {"account.owner CONTAINS 'Iva n'", 0}, 128 {"account.owner CONTAINS ' Ivan'", 0}, 129 {"account.owner CONTAINS 'Ivan '", 0}, 130 // search using the wrong key (of numeric type) using CONTAINS 131 {"account.number CONTAINS 'Iv'", 0}, 132 // search using EXISTS 133 {"account.number EXISTS", 1}, 134 // search using EXISTS for non existing key 135 {"account.date EXISTS", 0}, 136 {"not_allowed EXISTS", 0}, 137 } 138 139 ctx := context.Background() 140 141 for _, tc := range testCases { 142 tc := tc 143 t.Run(tc.q, func(t *testing.T) { 144 results, err := indexer.Search(ctx, query.MustCompile(tc.q)) 145 assert.NoError(t, err) 146 147 assert.Len(t, results, tc.resultsLength) 148 if tc.resultsLength > 0 { 149 for _, txr := range results { 150 assert.True(t, proto.Equal(txResult, txr)) 151 } 152 } 153 }) 154 } 155 } 156 157 func TestTxSearchEventMatch(t *testing.T) { 158 indexer := NewTxIndex(db.NewMemDB()) 159 160 txResult := txResultWithEvents([]abci.Event{ 161 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}, {Key: "owner", Value: "Ana", Index: true}}}, 162 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "2", Index: true}, {Key: "owner", Value: "/Ivan/.test", Index: true}}}, 163 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "3", Index: false}, {Key: "owner", Value: "Mickey", Index: false}}}, 164 {Type: "", Attributes: []abci.EventAttribute{{Key: "not_allowed", Value: "Vlad", Index: true}}}, 165 }) 166 167 err := indexer.Index(txResult) 168 require.NoError(t, err) 169 170 testCases := map[string]struct { 171 q string 172 resultsLength int 173 }{ 174 "Return all events from a height": { 175 q: "tx.height = 1", 176 resultsLength: 1, 177 }, 178 "Don't match non-indexed events": { 179 q: "account.number = 3 AND account.owner = 'Mickey'", 180 resultsLength: 0, 181 }, 182 "Return all events from a height with range": { 183 q: "tx.height > 0", 184 resultsLength: 1, 185 }, 186 "Return all events from a height with range 2": { 187 q: "tx.height <= 1", 188 resultsLength: 1, 189 }, 190 "Return all events from a height (deduplicate height)": { 191 q: "tx.height = 1 AND tx.height = 1", 192 resultsLength: 1, 193 }, 194 "Match attributes with height range and event": { 195 q: "tx.height < 2 AND tx.height > 0 AND account.number > 0 AND account.number <= 1 AND account.owner CONTAINS 'Ana'", 196 resultsLength: 1, 197 }, 198 "Match attributes with multiple CONTAIN and height range": { 199 q: "tx.height < 2 AND tx.height > 0 AND account.number = 1 AND account.owner CONTAINS 'Ana' AND account.owner CONTAINS 'An'", 200 resultsLength: 1, 201 }, 202 "Match attributes with height range and event - no match": { 203 q: "tx.height < 2 AND tx.height > 0 AND account.number = 2 AND account.owner = 'Ana'", 204 resultsLength: 0, 205 }, 206 "Match attributes with event": { 207 q: "account.number = 2 AND account.owner = 'Ana' AND tx.height = 1", 208 resultsLength: 0, 209 }, 210 "Deduplication test - should return nothing if attribute repeats multiple times": { 211 q: "tx.height < 2 AND account.number = 3 AND account.number = 2 AND account.number = 5", 212 resultsLength: 0, 213 }, 214 " Match range with special character": { 215 q: "account.number < 2 AND account.owner = '/Ivan/.test'", 216 resultsLength: 0, 217 }, 218 " Match range with special character 2": { 219 q: "account.number <= 2 AND account.owner = '/Ivan/.test' AND tx.height > 0", 220 resultsLength: 1, 221 }, 222 " Match range with contains with multiple items": { 223 q: "account.number <= 2 AND account.owner CONTAINS '/Iv' AND account.owner CONTAINS 'an' AND tx.height = 1", 224 resultsLength: 1, 225 }, 226 " Match range with contains": { 227 q: "account.number <= 2 AND account.owner CONTAINS 'an' AND tx.height > 0", 228 resultsLength: 1, 229 }, 230 } 231 232 ctx := context.Background() 233 234 for _, tc := range testCases { 235 tc := tc 236 t.Run(tc.q, func(t *testing.T) { 237 results, err := indexer.Search(ctx, query.MustCompile(tc.q)) 238 assert.NoError(t, err) 239 240 assert.Len(t, results, tc.resultsLength) 241 if tc.resultsLength > 0 { 242 for _, txr := range results { 243 assert.True(t, proto.Equal(txResult, txr)) 244 } 245 } 246 }) 247 } 248 } 249 250 func TestTxSearchEventMatchByHeight(t *testing.T) { 251 252 indexer := NewTxIndex(db.NewMemDB()) 253 254 txResult := txResultWithEvents([]abci.Event{ 255 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}, {Key: "owner", Value: "Ana", Index: true}}}, 256 }) 257 258 err := indexer.Index(txResult) 259 require.NoError(t, err) 260 261 txResult10 := txResultWithEvents([]abci.Event{ 262 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}, {Key: "owner", Value: "/Ivan/.test", Index: true}}}, 263 }) 264 txResult10.Tx = types.Tx("HELLO WORLD 10") 265 txResult10.Height = 10 266 267 err = indexer.Index(txResult10) 268 require.NoError(t, err) 269 270 testCases := map[string]struct { 271 q string 272 resultsLength int 273 }{ 274 "Return all events from a height 1": { 275 q: "tx.height = 1", 276 resultsLength: 1, 277 }, 278 "Return all events from a height 10": { 279 q: "tx.height = 10", 280 resultsLength: 1, 281 }, 282 "Return all events from a height 5": { 283 q: "tx.height = 5", 284 resultsLength: 0, 285 }, 286 "Return all events from a height in [2; 5]": { 287 q: "tx.height >= 2 AND tx.height <= 5", 288 resultsLength: 0, 289 }, 290 "Return all events from a height in [1; 5]": { 291 q: "tx.height >= 1 AND tx.height <= 5", 292 resultsLength: 1, 293 }, 294 "Return all events from a height in [1; 10]": { 295 q: "tx.height >= 1 AND tx.height <= 10", 296 resultsLength: 2, 297 }, 298 "Return all events from a height in [1; 5] by account.number": { 299 q: "tx.height >= 1 AND tx.height <= 5 AND account.number=1", 300 resultsLength: 1, 301 }, 302 "Return all events from a height in [1; 10] by account.number 2": { 303 q: "tx.height >= 1 AND tx.height <= 10 AND account.number=1", 304 resultsLength: 2, 305 }, 306 } 307 308 ctx := context.Background() 309 310 for _, tc := range testCases { 311 tc := tc 312 t.Run(tc.q, func(t *testing.T) { 313 results, err := indexer.Search(ctx, query.MustCompile(tc.q)) 314 assert.NoError(t, err) 315 316 assert.Len(t, results, tc.resultsLength) 317 if tc.resultsLength > 0 { 318 for _, txr := range results { 319 if txr.Height == 1 { 320 assert.True(t, proto.Equal(txResult, txr)) 321 } else if txr.Height == 10 { 322 assert.True(t, proto.Equal(txResult10, txr)) 323 } else { 324 assert.True(t, false) 325 } 326 } 327 } 328 }) 329 } 330 } 331 332 func TestTxSearchWithCancelation(t *testing.T) { 333 indexer := NewTxIndex(db.NewMemDB()) 334 335 txResult := txResultWithEvents([]abci.Event{ 336 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}}, 337 {Type: "account", Attributes: []abci.EventAttribute{{Key: "owner", Value: "Ivan", Index: true}}}, 338 {Type: "", Attributes: []abci.EventAttribute{{Key: "not_allowed", Value: "Vlad", Index: true}}}, 339 }) 340 err := indexer.Index(txResult) 341 require.NoError(t, err) 342 343 ctx, cancel := context.WithCancel(context.Background()) 344 cancel() 345 results, err := indexer.Search(ctx, query.MustCompile(`account.number = 1`)) 346 assert.NoError(t, err) 347 assert.Empty(t, results) 348 } 349 350 func TestTxSearchDeprecatedIndexing(t *testing.T) { 351 indexer := NewTxIndex(db.NewMemDB()) 352 353 // index tx using events indexing (composite key) 354 txResult1 := txResultWithEvents([]abci.Event{ 355 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}}, 356 }) 357 hash1 := types.Tx(txResult1.Tx).Hash() 358 359 err := indexer.Index(txResult1) 360 require.NoError(t, err) 361 362 // index tx also using deprecated indexing (event as key) 363 txResult2 := txResultWithEvents(nil) 364 txResult2.Tx = types.Tx("HELLO WORLD 2") 365 366 hash2 := types.Tx(txResult2.Tx).Hash() 367 b := indexer.store.NewBatch() 368 369 rawBytes, err := proto.Marshal(txResult2) 370 require.NoError(t, err) 371 372 depKey := []byte(fmt.Sprintf("%s/%s/%d/%d", 373 "sender", 374 "addr1", 375 txResult2.Height, 376 txResult2.Index, 377 )) 378 379 err = b.Set(depKey, hash2) 380 require.NoError(t, err) 381 err = b.Set(keyForHeight(txResult2), hash2) 382 require.NoError(t, err) 383 err = b.Set(hash2, rawBytes) 384 require.NoError(t, err) 385 err = b.Write() 386 require.NoError(t, err) 387 388 testCases := []struct { 389 q string 390 results []*abci.TxResult 391 }{ 392 // search by hash 393 {fmt.Sprintf("tx.hash = '%X'", hash1), []*abci.TxResult{txResult1}}, 394 // search by hash 395 {fmt.Sprintf("tx.hash = '%X'", hash2), []*abci.TxResult{txResult2}}, 396 // search by exact match (one key) 397 {"account.number = 1", []*abci.TxResult{txResult1}}, 398 {"account.number >= 1 AND account.number <= 5", []*abci.TxResult{txResult1}}, 399 // search by range (lower bound) 400 {"account.number >= 1", []*abci.TxResult{txResult1}}, 401 // search by range (upper bound) 402 {"account.number <= 5", []*abci.TxResult{txResult1}}, 403 // search using not allowed key 404 {"not_allowed = 'boom'", []*abci.TxResult{}}, 405 // search for not existing tx result 406 {"account.number >= 2 AND account.number <= 5", []*abci.TxResult{}}, 407 // search using not existing key 408 {"account.date >= TIME 2013-05-03T14:45:00Z", []*abci.TxResult{}}, 409 // search by deprecated key 410 {"sender = 'addr1'", []*abci.TxResult{txResult2}}, 411 } 412 413 ctx := context.Background() 414 415 for _, tc := range testCases { 416 tc := tc 417 t.Run(tc.q, func(t *testing.T) { 418 results, err := indexer.Search(ctx, query.MustCompile(tc.q)) 419 require.NoError(t, err) 420 for _, txr := range results { 421 for _, tr := range tc.results { 422 assert.True(t, proto.Equal(tr, txr)) 423 } 424 } 425 }) 426 } 427 } 428 429 func TestTxSearchOneTxWithMultipleSameTagsButDifferentValues(t *testing.T) { 430 indexer := NewTxIndex(db.NewMemDB()) 431 432 txResult := txResultWithEvents([]abci.Event{ 433 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}}, 434 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "2", Index: true}}}, 435 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "3", Index: false}}}, 436 }) 437 438 err := indexer.Index(txResult) 439 require.NoError(t, err) 440 441 testCases := []struct { 442 name string 443 q string 444 found bool 445 }{ 446 { 447 q: "account.number >= 1", 448 found: true, 449 }, 450 { 451 q: "account.number > 2", 452 found: false, 453 }, 454 { 455 q: "account.number >= 1 AND tx.height = 3 AND tx.height > 0", 456 found: true, 457 }, 458 { 459 q: "account.number >= 1 AND tx.height > 0 AND tx.height = 3", 460 found: true, 461 }, 462 463 { 464 q: "account.number >= 1 AND tx.height = 1 AND tx.height = 2 AND tx.height = 3", 465 found: true, 466 }, 467 468 { 469 q: "account.number >= 1 AND tx.height = 3 AND tx.height = 2 AND tx.height = 1", 470 found: false, 471 }, 472 { 473 q: "account.number >= 1 AND tx.height = 3", 474 found: false, 475 }, 476 { 477 q: "account.number > 1 AND tx.height < 2", 478 found: true, 479 }, 480 { 481 q: "account.number >= 2", 482 found: true, 483 }, 484 { 485 q: "account.number <= 1", 486 found: true, 487 }, 488 { 489 q: "account.number = 'something'", 490 found: false, 491 }, 492 { 493 q: "account.number CONTAINS 'bla'", 494 found: false, 495 }, 496 } 497 498 ctx := context.Background() 499 500 for _, tc := range testCases { 501 results, err := indexer.Search(ctx, query.MustCompile(tc.q)) 502 assert.NoError(t, err) 503 n := 0 504 if tc.found { 505 n = 1 506 } 507 assert.Len(t, results, n) 508 assert.True(t, !tc.found || proto.Equal(txResult, results[0])) 509 510 } 511 } 512 513 func TestTxIndexDuplicatePreviouslySuccessful(t *testing.T) { 514 mockTx := types.Tx("MOCK_TX_HASH") 515 516 testCases := []struct { 517 name string 518 tx1 *abci.TxResult 519 tx2 *abci.TxResult 520 expOverwrite bool // do we expect the second tx to overwrite the first tx 521 }{ 522 { 523 "don't overwrite as a non-zero code was returned and the previous tx was successful", 524 &abci.TxResult{ 525 Height: 1, 526 Index: 0, 527 Tx: mockTx, 528 Result: abci.ExecTxResult{ 529 Code: abci.CodeTypeOK, 530 }, 531 }, 532 &abci.TxResult{ 533 Height: 2, 534 Index: 0, 535 Tx: mockTx, 536 Result: abci.ExecTxResult{ 537 Code: abci.CodeTypeOK + 1, 538 }, 539 }, 540 false, 541 }, 542 { 543 "overwrite as the previous tx was also unsuccessful", 544 &abci.TxResult{ 545 Height: 1, 546 Index: 0, 547 Tx: mockTx, 548 Result: abci.ExecTxResult{ 549 Code: abci.CodeTypeOK + 1, 550 }, 551 }, 552 &abci.TxResult{ 553 Height: 2, 554 Index: 0, 555 Tx: mockTx, 556 Result: abci.ExecTxResult{ 557 Code: abci.CodeTypeOK + 1, 558 }, 559 }, 560 true, 561 }, 562 { 563 "overwrite as the most recent tx was successful", 564 &abci.TxResult{ 565 Height: 1, 566 Index: 0, 567 Tx: mockTx, 568 Result: abci.ExecTxResult{ 569 Code: abci.CodeTypeOK, 570 }, 571 }, 572 &abci.TxResult{ 573 Height: 2, 574 Index: 0, 575 Tx: mockTx, 576 Result: abci.ExecTxResult{ 577 Code: abci.CodeTypeOK, 578 }, 579 }, 580 true, 581 }, 582 } 583 584 hash := mockTx.Hash() 585 586 for _, tc := range testCases { 587 t.Run(tc.name, func(t *testing.T) { 588 indexer := NewTxIndex(db.NewMemDB()) 589 590 // index the first tx 591 err := indexer.Index(tc.tx1) 592 require.NoError(t, err) 593 594 // index the same tx with different results 595 err = indexer.Index(tc.tx2) 596 require.NoError(t, err) 597 598 res, err := indexer.Get(hash) 599 require.NoError(t, err) 600 601 if tc.expOverwrite { 602 require.Equal(t, tc.tx2, res) 603 } else { 604 require.Equal(t, tc.tx1, res) 605 } 606 }) 607 } 608 } 609 610 func TestTxSearchMultipleTxs(t *testing.T) { 611 indexer := NewTxIndex(db.NewMemDB()) 612 613 // indexed first, but bigger height (to test the order of transactions) 614 txResult := txResultWithEvents([]abci.Event{ 615 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}}, 616 }) 617 618 txResult.Tx = types.Tx("Bob's account") 619 txResult.Height = 2 620 txResult.Index = 1 621 err := indexer.Index(txResult) 622 require.NoError(t, err) 623 624 // indexed second, but smaller height (to test the order of transactions) 625 txResult2 := txResultWithEvents([]abci.Event{ 626 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "2", Index: true}}}, 627 }) 628 txResult2.Tx = types.Tx("Alice's account") 629 txResult2.Height = 1 630 txResult2.Index = 2 631 632 err = indexer.Index(txResult2) 633 require.NoError(t, err) 634 635 // indexed third (to test the order of transactions) 636 txResult3 := txResultWithEvents([]abci.Event{ 637 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "3", Index: true}}}, 638 }) 639 txResult3.Tx = types.Tx("Jack's account") 640 txResult3.Height = 1 641 txResult3.Index = 1 642 err = indexer.Index(txResult3) 643 require.NoError(t, err) 644 645 // indexed fourth (to test we don't include txs with similar events) 646 // https://github.com/tendermint/tendermint/issues/2908 647 txResult4 := txResultWithEvents([]abci.Event{ 648 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number.id", Value: "1", Index: true}}}, 649 }) 650 txResult4.Tx = types.Tx("Mike's account") 651 txResult4.Height = 2 652 txResult4.Index = 2 653 err = indexer.Index(txResult4) 654 require.NoError(t, err) 655 656 ctx := context.Background() 657 658 results, err := indexer.Search(ctx, query.MustCompile(`account.number >= 1`)) 659 assert.NoError(t, err) 660 661 require.Len(t, results, 3) 662 } 663 664 func txResultWithEvents(events []abci.Event) *abci.TxResult { 665 tx := types.Tx("HELLO WORLD") 666 return &abci.TxResult{ 667 Height: 1, 668 Index: 0, 669 Tx: tx, 670 Result: abci.ExecTxResult{ 671 Data: []byte{0}, 672 Code: abci.CodeTypeOK, 673 Log: "", 674 Events: events, 675 }, 676 } 677 } 678 679 func benchmarkTxIndex(txsCount int64, b *testing.B) { 680 dir, err := os.MkdirTemp("", "tx_index_db") 681 require.NoError(b, err) 682 defer os.RemoveAll(dir) 683 684 store, err := db.NewDB("tx_index", "goleveldb", dir) 685 require.NoError(b, err) 686 indexer := NewTxIndex(store) 687 688 batch := txindex.NewBatch(txsCount) 689 txIndex := uint32(0) 690 for i := int64(0); i < txsCount; i++ { 691 tx := cmtrand.Bytes(250) 692 txResult := &abci.TxResult{ 693 Height: 1, 694 Index: txIndex, 695 Tx: tx, 696 Result: abci.ExecTxResult{ 697 Data: []byte{0}, 698 Code: abci.CodeTypeOK, 699 Log: "", 700 Events: []abci.Event{}, 701 }, 702 } 703 if err := batch.Add(txResult); err != nil { 704 b.Fatal(err) 705 } 706 txIndex++ 707 } 708 709 b.ResetTimer() 710 711 for n := 0; n < b.N; n++ { 712 err = indexer.AddBatch(batch) 713 } 714 if err != nil { 715 b.Fatal(err) 716 } 717 } 718 719 func TestBigInt(t *testing.T) { 720 indexer := NewTxIndex(db.NewMemDB()) 721 722 bigInt := "10000000000000000000" 723 bigIntPlus1 := "10000000000000000001" 724 bigFloat := bigInt + ".76" 725 bigFloatLower := bigInt + ".1" 726 bigFloatSmaller := "9999999999999999999" + ".1" 727 bigIntSmaller := "9999999999999999999" 728 729 txResult := txResultWithEvents([]abci.Event{ 730 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigInt, Index: true}}}, 731 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigFloatSmaller, Index: true}}}, 732 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigIntPlus1, Index: true}}}, 733 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigFloatLower, Index: true}}}, 734 {Type: "account", Attributes: []abci.EventAttribute{{Key: "owner", Value: "/Ivan/", Index: true}}}, 735 {Type: "", Attributes: []abci.EventAttribute{{Key: "not_allowed", Value: "Vlad", Index: true}}}, 736 }) 737 hash := types.Tx(txResult.Tx).Hash() 738 739 err := indexer.Index(txResult) 740 741 require.NoError(t, err) 742 743 txResult2 := txResultWithEvents([]abci.Event{ 744 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigFloat, Index: true}}}, 745 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigFloat, Index: true}, {Key: "amount", Value: "5", Index: true}}}, 746 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigIntSmaller, Index: true}}}, 747 {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigInt, Index: true}, {Key: "amount", Value: "3", Index: true}}}}) 748 749 txResult2.Tx = types.Tx("NEW TX") 750 txResult2.Height = 2 751 txResult2.Index = 2 752 753 hash2 := types.Tx(txResult2.Tx).Hash() 754 755 err = indexer.Index(txResult2) 756 require.NoError(t, err) 757 testCases := []struct { 758 q string 759 txRes *abci.TxResult 760 resultsLength int 761 }{ 762 // search by hash 763 {fmt.Sprintf("tx.hash = '%X'", hash), txResult, 1}, 764 // search by hash (lower) 765 {fmt.Sprintf("tx.hash = '%x'", hash), txResult, 1}, 766 {fmt.Sprintf("tx.hash = '%x'", hash2), txResult2, 1}, 767 // search by exact match (one key) - bigint 768 {"account.number >= " + bigInt, nil, 2}, 769 // search by exact match (one key) - bigint range 770 {"account.number >= " + bigInt + " AND tx.height > 0", nil, 2}, 771 {"account.number >= " + bigInt + " AND tx.height > 0 AND account.owner = '/Ivan/'", nil, 0}, 772 // Floats are not parsed 773 {"account.number >= " + bigInt + " AND tx.height > 0 AND account.amount > 4", txResult2, 1}, 774 {"account.number >= " + bigInt + " AND tx.height > 0 AND account.amount = 5", txResult2, 1}, 775 {"account.number >= " + bigInt + " AND account.amount <= 5", txResult2, 1}, 776 {"account.number > " + bigFloatSmaller + " AND account.amount = 3", txResult2, 1}, 777 {"account.number < " + bigInt + " AND tx.height >= 1", nil, 2}, 778 {"account.number < " + bigInt + " AND tx.height = 1", nil, 1}, 779 {"account.number < " + bigInt + " AND tx.height = 2", nil, 1}, 780 } 781 782 ctx := context.Background() 783 784 for _, tc := range testCases { 785 tc := tc 786 t.Run(tc.q, func(t *testing.T) { 787 results, err := indexer.Search(ctx, query.MustCompile(tc.q)) 788 assert.NoError(t, err) 789 assert.Len(t, results, tc.resultsLength) 790 if tc.resultsLength > 0 && tc.txRes != nil { 791 assert.True(t, proto.Equal(results[0], tc.txRes)) 792 } 793 }) 794 } 795 } 796 797 func BenchmarkTxIndex1(b *testing.B) { benchmarkTxIndex(1, b) } 798 func BenchmarkTxIndex500(b *testing.B) { benchmarkTxIndex(500, b) } 799 func BenchmarkTxIndex1000(b *testing.B) { benchmarkTxIndex(1000, b) } 800 func BenchmarkTxIndex2000(b *testing.B) { benchmarkTxIndex(2000, b) } 801 func BenchmarkTxIndex10000(b *testing.B) { benchmarkTxIndex(10000, b) }