github.com/Finschia/ostracon@v1.1.5/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/gogo/protobuf/proto" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 13 abci "github.com/tendermint/tendermint/abci/types" 14 db "github.com/tendermint/tm-db" 15 16 ocabci "github.com/Finschia/ostracon/abci/types" 17 "github.com/Finschia/ostracon/libs/pubsub/query" 18 tmrand "github.com/Finschia/ostracon/libs/rand" 19 "github.com/Finschia/ostracon/state/txindex" 20 "github.com/Finschia/ostracon/types" 21 ) 22 23 func TestTxIndex(t *testing.T) { 24 indexer := 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: ocabci.CodeTypeOK, Log: "", Events: nil, 34 }, 35 } 36 hash := tx.Hash() 37 38 batch := txindex.NewBatch(1) 39 if err := batch.Add(txResult); err != nil { 40 t.Error(err) 41 } 42 err := indexer.AddBatch(batch) 43 require.NoError(t, err) 44 45 loadedTxResult, err := indexer.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: ocabci.CodeTypeOK, Log: "", Events: nil, 57 }, 58 } 59 hash2 := tx2.Hash() 60 61 err = indexer.Index(txResult2) 62 require.NoError(t, err) 63 64 loadedTxResult2, err := indexer.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: []byte("number"), Value: []byte("1"), Index: true}}}, 74 {Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("owner"), Value: []byte("Ivan"), Index: true}}}, 75 {Type: "", Attributes: []abci.EventAttribute{{Key: []byte("not_allowed"), Value: []byte("Vlad"), Index: true}}}, 76 }) 77 hash := types.Tx(txResult.Tx).Hash() 78 79 err := indexer.Index(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 } 124 125 ctx := context.Background() 126 127 for _, tc := range testCases { 128 tc := tc 129 t.Run(tc.q, func(t *testing.T) { 130 results, err := indexer.Search(ctx, query.MustParse(tc.q)) 131 assert.NoError(t, err) 132 133 assert.Len(t, results, tc.resultsLength) 134 if tc.resultsLength > 0 { 135 for _, txr := range results { 136 assert.True(t, proto.Equal(txResult, txr)) 137 } 138 } 139 }) 140 } 141 } 142 143 func TestTxSearchWithCancelation(t *testing.T) { 144 indexer := NewTxIndex(db.NewMemDB()) 145 146 txResult := txResultWithEvents([]abci.Event{ 147 {Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("1"), Index: true}}}, 148 {Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("owner"), Value: []byte("Ivan"), Index: true}}}, 149 {Type: "", Attributes: []abci.EventAttribute{{Key: []byte("not_allowed"), Value: []byte("Vlad"), Index: true}}}, 150 }) 151 err := indexer.Index(txResult) 152 require.NoError(t, err) 153 154 ctx, cancel := context.WithCancel(context.Background()) 155 cancel() 156 results, err := indexer.Search(ctx, query.MustParse("account.number = 1")) 157 assert.NoError(t, err) 158 assert.Empty(t, results) 159 } 160 161 func TestTxSearchDeprecatedIndexing(t *testing.T) { 162 indexer := NewTxIndex(db.NewMemDB()) 163 164 // index tx using events indexing (composite key) 165 txResult1 := txResultWithEvents([]abci.Event{ 166 {Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("1"), Index: true}}}, 167 }) 168 hash1 := types.Tx(txResult1.Tx).Hash() 169 170 err := indexer.Index(txResult1) 171 require.NoError(t, err) 172 173 // index tx also using deprecated indexing (event as key) 174 txResult2 := txResultWithEvents(nil) 175 txResult2.Tx = types.Tx("HELLO WORLD 2") 176 177 hash2 := types.Tx(txResult2.Tx).Hash() 178 b := indexer.store.NewBatch() 179 180 rawBytes, err := proto.Marshal(txResult2) 181 require.NoError(t, err) 182 183 depKey := []byte(fmt.Sprintf("%s/%s/%d/%d", 184 "sender", 185 "addr1", 186 txResult2.Height, 187 txResult2.Index, 188 )) 189 190 err = b.Set(depKey, hash2) 191 require.NoError(t, err) 192 err = b.Set(keyForHeight(txResult2), hash2) 193 require.NoError(t, err) 194 err = b.Set(hash2, rawBytes) 195 require.NoError(t, err) 196 err = b.Write() 197 require.NoError(t, err) 198 199 testCases := []struct { 200 q string 201 results []*abci.TxResult 202 }{ 203 // search by hash 204 {fmt.Sprintf("tx.hash = '%X'", hash1), []*abci.TxResult{txResult1}}, 205 // search by hash 206 {fmt.Sprintf("tx.hash = '%X'", hash2), []*abci.TxResult{txResult2}}, 207 // search by exact match (one key) 208 {"account.number = 1", []*abci.TxResult{txResult1}}, 209 {"account.number >= 1 AND account.number <= 5", []*abci.TxResult{txResult1}}, 210 // search by range (lower bound) 211 {"account.number >= 1", []*abci.TxResult{txResult1}}, 212 // search by range (upper bound) 213 {"account.number <= 5", []*abci.TxResult{txResult1}}, 214 // search using not allowed key 215 {"not_allowed = 'boom'", []*abci.TxResult{}}, 216 // search for not existing tx result 217 {"account.number >= 2 AND account.number <= 5", []*abci.TxResult{}}, 218 // search using not existing key 219 {"account.date >= TIME 2013-05-03T14:45:00Z", []*abci.TxResult{}}, 220 // search by deprecated key 221 {"sender = 'addr1'", []*abci.TxResult{txResult2}}, 222 } 223 224 ctx := context.Background() 225 226 for _, tc := range testCases { 227 tc := tc 228 t.Run(tc.q, func(t *testing.T) { 229 results, err := indexer.Search(ctx, query.MustParse(tc.q)) 230 require.NoError(t, err) 231 for _, txr := range results { 232 for _, tr := range tc.results { 233 assert.True(t, proto.Equal(tr, txr)) 234 } 235 } 236 }) 237 } 238 } 239 240 func TestTxSearchOneTxWithMultipleSameTagsButDifferentValues(t *testing.T) { 241 indexer := NewTxIndex(db.NewMemDB()) 242 243 txResult := txResultWithEvents([]abci.Event{ 244 {Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("1"), Index: true}}}, 245 {Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("2"), Index: true}}}, 246 }) 247 248 err := indexer.Index(txResult) 249 require.NoError(t, err) 250 251 ctx := context.Background() 252 253 results, err := indexer.Search(ctx, query.MustParse("account.number >= 1")) 254 assert.NoError(t, err) 255 256 assert.Len(t, results, 1) 257 for _, txr := range results { 258 assert.True(t, proto.Equal(txResult, txr)) 259 } 260 } 261 262 func TestTxIndexDuplicatePreviouslySuccessful(t *testing.T) { 263 mockTx := types.Tx("MOCK_TX_HASH") 264 265 testCases := []struct { 266 name string 267 tx1 *abci.TxResult 268 tx2 *abci.TxResult 269 expOverwrite bool // do we expect the second tx to overwrite the first tx 270 }{ 271 { 272 "don't overwrite as a non-zero code was returned and the previous tx was successful", 273 &abci.TxResult{ 274 Height: 1, 275 Index: 0, 276 Tx: mockTx, 277 Result: abci.ResponseDeliverTx{ 278 Code: abci.CodeTypeOK, 279 }, 280 }, 281 &abci.TxResult{ 282 Height: 2, 283 Index: 0, 284 Tx: mockTx, 285 Result: abci.ResponseDeliverTx{ 286 Code: abci.CodeTypeOK + 1, 287 }, 288 }, 289 false, 290 }, 291 { 292 "overwrite as the previous tx was also unsuccessful", 293 &abci.TxResult{ 294 Height: 1, 295 Index: 0, 296 Tx: mockTx, 297 Result: abci.ResponseDeliverTx{ 298 Code: abci.CodeTypeOK + 1, 299 }, 300 }, 301 &abci.TxResult{ 302 Height: 2, 303 Index: 0, 304 Tx: mockTx, 305 Result: abci.ResponseDeliverTx{ 306 Code: abci.CodeTypeOK + 1, 307 }, 308 }, 309 true, 310 }, 311 { 312 "overwrite as the most recent tx was successful", 313 &abci.TxResult{ 314 Height: 1, 315 Index: 0, 316 Tx: mockTx, 317 Result: abci.ResponseDeliverTx{ 318 Code: abci.CodeTypeOK, 319 }, 320 }, 321 &abci.TxResult{ 322 Height: 2, 323 Index: 0, 324 Tx: mockTx, 325 Result: abci.ResponseDeliverTx{ 326 Code: abci.CodeTypeOK, 327 }, 328 }, 329 true, 330 }, 331 } 332 333 hash := mockTx.Hash() 334 335 for _, tc := range testCases { 336 t.Run(tc.name, func(t *testing.T) { 337 indexer := NewTxIndex(db.NewMemDB()) 338 339 // index the first tx 340 err := indexer.Index(tc.tx1) 341 require.NoError(t, err) 342 343 // index the same tx with different results 344 err = indexer.Index(tc.tx2) 345 require.NoError(t, err) 346 347 res, err := indexer.Get(hash) 348 require.NoError(t, err) 349 350 if tc.expOverwrite { 351 require.Equal(t, tc.tx2, res) 352 } else { 353 require.Equal(t, tc.tx1, res) 354 } 355 }) 356 } 357 } 358 359 func TestTxSearchMultipleTxs(t *testing.T) { 360 indexer := NewTxIndex(db.NewMemDB()) 361 362 // indexed first, but bigger height (to test the order of transactions) 363 txResult := txResultWithEvents([]abci.Event{ 364 {Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("1"), Index: true}}}, 365 }) 366 367 txResult.Tx = types.Tx("Bob's account") 368 txResult.Height = 2 369 txResult.Index = 1 370 err := indexer.Index(txResult) 371 require.NoError(t, err) 372 373 // indexed second, but smaller height (to test the order of transactions) 374 txResult2 := txResultWithEvents([]abci.Event{ 375 {Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("2"), Index: true}}}, 376 }) 377 txResult2.Tx = types.Tx("Alice's account") 378 txResult2.Height = 1 379 txResult2.Index = 2 380 381 err = indexer.Index(txResult2) 382 require.NoError(t, err) 383 384 // indexed third (to test the order of transactions) 385 txResult3 := txResultWithEvents([]abci.Event{ 386 {Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("3"), Index: true}}}, 387 }) 388 txResult3.Tx = types.Tx("Jack's account") 389 txResult3.Height = 1 390 txResult3.Index = 1 391 err = indexer.Index(txResult3) 392 require.NoError(t, err) 393 394 // indexed fourth (to test we don't include txs with similar events) 395 // https://github.com/tendermint/tendermint/issues/2908 396 txResult4 := txResultWithEvents([]abci.Event{ 397 {Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number.id"), Value: []byte("1"), Index: true}}}, 398 }) 399 txResult4.Tx = types.Tx("Mike's account") 400 txResult4.Height = 2 401 txResult4.Index = 2 402 err = indexer.Index(txResult4) 403 require.NoError(t, err) 404 405 ctx := context.Background() 406 407 results, err := indexer.Search(ctx, query.MustParse("account.number >= 1")) 408 assert.NoError(t, err) 409 410 require.Len(t, results, 3) 411 } 412 413 func txResultWithEvents(events []abci.Event) *abci.TxResult { 414 tx := types.Tx("HELLO WORLD") 415 return &abci.TxResult{ 416 Height: 1, 417 Index: 0, 418 Tx: tx, 419 Result: abci.ResponseDeliverTx{ 420 Data: []byte{0}, 421 Code: ocabci.CodeTypeOK, 422 Log: "", 423 Events: events, 424 }, 425 } 426 } 427 428 func benchmarkTxIndex(txsCount int64, b *testing.B) { 429 dir, err := os.MkdirTemp("", "tx_index_db") 430 require.NoError(b, err) 431 defer os.RemoveAll(dir) 432 433 store, err := db.NewDB("tx_index", "goleveldb", dir) 434 require.NoError(b, err) 435 indexer := NewTxIndex(store) 436 437 batch := txindex.NewBatch(txsCount) 438 txIndex := uint32(0) 439 for i := int64(0); i < txsCount; i++ { 440 tx := tmrand.Bytes(250) 441 txResult := &abci.TxResult{ 442 Height: 1, 443 Index: txIndex, 444 Tx: tx, 445 Result: abci.ResponseDeliverTx{ 446 Data: []byte{0}, 447 Code: ocabci.CodeTypeOK, 448 Log: "", 449 Events: []abci.Event{}, 450 }, 451 } 452 if err := batch.Add(txResult); err != nil { 453 b.Fatal(err) 454 } 455 txIndex++ 456 } 457 458 b.ResetTimer() 459 460 for n := 0; n < b.N; n++ { 461 err = indexer.AddBatch(batch) 462 } 463 if err != nil { 464 b.Fatal(err) 465 } 466 } 467 468 func BenchmarkTxIndex1(b *testing.B) { benchmarkTxIndex(1, b) } 469 func BenchmarkTxIndex500(b *testing.B) { benchmarkTxIndex(500, b) } 470 func BenchmarkTxIndex1000(b *testing.B) { benchmarkTxIndex(1000, b) } 471 func BenchmarkTxIndex2000(b *testing.B) { benchmarkTxIndex(2000, b) } 472 func BenchmarkTxIndex10000(b *testing.B) { benchmarkTxIndex(10000, b) }