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