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