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