github.com/okex/exchain@v1.8.0/libs/tendermint/state/txindex/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/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 13 db "github.com/okex/exchain/libs/tm-db" 14 15 abci "github.com/okex/exchain/libs/tendermint/abci/types" 16 "github.com/okex/exchain/libs/tendermint/libs/kv" 17 "github.com/okex/exchain/libs/tendermint/libs/pubsub/query" 18 tmrand "github.com/okex/exchain/libs/tendermint/libs/rand" 19 "github.com/okex/exchain/libs/tendermint/state/txindex" 20 "github.com/okex/exchain/libs/tendermint/types" 21 ) 22 23 func TestTxIndex(t *testing.T) { 24 indexer := NewTxIndex(db.NewMemDB()) 25 26 tx := types.Tx("HELLO WORLD") 27 txResult := &types.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(txResult.Height) 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.Equal(t, txResult, loadedTxResult) 48 49 tx2 := types.Tx("BYE BYE WORLD") 50 txResult2 := &types.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(txResult2.Height) 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.Equal(t, txResult2, loadedTxResult2) 67 } 68 69 func TestTxSearch(t *testing.T) { 70 allowedKeys := []string{"account.number", "account.owner", "account.date"} 71 indexer := NewTxIndex(db.NewMemDB(), IndexEvents(allowedKeys)) 72 73 txResult := txResultWithEvents([]abci.Event{ 74 {Type: "account", Attributes: []kv.Pair{{Key: []byte("number"), Value: []byte("1")}}}, 75 {Type: "account", Attributes: []kv.Pair{{Key: []byte("owner"), Value: []byte("Ivan")}}}, 76 {Type: "", Attributes: []kv.Pair{{Key: []byte("not_allowed"), Value: []byte("Vlad")}}}, 77 }) 78 hash := txResult.Tx.Hash(txResult.Height) 79 80 err := indexer.Index(txResult) 81 require.NoError(t, err) 82 83 testCases := []struct { 84 q string 85 resultsLength int 86 }{ 87 // search by hash 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'", 1}, 93 // search by exact match (two keys) 94 {"account.number = 1 AND account.owner = 'Vlad'", 0}, 95 {"account.owner = 'Vlad' AND account.number = 1", 0}, 96 {"account.number >= 1 AND account.owner = 'Vlad'", 0}, 97 {"account.owner = 'Vlad' AND account.number >= 1", 0}, 98 {"account.number <= 0", 0}, 99 {"account.number <= 0 AND account.owner = 'Ivan'", 0}, 100 // search using a prefix of the stored value 101 {"account.owner = 'Iv'", 0}, 102 // search by range 103 {"account.number >= 1 AND account.number <= 5", 1}, 104 // search by range (lower bound) 105 {"account.number >= 1", 1}, 106 // search by range (upper bound) 107 {"account.number <= 5", 1}, 108 // search using not allowed key 109 {"not_allowed = 'boom'", 0}, 110 // search for not existing tx result 111 {"account.number >= 2 AND account.number <= 5", 0}, 112 // search using not existing key 113 {"account.date >= TIME 2013-05-03T14:45:00Z", 0}, 114 // search using CONTAINS 115 {"account.owner CONTAINS 'an'", 1}, 116 // search for non existing value using CONTAINS 117 {"account.owner CONTAINS 'Vlad'", 0}, 118 // search using the wrong key (of numeric type) using CONTAINS 119 {"account.number CONTAINS 'Iv'", 0}, 120 } 121 122 ctx := context.Background() 123 124 for _, tc := range testCases { 125 tc := tc 126 t.Run(tc.q, func(t *testing.T) { 127 results, err := indexer.Search(ctx, query.MustParse(tc.q)) 128 assert.NoError(t, err) 129 130 assert.Len(t, results, tc.resultsLength) 131 if tc.resultsLength > 0 { 132 assert.Equal(t, []*types.TxResult{txResult}, results) 133 } 134 }) 135 } 136 } 137 138 func TestTxSearchWithCancelation(t *testing.T) { 139 allowedKeys := []string{"account.number", "account.owner", "account.date"} 140 indexer := NewTxIndex(db.NewMemDB(), IndexEvents(allowedKeys)) 141 142 txResult := txResultWithEvents([]abci.Event{ 143 {Type: "account", Attributes: []kv.Pair{{Key: []byte("number"), Value: []byte("1")}}}, 144 {Type: "account", Attributes: []kv.Pair{{Key: []byte("owner"), Value: []byte("Ivan")}}}, 145 {Type: "", Attributes: []kv.Pair{{Key: []byte("not_allowed"), Value: []byte("Vlad")}}}, 146 }) 147 err := indexer.Index(txResult) 148 require.NoError(t, err) 149 150 ctx, cancel := context.WithCancel(context.Background()) 151 cancel() 152 results, err := indexer.Search(ctx, query.MustParse("account.number = 1")) 153 assert.NoError(t, err) 154 assert.Empty(t, results) 155 } 156 157 func TestTxSearchDeprecatedIndexing(t *testing.T) { 158 allowedKeys := []string{"account.number", "sender"} 159 indexer := NewTxIndex(db.NewMemDB(), IndexEvents(allowedKeys)) 160 161 // index tx using events indexing (composite key) 162 txResult1 := txResultWithEvents([]abci.Event{ 163 {Type: "account", Attributes: []kv.Pair{{Key: []byte("number"), Value: []byte("1")}}}, 164 }) 165 hash1 := txResult1.Tx.Hash(txResult1.Height) 166 167 err := indexer.Index(txResult1) 168 require.NoError(t, err) 169 170 // index tx also using deprecated indexing (event as key) 171 txResult2 := txResultWithEvents(nil) 172 txResult2.Tx = types.Tx("HELLO WORLD 2") 173 174 hash2 := txResult2.Tx.Hash(txResult2.Height) 175 b := indexer.store.NewBatch() 176 177 rawBytes, err := cdc.MarshalBinaryBare(txResult2) 178 require.NoError(t, err) 179 180 depKey := []byte(fmt.Sprintf("%s/%s/%d/%d", 181 "sender", 182 "addr1", 183 txResult2.Height, 184 txResult2.Index, 185 )) 186 187 b.Set(depKey, hash2) 188 b.Set(keyForHeight(txResult2), hash2) 189 b.Set(hash2, rawBytes) 190 b.Write() 191 192 testCases := []struct { 193 q string 194 results []*types.TxResult 195 }{ 196 // search by hash 197 {fmt.Sprintf("tx.hash = '%X'", hash1), []*types.TxResult{txResult1}}, 198 // search by hash 199 {fmt.Sprintf("tx.hash = '%X'", hash2), []*types.TxResult{txResult2}}, 200 // search by exact match (one key) 201 {"account.number = 1", []*types.TxResult{txResult1}}, 202 {"account.number >= 1 AND account.number <= 5", []*types.TxResult{txResult1}}, 203 // search by range (lower bound) 204 {"account.number >= 1", []*types.TxResult{txResult1}}, 205 // search by range (upper bound) 206 {"account.number <= 5", []*types.TxResult{txResult1}}, 207 // search using not allowed key 208 {"not_allowed = 'boom'", []*types.TxResult{}}, 209 // search for not existing tx result 210 {"account.number >= 2 AND account.number <= 5", []*types.TxResult{}}, 211 // search using not existing key 212 {"account.date >= TIME 2013-05-03T14:45:00Z", []*types.TxResult{}}, 213 // search by deprecated key 214 {"sender = 'addr1'", []*types.TxResult{txResult2}}, 215 } 216 217 ctx := context.Background() 218 219 for _, tc := range testCases { 220 tc := tc 221 t.Run(tc.q, func(t *testing.T) { 222 results, err := indexer.Search(ctx, query.MustParse(tc.q)) 223 require.NoError(t, err) 224 require.Equal(t, results, tc.results) 225 }) 226 } 227 } 228 229 func TestTxSearchOneTxWithMultipleSameTagsButDifferentValues(t *testing.T) { 230 allowedKeys := []string{"account.number"} 231 indexer := NewTxIndex(db.NewMemDB(), IndexEvents(allowedKeys)) 232 233 txResult := txResultWithEvents([]abci.Event{ 234 {Type: "account", Attributes: []kv.Pair{{Key: []byte("number"), Value: []byte("1")}}}, 235 {Type: "account", Attributes: []kv.Pair{{Key: []byte("number"), Value: []byte("2")}}}, 236 }) 237 238 err := indexer.Index(txResult) 239 require.NoError(t, err) 240 241 ctx := context.Background() 242 243 results, err := indexer.Search(ctx, query.MustParse("account.number >= 1")) 244 assert.NoError(t, err) 245 246 assert.Len(t, results, 1) 247 assert.Equal(t, []*types.TxResult{txResult}, results) 248 } 249 250 func TestTxSearchMultipleTxs(t *testing.T) { 251 allowedKeys := []string{"account.number", "account.number.id"} 252 indexer := NewTxIndex(db.NewMemDB(), IndexEvents(allowedKeys)) 253 254 // indexed first, but bigger height (to test the order of transactions) 255 txResult := txResultWithEvents([]abci.Event{ 256 {Type: "account", Attributes: []kv.Pair{{Key: []byte("number"), Value: []byte("1")}}}, 257 }) 258 259 txResult.Tx = types.Tx("Bob's account") 260 txResult.Height = 2 261 txResult.Index = 1 262 err := indexer.Index(txResult) 263 require.NoError(t, err) 264 265 // indexed second, but smaller height (to test the order of transactions) 266 txResult2 := txResultWithEvents([]abci.Event{ 267 {Type: "account", Attributes: []kv.Pair{{Key: []byte("number"), Value: []byte("2")}}}, 268 }) 269 txResult2.Tx = types.Tx("Alice's account") 270 txResult2.Height = 1 271 txResult2.Index = 2 272 273 err = indexer.Index(txResult2) 274 require.NoError(t, err) 275 276 // indexed third (to test the order of transactions) 277 txResult3 := txResultWithEvents([]abci.Event{ 278 {Type: "account", Attributes: []kv.Pair{{Key: []byte("number"), Value: []byte("3")}}}, 279 }) 280 txResult3.Tx = types.Tx("Jack's account") 281 txResult3.Height = 1 282 txResult3.Index = 1 283 err = indexer.Index(txResult3) 284 require.NoError(t, err) 285 286 // indexed fourth (to test we don't include txs with similar events) 287 // https://github.com/tendermint/tendermint/issues/2908 288 txResult4 := txResultWithEvents([]abci.Event{ 289 {Type: "account", Attributes: []kv.Pair{{Key: []byte("number.id"), Value: []byte("1")}}}, 290 }) 291 txResult4.Tx = types.Tx("Mike's account") 292 txResult4.Height = 2 293 txResult4.Index = 2 294 err = indexer.Index(txResult4) 295 require.NoError(t, err) 296 297 ctx := context.Background() 298 299 results, err := indexer.Search(ctx, query.MustParse("account.number >= 1")) 300 assert.NoError(t, err) 301 302 require.Len(t, results, 3) 303 } 304 305 func txResultWithEvents(events []abci.Event) *types.TxResult { 306 tx := types.Tx("HELLO WORLD") 307 return &types.TxResult{ 308 Height: 1, 309 Index: 0, 310 Tx: tx, 311 Result: abci.ResponseDeliverTx{ 312 Data: []byte{0}, 313 Code: abci.CodeTypeOK, 314 Log: "", 315 Events: events, 316 }, 317 } 318 } 319 320 func benchmarkTxIndex(txsCount int64, b *testing.B) { 321 dir, err := ioutil.TempDir("", "tx_index_db") 322 if err != nil { 323 b.Fatal(err) 324 } 325 defer os.RemoveAll(dir) // nolint: errcheck 326 327 store := db.NewDB("tx_index", "goleveldb", dir) 328 indexer := NewTxIndex(store) 329 330 batch := txindex.NewBatch(txsCount) 331 txIndex := uint32(0) 332 for i := int64(0); i < txsCount; i++ { 333 tx := tmrand.Bytes(250) 334 txResult := &types.TxResult{ 335 Height: 1, 336 Index: txIndex, 337 Tx: tx, 338 Result: abci.ResponseDeliverTx{ 339 Data: []byte{0}, 340 Code: abci.CodeTypeOK, 341 Log: "", 342 Events: []abci.Event{}, 343 }, 344 } 345 if err := batch.Add(txResult); err != nil { 346 b.Fatal(err) 347 } 348 txIndex++ 349 } 350 351 b.ResetTimer() 352 353 for n := 0; n < b.N; n++ { 354 err = indexer.AddBatch(batch) 355 } 356 if err != nil { 357 b.Fatal(err) 358 } 359 } 360 361 func BenchmarkTxIndex1(b *testing.B) { benchmarkTxIndex(1, b) } 362 func BenchmarkTxIndex500(b *testing.B) { benchmarkTxIndex(500, b) } 363 func BenchmarkTxIndex1000(b *testing.B) { benchmarkTxIndex(1000, b) } 364 func BenchmarkTxIndex2000(b *testing.B) { benchmarkTxIndex(2000, b) } 365 func BenchmarkTxIndex10000(b *testing.B) { benchmarkTxIndex(10000, b) }