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