github.com/iotexproject/iotex-core@v1.14.1-rc1/blockindex/bloomfilterindexer.go (about) 1 // Copyright (c) 2020 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package blockindex 7 8 import ( 9 "context" 10 "sync" 11 "time" 12 13 "github.com/iotexproject/go-pkgs/bloom" 14 "github.com/pkg/errors" 15 "golang.org/x/sync/errgroup" 16 17 "github.com/iotexproject/iotex-core/action" 18 filter "github.com/iotexproject/iotex-core/api/logfilter" 19 "github.com/iotexproject/iotex-core/blockchain/block" 20 "github.com/iotexproject/iotex-core/blockchain/blockdao" 21 "github.com/iotexproject/iotex-core/db" 22 "github.com/iotexproject/iotex-core/db/batch" 23 "github.com/iotexproject/iotex-core/pkg/util/byteutil" 24 ) 25 26 const ( 27 // BlockBloomFilterNamespace indicated the kvstore namespace to store block BloomFilters 28 BlockBloomFilterNamespace = "BlockBloomFilters" 29 // RangeBloomFilterNamespace indicates the kvstore namespace to store range BloomFilters 30 RangeBloomFilterNamespace = "RangeBloomFilters" 31 // CurrentHeightKey indicates the key of current bf indexer height in underlying DB 32 CurrentHeightKey = "CurrentHeight" 33 ) 34 35 const ( 36 _maxBlockRange = 1e6 37 _workerNumbers = 5 38 ) 39 40 var ( 41 // TotalBloomFilterNamespace indicates the kvstore namespace to store total ranges 42 TotalBloomFilterNamespace = []byte("TotalBloomFilters") 43 44 errRangeTooLarge = errors.New("block range is too large") 45 46 _queryTimeout = 20 * time.Second 47 ) 48 49 type ( 50 // BloomFilterIndexer is the interface for bloomfilter indexer 51 BloomFilterIndexer interface { 52 blockdao.BlockIndexer 53 // RangeBloomFilterNumElements returns the number of elements that each rangeBloomfilter indexes 54 RangeBloomFilterNumElements() uint64 55 // BlockFilterByHeight returns the block-level bloomfilter which includes not only topic but also address of logs info by given block height 56 BlockFilterByHeight(uint64) (bloom.BloomFilter, error) 57 // FilterBlocksInRange returns the block numbers by given logFilter in range from start to end 58 FilterBlocksInRange(*filter.LogFilter, uint64, uint64, uint64) ([]uint64, error) 59 } 60 61 // bloomfilterIndexer is a struct for bloomfilter indexer 62 bloomfilterIndexer struct { 63 mutex sync.RWMutex // mutex for curRangeBloomfilter 64 kvStore db.KVStore 65 rangeSize uint64 66 bfSize uint64 67 bfNumHash uint64 68 currRangeBfKey []byte 69 curRangeBloomfilter *bloomRange 70 totalRange db.RangeIndex 71 } 72 73 jobDesc struct { 74 idx uint64 75 key []byte 76 } 77 ) 78 79 // NewBloomfilterIndexer creates a new bloomfilterindexer struct by given kvstore and rangebloomfilter size 80 func NewBloomfilterIndexer(kv db.KVStore, cfg Config) (BloomFilterIndexer, error) { 81 if kv == nil { 82 return nil, errors.New("empty kvStore") 83 } 84 85 return &bloomfilterIndexer{ 86 kvStore: kv, 87 rangeSize: cfg.RangeBloomFilterNumElements, 88 bfSize: cfg.RangeBloomFilterSize, 89 bfNumHash: cfg.RangeBloomFilterNumHash, 90 }, nil 91 } 92 93 // Start starts the bloomfilter indexer 94 func (bfx *bloomfilterIndexer) Start(ctx context.Context) error { 95 if err := bfx.kvStore.Start(ctx); err != nil { 96 return err 97 } 98 99 bfx.mutex.Lock() 100 defer bfx.mutex.Unlock() 101 tipHeightData, err := bfx.kvStore.Get(RangeBloomFilterNamespace, []byte(CurrentHeightKey)) 102 switch errors.Cause(err) { 103 case nil: 104 tipHeight := byteutil.BytesToUint64BigEndian(tipHeightData) 105 return bfx.initRangeBloomFilter(tipHeight) 106 case db.ErrNotExist: 107 if err = bfx.kvStore.Put(RangeBloomFilterNamespace, []byte(CurrentHeightKey), byteutil.Uint64ToBytes(0)); err != nil { 108 return err 109 } 110 return bfx.initRangeBloomFilter(0) 111 default: 112 return err 113 } 114 } 115 116 func (bfx *bloomfilterIndexer) initRangeBloomFilter(height uint64) error { 117 var ( 118 err error 119 zero8Bytes = make([]byte, 8) 120 ) 121 bfx.totalRange, err = db.NewRangeIndex(bfx.kvStore, TotalBloomFilterNamespace, zero8Bytes) 122 if err != nil { 123 return err 124 } 125 if bfx.curRangeBloomfilter, err = newBloomRange(bfx.bfSize, bfx.bfNumHash); err != nil { 126 return err 127 } 128 if height > 0 { 129 bfx.currRangeBfKey, err = bfx.totalRange.Get(height) 130 if err != nil { 131 return err 132 } 133 if err := bfx.loadBloomRangeFromDB(bfx.curRangeBloomfilter, bfx.currRangeBfKey); err != nil { 134 return err 135 } 136 } else { 137 bfx.curRangeBloomfilter.SetStart(1) 138 bfx.currRangeBfKey = zero8Bytes 139 } 140 return nil 141 } 142 143 // Stop stops the bloomfilter indexer 144 func (bfx *bloomfilterIndexer) Stop(ctx context.Context) error { 145 bfx.totalRange.Close() 146 return bfx.kvStore.Stop(ctx) 147 } 148 149 // Height returns the tipHeight from underlying DB 150 func (bfx *bloomfilterIndexer) Height() (uint64, error) { 151 h, err := bfx.kvStore.Get(RangeBloomFilterNamespace, []byte(CurrentHeightKey)) 152 if err != nil { 153 return 0, err 154 } 155 return byteutil.BytesToUint64BigEndian(h), nil 156 } 157 158 // PutBlock processes new block by adding logs into rangebloomfilter, and if necessary, updating underlying DB 159 func (bfx *bloomfilterIndexer) PutBlock(ctx context.Context, blk *block.Block) (err error) { 160 bfx.mutex.Lock() 161 defer bfx.mutex.Unlock() 162 bfx.addLogsToRangeBloomFilter(ctx, blk.Height(), blk.Receipts) 163 // commit into DB and update tipHeight 164 if err := bfx.commit(blk.Height(), bfx.calculateBlockBloomFilter(ctx, blk.Receipts)); err != nil { 165 return err 166 } 167 if bfx.curRangeBloomfilter.NumElements() >= bfx.rangeSize { 168 nextIndex := byteutil.BytesToUint64BigEndian(bfx.currRangeBfKey) + 1 169 bfx.currRangeBfKey = byteutil.Uint64ToBytesBigEndian(nextIndex) 170 if err := bfx.totalRange.Insert(blk.Height()+1, bfx.currRangeBfKey); err != nil { 171 return errors.Wrapf(err, "failed to write next bloomfilter index") 172 } 173 if bfx.curRangeBloomfilter, err = newBloomRange(bfx.bfSize, bfx.bfNumHash); err != nil { 174 return err 175 } 176 bfx.curRangeBloomfilter.SetStart(blk.Height() + 1) 177 } 178 return nil 179 } 180 181 // DeleteTipBlock deletes tip height from underlying DB if necessary 182 func (bfx *bloomfilterIndexer) DeleteTipBlock(_ context.Context, blk *block.Block) (err error) { 183 bfx.mutex.Lock() 184 defer bfx.mutex.Unlock() 185 height := blk.Height() 186 if err := bfx.kvStore.Delete(BlockBloomFilterNamespace, byteutil.Uint64ToBytesBigEndian(height)); err != nil { 187 return err 188 } 189 bfx.curRangeBloomfilter = nil 190 return nil 191 } 192 193 // RangeBloomFilterNumElements returns the number of elements that each rangeBloomfilter indexes 194 func (bfx *bloomfilterIndexer) RangeBloomFilterNumElements() uint64 { 195 bfx.mutex.RLock() 196 defer bfx.mutex.RUnlock() 197 return bfx.rangeSize 198 } 199 200 // BlockFilterByHeight returns the block-level bloomfilter which includes not only topic but also address of logs info by given block height 201 func (bfx *bloomfilterIndexer) BlockFilterByHeight(height uint64) (bloom.BloomFilter, error) { 202 bfBytes, err := bfx.kvStore.Get(BlockBloomFilterNamespace, byteutil.Uint64ToBytesBigEndian(height)) 203 if err != nil { 204 return nil, err 205 } 206 bf, err := bloom.NewBloomFilter(bfx.bfSize, bfx.bfNumHash) 207 if err != nil { 208 return nil, err 209 } 210 if err := bf.FromBytes(bfBytes); err != nil { 211 return nil, err 212 } 213 return bf, nil 214 } 215 216 // FilterBlocksInRange returns the block numbers by given logFilter in range [start, end]. 217 // Result blocks are limited when pagination is larger than 0 218 func (bfx *bloomfilterIndexer) FilterBlocksInRange(l *filter.LogFilter, start, end uint64, pagination uint64) ([]uint64, error) { 219 if start == 0 || end == 0 || end < start { 220 return nil, errors.New("start/end height should be bigger than zero") 221 } 222 if end-start > _maxBlockRange { 223 return nil, errRangeTooLarge 224 } 225 var ( 226 startIndex, endIndex uint64 227 err error 228 ) 229 if startIndex, err = bfx.getIndexByHeight(start); err != nil { 230 return nil, err 231 } 232 if endIndex, err = bfx.getIndexByHeight(end); err != nil { 233 return nil, err 234 } 235 236 var ( 237 ctx, cancel = context.WithTimeout(context.Background(), _queryTimeout) 238 blkNums = make([][]uint64, endIndex-startIndex+1) 239 jobs = make(chan jobDesc, endIndex-startIndex+1) 240 eg *errgroup.Group 241 bufPool sync.Pool 242 ) 243 defer cancel() 244 eg, ctx = errgroup.WithContext(ctx) 245 246 // create pool for BloomRange object reusing 247 if _, err := newBloomRange(bfx.bfSize, bfx.bfNumHash); err != nil { 248 return nil, err 249 } 250 bufPool = sync.Pool{ 251 New: func() interface{} { 252 br, _ := newBloomRange(bfx.bfSize, bfx.bfNumHash) 253 return br 254 }, 255 } 256 257 for w := 0; w < _workerNumbers; w++ { 258 eg.Go(func() error { 259 for { 260 select { 261 case <-ctx.Done(): 262 return ctx.Err() 263 case job, ok := <-jobs: 264 if !ok { 265 return nil 266 } 267 br := bufPool.Get().(*bloomRange) 268 if err := bfx.loadBloomRangeFromDB(br, job.key); err != nil { 269 bufPool.Put(br) 270 return err 271 } 272 if l.ExistInBloomFilterv2(br.BloomFilter) { 273 searchStart := br.Start() 274 if start > searchStart { 275 searchStart = start 276 } 277 searchEnd := br.End() 278 if end < searchEnd { 279 searchEnd = end 280 } 281 blkNums[job.idx] = l.SelectBlocksFromRangeBloomFilter(br.BloomFilter, searchStart, searchEnd) 282 } 283 bufPool.Put(br) 284 } 285 } 286 }) 287 } 288 289 // send job to job chan 290 for idx := startIndex; idx <= endIndex; idx++ { 291 jobs <- jobDesc{idx - startIndex, byteutil.Uint64ToBytesBigEndian(idx)} 292 } 293 close(jobs) 294 295 if err := eg.Wait(); err != nil { 296 return nil, err 297 } 298 299 // collect results from goroutines 300 ret := []uint64{} 301 for i := range blkNums { 302 if len(blkNums[i]) > 0 { 303 ret = append(ret, blkNums[i]...) 304 if pagination > 0 && uint64(len(ret)) > pagination { 305 return ret, nil 306 } 307 } 308 } 309 return ret, nil 310 } 311 312 func (bfx *bloomfilterIndexer) commit(blockNumber uint64, blkBloomfilter bloom.BloomFilter) error { 313 bfx.curRangeBloomfilter.SetEnd(blockNumber) 314 bfBytes, err := bfx.curRangeBloomfilter.Bytes() 315 if err != nil { 316 return err 317 } 318 b := batch.NewBatch() 319 b.Put(RangeBloomFilterNamespace, bfx.currRangeBfKey, bfBytes, "failed to put range bloom filter") 320 b.Put(BlockBloomFilterNamespace, byteutil.Uint64ToBytesBigEndian(blockNumber), blkBloomfilter.Bytes(), "failed to put block bloom filter") 321 b.Put(RangeBloomFilterNamespace, []byte(CurrentHeightKey), byteutil.Uint64ToBytesBigEndian(blockNumber), "failed to put current height") 322 b.AddFillPercent(RangeBloomFilterNamespace, 1.0) 323 b.AddFillPercent(BlockBloomFilterNamespace, 1.0) 324 return bfx.kvStore.WriteBatch(b) 325 } 326 327 // TODO: improve performance 328 func (bfx *bloomfilterIndexer) calculateBlockBloomFilter(ctx context.Context, receipts []*action.Receipt) bloom.BloomFilter { 329 bloom, _ := bloom.NewBloomFilter(2048, 3) 330 for _, receipt := range receipts { 331 for _, l := range receipt.Logs() { 332 bloom.Add([]byte(l.Address)) 333 for i, topic := range l.Topics { 334 bloom.Add(append(byteutil.Uint64ToBytes(uint64(i)), topic[:]...)) //position-sensitive 335 } 336 } 337 } 338 return bloom 339 } 340 341 // TODO: improve performance 342 func (bfx *bloomfilterIndexer) addLogsToRangeBloomFilter(ctx context.Context, blockNumber uint64, receipts []*action.Receipt) { 343 Heightkey := append([]byte(filter.BlockHeightPrefix), byteutil.Uint64ToBytes(blockNumber)...) 344 345 for _, receipt := range receipts { 346 for _, l := range receipt.Logs() { 347 bfx.curRangeBloomfilter.Add([]byte(l.Address)) 348 bfx.curRangeBloomfilter.Add(append(Heightkey, []byte(l.Address)...)) // concatenate with block number 349 for i, topic := range l.Topics { 350 bfx.curRangeBloomfilter.Add(append(byteutil.Uint64ToBytes(uint64(i)), topic[:]...)) //position-sensitive 351 bfx.curRangeBloomfilter.Add(append(Heightkey, topic[:]...)) // concatenate with block number 352 } 353 } 354 } 355 } 356 357 func (bfx *bloomfilterIndexer) loadBloomRangeFromDB(br *bloomRange, bfKey []byte) error { 358 if br == nil { 359 return errors.New("bloomRange is empty") 360 } 361 bfBytes, err := bfx.kvStore.Get(RangeBloomFilterNamespace, bfKey) 362 if err != nil { 363 return err 364 } 365 return br.FromBytes(bfBytes) 366 } 367 368 func (bfx *bloomfilterIndexer) getIndexByHeight(height uint64) (uint64, error) { 369 val, err := bfx.totalRange.Get(height) 370 if err != nil { 371 return 0, err 372 } 373 return byteutil.BytesToUint64BigEndian(val), nil 374 }