github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/topicsdb/topicsdb_test.go (about) 1 package topicsdb 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "os" 8 "runtime/debug" 9 "testing" 10 11 "github.com/stretchr/testify/require" 12 "github.com/unicornultrafoundation/go-helios/hash" 13 "github.com/unicornultrafoundation/go-helios/native/idx" 14 "github.com/unicornultrafoundation/go-helios/u2udb/memorydb" 15 "github.com/unicornultrafoundation/go-u2u/common" 16 "github.com/unicornultrafoundation/go-u2u/core/types" 17 18 "github.com/unicornultrafoundation/go-u2u/logger" 19 "github.com/unicornultrafoundation/go-u2u/utils/dbutil/threads" 20 ) 21 22 func TestMain(m *testing.M) { 23 debug.SetMaxThreads(20) 24 25 os.Exit(m.Run()) 26 } 27 28 func newTestIndex() *index { 29 mem := threads.CountedDBProducer(memorydb.NewProducer("")) 30 return newIndex(mem) 31 } 32 33 func TestIndexSearchMultyVariants(t *testing.T) { 34 logger.SetTestMode(t) 35 var ( 36 hash1 = common.BytesToHash([]byte("topic1")) 37 hash2 = common.BytesToHash([]byte("topic2")) 38 hash3 = common.BytesToHash([]byte("topic3")) 39 hash4 = common.BytesToHash([]byte("topic4")) 40 addr1 = randAddress() 41 addr2 = randAddress() 42 addr3 = randAddress() 43 addr4 = randAddress() 44 ) 45 testdata := []*types.Log{{ 46 BlockNumber: 1, 47 Address: addr1, 48 Topics: []common.Hash{hash1, hash1, hash1}, 49 }, { 50 BlockNumber: 3, 51 Address: addr2, 52 Topics: []common.Hash{hash2, hash2, hash2}, 53 }, { 54 BlockNumber: 998, 55 Address: addr3, 56 Topics: []common.Hash{hash3, hash3, hash3}, 57 }, { 58 BlockNumber: 999, 59 Address: addr4, 60 Topics: []common.Hash{hash4, hash4, hash4}, 61 }, 62 } 63 64 index := newTestIndex() 65 66 for _, l := range testdata { 67 err := index.Push(l) 68 require.NoError(t, err) 69 } 70 71 // require.ElementsMatchf(testdata, got, "") doesn't work properly here, 72 // so use check() 73 check := func(require *require.Assertions, got []*types.Log) { 74 count := 0 75 for _, a := range got { 76 for _, b := range testdata { 77 if b.Address == a.Address { 78 require.ElementsMatch(a.Topics, b.Topics) 79 count++ 80 break 81 } 82 } 83 } 84 } 85 86 pooled := withThreadPool{index} 87 88 for dsc, method := range map[string]func(context.Context, idx.Block, idx.Block, [][]common.Hash) ([]*types.Log, error){ 89 "index": index.FindInBlocks, 90 "pooled": pooled.FindInBlocks, 91 } { 92 t.Run(dsc, func(t *testing.T) { 93 94 t.Run("With no addresses", func(t *testing.T) { 95 require := require.New(t) 96 got, err := method(nil, 0, 1000, [][]common.Hash{ 97 {}, 98 {hash1, hash2, hash3, hash4}, 99 {}, 100 {hash1, hash2, hash3, hash4}, 101 }) 102 require.NoError(err) 103 require.Equal(4, len(got)) 104 check(require, got) 105 }) 106 107 t.Run("With addresses", func(t *testing.T) { 108 require := require.New(t) 109 got, err := method(nil, 0, 1000, [][]common.Hash{ 110 {addr1.Hash(), addr2.Hash(), addr3.Hash(), addr4.Hash()}, 111 {hash1, hash2, hash3, hash4}, 112 {}, 113 {hash1, hash2, hash3, hash4}, 114 }) 115 require.NoError(err) 116 require.Equal(4, len(got)) 117 check(require, got) 118 }) 119 120 t.Run("With block range", func(t *testing.T) { 121 require := require.New(t) 122 got, err := method(nil, 2, 998, [][]common.Hash{ 123 {addr1.Hash(), addr2.Hash(), addr3.Hash(), addr4.Hash()}, 124 {hash1, hash2, hash3, hash4}, 125 {}, 126 {hash1, hash2, hash3, hash4}, 127 }) 128 require.NoError(err) 129 require.Equal(2, len(got)) 130 check(require, got) 131 }) 132 133 t.Run("With addresses and blocks", func(t *testing.T) { 134 require := require.New(t) 135 136 got1, err := method(nil, 2, 998, [][]common.Hash{ 137 {addr1.Hash(), addr2.Hash(), addr3.Hash(), addr4.Hash()}, 138 {hash1, hash2, hash3, hash4}, 139 {}, 140 {hash1, hash2, hash3, hash4}, 141 }) 142 require.NoError(err) 143 require.Equal(2, len(got1)) 144 check(require, got1) 145 146 got2, err := method(nil, 2, 998, [][]common.Hash{ 147 {addr4.Hash(), addr3.Hash(), addr2.Hash(), addr1.Hash()}, 148 {hash1, hash2, hash3, hash4}, 149 {}, 150 {hash1, hash2, hash3, hash4}, 151 }) 152 require.NoError(err) 153 require.ElementsMatch(got1, got2) 154 }) 155 156 }) 157 } 158 } 159 160 func TestIndexSearchShortCircuits(t *testing.T) { 161 logger.SetTestMode(t) 162 var ( 163 hash1 = common.BytesToHash([]byte("topic1")) 164 hash2 = common.BytesToHash([]byte("topic2")) 165 hash3 = common.BytesToHash([]byte("topic3")) 166 hash4 = common.BytesToHash([]byte("topic4")) 167 addr1 = randAddress() 168 addr2 = randAddress() 169 ) 170 testdata := []*types.Log{{ 171 BlockNumber: 1, 172 Address: addr1, 173 Topics: []common.Hash{hash1, hash2}, 174 }, { 175 BlockNumber: 3, 176 Address: addr1, 177 Topics: []common.Hash{hash1, hash2, hash3}, 178 }, { 179 BlockNumber: 998, 180 Address: addr2, 181 Topics: []common.Hash{hash1, hash2, hash4}, 182 }, { 183 BlockNumber: 999, 184 Address: addr1, 185 Topics: []common.Hash{hash1, hash2, hash4}, 186 }, 187 } 188 189 index := newTestIndex() 190 191 for _, l := range testdata { 192 err := index.Push(l) 193 require.NoError(t, err) 194 } 195 196 pooled := withThreadPool{index} 197 198 for dsc, method := range map[string]func(context.Context, idx.Block, idx.Block, [][]common.Hash) ([]*types.Log, error){ 199 "index": index.FindInBlocks, 200 "pooled": pooled.FindInBlocks, 201 } { 202 t.Run(dsc, func(t *testing.T) { 203 204 t.Run("topics count 1", func(t *testing.T) { 205 require := require.New(t) 206 got, err := method(nil, 0, 1000, [][]common.Hash{ 207 {addr1.Hash()}, 208 {}, 209 {}, 210 {hash3}, 211 }) 212 require.NoError(err) 213 require.Equal(1, len(got)) 214 }) 215 216 t.Run("topics count 2", func(t *testing.T) { 217 require := require.New(t) 218 got, err := method(nil, 0, 1000, [][]common.Hash{ 219 {addr1.Hash()}, 220 {}, 221 {}, 222 {hash3, hash4}, 223 }) 224 require.NoError(err) 225 require.Equal(2, len(got)) 226 }) 227 228 t.Run("block range", func(t *testing.T) { 229 require := require.New(t) 230 got, err := method(nil, 3, 998, [][]common.Hash{ 231 {addr1.Hash()}, 232 {}, 233 {}, 234 {hash3, hash4}, 235 }) 236 require.NoError(err) 237 require.Equal(1, len(got)) 238 }) 239 240 }) 241 } 242 } 243 244 func TestIndexSearchSingleVariant(t *testing.T) { 245 logger.SetTestMode(t) 246 247 topics, recs, topics4rec := genTestData(100) 248 249 index := newTestIndex() 250 251 for _, rec := range recs { 252 err := index.Push(rec) 253 require.NoError(t, err) 254 } 255 256 pooled := withThreadPool{index} 257 258 for dsc, method := range map[string]func(context.Context, idx.Block, idx.Block, [][]common.Hash) ([]*types.Log, error){ 259 "index": index.FindInBlocks, 260 "pooled": pooled.FindInBlocks, 261 } { 262 t.Run(dsc, func(t *testing.T) { 263 require := require.New(t) 264 265 for i := 0; i < len(topics); i++ { 266 from, to := topics4rec(i) 267 tt := topics[from : to-1] 268 269 qq := make([][]common.Hash, len(tt)+1) 270 for pos, t := range tt { 271 qq[pos+1] = []common.Hash{t} 272 } 273 274 got, err := method(nil, 0, 1000, qq) 275 require.NoError(err) 276 277 var expect []*types.Log 278 for j, rec := range recs { 279 if f, t := topics4rec(j); f != from || t != to { 280 continue 281 } 282 expect = append(expect, rec) 283 } 284 285 require.ElementsMatchf(expect, got, "step %d", i) 286 } 287 288 }) 289 } 290 } 291 292 func TestIndexSearchSimple(t *testing.T) { 293 logger.SetTestMode(t) 294 295 var ( 296 hash1 = common.BytesToHash([]byte("topic1")) 297 hash2 = common.BytesToHash([]byte("topic2")) 298 hash3 = common.BytesToHash([]byte("topic3")) 299 hash4 = common.BytesToHash([]byte("topic4")) 300 addr = randAddress() 301 ) 302 testdata := []*types.Log{{ 303 BlockNumber: 1, 304 Address: addr, 305 Topics: []common.Hash{hash1}, 306 }, { 307 BlockNumber: 2, 308 Address: addr, 309 Topics: []common.Hash{hash2}, 310 }, { 311 BlockNumber: 998, 312 Address: addr, 313 Topics: []common.Hash{hash3}, 314 }, { 315 BlockNumber: 999, 316 Address: addr, 317 Topics: []common.Hash{hash4}, 318 }, 319 } 320 321 index := newTestIndex() 322 323 for _, l := range testdata { 324 err := index.Push(l) 325 require.NoError(t, err) 326 } 327 328 var ( 329 got []*types.Log 330 err error 331 ) 332 333 pooled := withThreadPool{index} 334 335 for dsc, method := range map[string]func(context.Context, idx.Block, idx.Block, [][]common.Hash) ([]*types.Log, error){ 336 "index": index.FindInBlocks, 337 "pooled": pooled.FindInBlocks, 338 } { 339 t.Run(dsc, func(t *testing.T) { 340 require := require.New(t) 341 342 got, err = method(nil, 0, 0xffffffff, [][]common.Hash{ 343 {addr.Hash()}, 344 {hash1}, 345 }) 346 require.NoError(err) 347 require.Equal(1, len(got)) 348 349 got, err = method(nil, 0, 0xffffffff, [][]common.Hash{ 350 {addr.Hash()}, 351 {hash2}, 352 }) 353 require.NoError(err) 354 require.Equal(1, len(got)) 355 356 got, err = method(nil, 0, 0xffffffff, [][]common.Hash{ 357 {addr.Hash()}, 358 {hash3}, 359 }) 360 require.NoError(err) 361 require.Equal(1, len(got)) 362 }) 363 } 364 365 } 366 367 func TestMaxTopicsCount(t *testing.T) { 368 logger.SetTestMode(t) 369 370 testdata := &types.Log{ 371 BlockNumber: 1, 372 Address: randAddress(), 373 Topics: make([]common.Hash, maxTopicsCount), 374 } 375 pattern := make([][]common.Hash, maxTopicsCount+1) 376 pattern[0] = []common.Hash{testdata.Address.Hash()} 377 for i := range testdata.Topics { 378 testdata.Topics[i] = common.BytesToHash([]byte(fmt.Sprintf("topic%d", i))) 379 pattern[0] = append(pattern[0], testdata.Topics[i]) 380 pattern[i+1] = []common.Hash{testdata.Topics[i]} 381 } 382 383 index := newTestIndex() 384 err := index.Push(testdata) 385 require.NoError(t, err) 386 387 pooled := withThreadPool{index} 388 389 for dsc, method := range map[string]func(context.Context, idx.Block, idx.Block, [][]common.Hash) ([]*types.Log, error){ 390 "index": index.FindInBlocks, 391 "pooled": pooled.FindInBlocks, 392 } { 393 t.Run(dsc, func(t *testing.T) { 394 require := require.New(t) 395 396 got, err := method(nil, 0, 0xffffffff, pattern) 397 require.NoError(err) 398 require.Equal(1, len(got)) 399 require.Equal(maxTopicsCount, len(got[0].Topics)) 400 }) 401 } 402 403 require.Equal(t, maxTopicsCount+1, len(pattern)) 404 require.Equal(t, maxTopicsCount+1, len(pattern[0])) 405 } 406 407 func TestPatternLimit(t *testing.T) { 408 logger.SetTestMode(t) 409 require := require.New(t) 410 411 data := []struct { 412 pattern [][]common.Hash 413 exp [][]common.Hash 414 err error 415 }{ 416 { 417 pattern: [][]common.Hash{}, 418 exp: [][]common.Hash{}, 419 err: ErrEmptyTopics, 420 }, 421 { 422 pattern: [][]common.Hash{[]common.Hash{}, []common.Hash{}, []common.Hash{}}, 423 exp: [][]common.Hash{[]common.Hash{}, []common.Hash{}, []common.Hash{}}, 424 err: ErrEmptyTopics, 425 }, 426 { 427 pattern: [][]common.Hash{ 428 []common.Hash{hash.FakeHash(1), hash.FakeHash(1)}, []common.Hash{hash.FakeHash(2), hash.FakeHash(2)}, []common.Hash{hash.FakeHash(3), hash.FakeHash(4)}}, 429 exp: [][]common.Hash{ 430 []common.Hash{hash.FakeHash(1)}, []common.Hash{hash.FakeHash(2)}, []common.Hash{hash.FakeHash(3), hash.FakeHash(4)}}, 431 err: nil, 432 }, 433 { 434 pattern: [][]common.Hash{ 435 []common.Hash{hash.FakeHash(1), hash.FakeHash(2)}, []common.Hash{hash.FakeHash(3), hash.FakeHash(4)}, []common.Hash{hash.FakeHash(5), hash.FakeHash(6)}}, 436 exp: [][]common.Hash{ 437 []common.Hash{hash.FakeHash(1), hash.FakeHash(2)}, []common.Hash{hash.FakeHash(3), hash.FakeHash(4)}, []common.Hash{hash.FakeHash(5), hash.FakeHash(6)}}, 438 err: nil, 439 }, 440 { 441 pattern: append(append(make([][]common.Hash, maxTopicsCount), []common.Hash{hash.FakeHash(1)}), []common.Hash{hash.FakeHash(1)}), 442 exp: append(make([][]common.Hash, maxTopicsCount), []common.Hash{hash.FakeHash(1)}), 443 err: nil, 444 }, 445 } 446 447 for i, x := range data { 448 got, err := limitPattern(x.pattern) 449 require.Equal(len(x.exp), len(got)) 450 for j := range got { 451 require.ElementsMatch(x.exp[j], got[j], i, j) 452 } 453 require.Equal(x.err, err, i) 454 } 455 } 456 457 func TestKvdbThreadsPoolLimit(t *testing.T) { 458 logger.SetTestMode(t) 459 460 const N = 100 461 462 _, recs, _ := genTestData(N) 463 index := newTestIndex() 464 for _, rec := range recs { 465 err := index.Push(rec) 466 require.NoError(t, err) 467 } 468 469 pooled := withThreadPool{index} 470 471 for dsc, method := range map[string]func(context.Context, idx.Block, idx.Block, [][]common.Hash) ([]*types.Log, error){ 472 "index": index.FindInBlocks, 473 "pooled": pooled.FindInBlocks, 474 } { 475 t.Run(dsc, func(t *testing.T) { 476 require := require.New(t) 477 478 topics := make([]common.Hash, threads.GlobalPool.Cap()+1) 479 for i := range topics { 480 topics[i] = hash.FakeHash(int64(i)) 481 } 482 require.Less(threads.GlobalPool.Cap(), len(topics)) 483 qq := make([][]common.Hash, 3) 484 485 // one big pattern 486 qq[1] = topics 487 got, err := method(nil, 0, 1000, qq) 488 require.NoError(err) 489 require.Equal(N, len(got)) 490 491 // more than one big pattern 492 qq[1], qq[2] = topics, topics 493 got, err = method(nil, 0, 1000, qq) 494 switch dsc { 495 case "index": 496 require.NoError(err) 497 require.Equal(N, len(got)) 498 case "pooled": 499 require.Equal(ErrTooBigTopics, err) 500 require.Equal(0, len(got)) 501 502 } 503 504 }) 505 } 506 } 507 508 func genTestData(count int) ( 509 topics []common.Hash, 510 recs []*types.Log, 511 topics4rec func(rec int) (from, to int), 512 ) { 513 const ( 514 period = 5 515 ) 516 517 topics = make([]common.Hash, period) 518 for i := range topics { 519 topics[i] = hash.FakeHash(int64(i)) 520 } 521 522 topics4rec = func(rec int) (from, to int) { 523 from = rec % (period - 3) 524 to = from + 3 525 return 526 } 527 528 recs = make([]*types.Log, count) 529 for i := range recs { 530 from, to := topics4rec(i) 531 r := &types.Log{ 532 BlockNumber: uint64(i / period), 533 BlockHash: hash.FakeHash(int64(i / period)), 534 TxHash: hash.FakeHash(int64(i % period)), 535 Index: uint(i % period), 536 Address: randAddress(), 537 Topics: topics[from:to], 538 Data: make([]byte, i), 539 } 540 _, _ = rand.Read(r.Data) 541 recs[i] = r 542 } 543 544 return 545 } 546 547 func randAddress() (addr common.Address) { 548 n, err := rand.Read(addr[:]) 549 if err != nil { 550 panic(err) 551 } 552 if n != common.AddressLength { 553 panic("address is not filled") 554 } 555 return 556 }