github.com/iotexproject/iotex-core@v1.14.1-rc1/blockindex/indexer.go (about) 1 // Copyright (c) 2019 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 "bytes" 10 "context" 11 "fmt" 12 "math/big" 13 "sync" 14 15 "github.com/iotexproject/go-pkgs/hash" 16 "github.com/pkg/errors" 17 18 "github.com/iotexproject/iotex-address/address" 19 "github.com/iotexproject/iotex-core/action" 20 "github.com/iotexproject/iotex-core/action/protocol" 21 "github.com/iotexproject/iotex-core/blockchain/block" 22 "github.com/iotexproject/iotex-core/db" 23 "github.com/iotexproject/iotex-core/db/batch" 24 "github.com/iotexproject/iotex-core/pkg/util/byteutil" 25 ) 26 27 // the NS/bucket name here are used in index.db, which is separate from chain.db 28 // still we use 2-byte NS/bucket name here, to clearly differentiate from those (3-byte) in BlockDAO 29 const ( 30 // first 12-byte of hash is cut off, only last 20-byte is written to DB to reduce storage 31 _hashOffset = 12 32 _blockHashToHeightNS = "hh" 33 _actionToBlockHashNS = "ab" 34 ) 35 36 var ( 37 _totalBlocksBucket = []byte("bk") 38 _totalActionsBucket = []byte("ac") 39 // ErrActionIndexNA indicates action index is not supported 40 ErrActionIndexNA = errors.New("action index not supported") 41 ) 42 43 type ( 44 addrIndex map[hash.Hash160]db.CountingIndex 45 46 // Indexer is the interface for block indexer 47 Indexer interface { 48 Start(context.Context) error 49 Stop(context.Context) error 50 PutBlock(context.Context, *block.Block) error 51 PutBlocks(context.Context, []*block.Block) error 52 DeleteTipBlock(context.Context, *block.Block) error 53 Height() (uint64, error) 54 GetBlockHash(height uint64) (hash.Hash256, error) 55 GetBlockHeight(hash hash.Hash256) (uint64, error) 56 GetBlockIndex(uint64) (*blockIndex, error) 57 GetActionIndex([]byte) (*actionIndex, error) 58 GetTotalActions() (uint64, error) 59 GetActionHashFromIndex(uint64, uint64) ([][]byte, error) 60 GetActionCountByAddress(hash.Hash160) (uint64, error) 61 GetActionsByAddress(hash.Hash160, uint64, uint64) ([][]byte, error) 62 } 63 64 // blockIndexer implements the Indexer interface 65 blockIndexer struct { 66 mutex sync.RWMutex 67 genesisHash hash.Hash256 68 kvStore db.KVStoreWithRange 69 batch batch.KVStoreBatch 70 dirtyAddr addrIndex 71 tbk db.CountingIndex 72 tac db.CountingIndex 73 } 74 ) 75 76 // NewIndexer creates a new indexer 77 func NewIndexer(kv db.KVStore, genesisHash hash.Hash256) (Indexer, error) { 78 if kv == nil { 79 return nil, errors.New("empty kvStore") 80 } 81 kvRange, ok := kv.(db.KVStoreWithRange) 82 if !ok { 83 return nil, errors.New("indexer can only be created from KVStoreWithRange") 84 } 85 x := blockIndexer{ 86 kvStore: kvRange, 87 batch: batch.NewBatch(), 88 dirtyAddr: make(addrIndex), 89 genesisHash: genesisHash, 90 } 91 return &x, nil 92 } 93 94 // Start starts the indexer 95 func (x *blockIndexer) Start(ctx context.Context) error { 96 if err := x.kvStore.Start(ctx); err != nil { 97 return err 98 } 99 // create the total block and action index 100 var err error 101 if x.tbk, err = db.NewCountingIndexNX(x.kvStore, _totalBlocksBucket); err != nil { 102 return err 103 } 104 if x.tbk.Size() == 0 { 105 // insert genesis block 106 if err = x.tbk.Add((&blockIndex{ 107 x.genesisHash[:], 108 0, 109 big.NewInt(0)}).Serialize(), false); err != nil { 110 return err 111 } 112 } 113 x.tac, err = db.NewCountingIndexNX(x.kvStore, _totalActionsBucket) 114 return err 115 } 116 117 // Stop stops the indexer 118 func (x *blockIndexer) Stop(ctx context.Context) error { 119 return x.kvStore.Stop(ctx) 120 } 121 122 // PutBlocks writes the batch to DB 123 func (x *blockIndexer) PutBlocks(ctx context.Context, blks []*block.Block) error { 124 x.mutex.Lock() 125 defer x.mutex.Unlock() 126 for _, blk := range blks { 127 if err := x.putBlock(ctx, blk); err != nil { 128 // TODO: Revert changes 129 return err 130 } 131 } 132 return x.commit() 133 } 134 135 // PutBlock index the block 136 func (x *blockIndexer) PutBlock(ctx context.Context, blk *block.Block) error { 137 x.mutex.Lock() 138 defer x.mutex.Unlock() 139 140 if err := x.putBlock(ctx, blk); err != nil { 141 return err 142 } 143 return x.commit() 144 } 145 146 // DeleteBlock deletes a block's index 147 func (x *blockIndexer) DeleteTipBlock(ctx context.Context, blk *block.Block) error { 148 x.mutex.Lock() 149 defer x.mutex.Unlock() 150 151 // the block to be deleted must be exactly current top, otherwise counting index would not work correctly 152 height := blk.Height() 153 if height != x.tbk.Size()-1 { 154 return errors.Wrapf(db.ErrInvalid, "wrong block height %d, expecting %d", height, x.tbk.Size()-1) 155 } 156 // delete hash --> height 157 hash := blk.HashBlock() 158 x.batch.Delete(_blockHashToHeightNS, hash[_hashOffset:], fmt.Sprintf("failed to delete block at height %d", height)) 159 // delete from total block index 160 if err := x.tbk.Revert(1); err != nil { 161 return err 162 } 163 164 // delete action index 165 fCtx := protocol.MustGetFeatureCtx(protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 166 BlockHeight: blk.Height(), 167 }))) 168 for _, selp := range blk.Actions { 169 actHash, err := selp.Hash() 170 if err != nil { 171 return err 172 } 173 x.batch.Delete(_actionToBlockHashNS, actHash[_hashOffset:], fmt.Sprintf("failed to delete action hash %x", actHash)) 174 if err := x.indexAction(actHash, selp, false, fCtx.TolerateLegacyAddress); err != nil { 175 return err 176 } 177 } 178 // delete from total action index 179 if err := x.tac.Revert(uint64(len(blk.Actions))); err != nil { 180 return err 181 } 182 if err := x.kvStore.WriteBatch(x.batch); err != nil { 183 return err 184 } 185 x.batch.Clear() 186 return nil 187 } 188 189 // Height return the blockchain height 190 func (x *blockIndexer) Height() (uint64, error) { 191 x.mutex.RLock() 192 defer x.mutex.RUnlock() 193 return x.tbk.Size() - 1, nil 194 } 195 196 // GetBlockHash returns the block hash by height 197 func (x *blockIndexer) GetBlockHash(height uint64) (hash.Hash256, error) { 198 index, err := x.GetBlockIndex(height) 199 if err != nil { 200 return hash.ZeroHash256, errors.Wrap(err, "failed to get block hash") 201 } 202 return hash.BytesToHash256(index.Hash()), nil 203 } 204 205 // GetBlockHeight returns the block height by hash 206 func (x *blockIndexer) GetBlockHeight(hash hash.Hash256) (uint64, error) { 207 x.mutex.RLock() 208 defer x.mutex.RUnlock() 209 210 value, err := x.kvStore.Get(_blockHashToHeightNS, hash[_hashOffset:]) 211 if err != nil { 212 return 0, errors.Wrap(err, "failed to get block height") 213 } 214 if len(value) == 0 { 215 return 0, errors.Wrapf(db.ErrNotExist, "height missing for block with hash = %x", hash) 216 } 217 return byteutil.BytesToUint64BigEndian(value), nil 218 } 219 220 // GetBlockIndex return the index of block 221 func (x *blockIndexer) GetBlockIndex(height uint64) (*blockIndex, error) { 222 x.mutex.RLock() 223 defer x.mutex.RUnlock() 224 225 v, err := x.tbk.Get(height) 226 if err != nil { 227 return nil, err 228 } 229 b := &blockIndex{} 230 if err := b.Deserialize(v); err != nil { 231 return nil, err 232 } 233 return b, nil 234 } 235 236 // GetActionIndex return the index of action 237 func (x *blockIndexer) GetActionIndex(h []byte) (*actionIndex, error) { 238 x.mutex.RLock() 239 defer x.mutex.RUnlock() 240 241 v, err := x.kvStore.Get(_actionToBlockHashNS, h[_hashOffset:]) 242 if err != nil { 243 return nil, err 244 } 245 a := &actionIndex{} 246 if err := a.Deserialize(v); err != nil { 247 return nil, err 248 } 249 return a, nil 250 } 251 252 // GetTotalActions return total number of all actions 253 func (x *blockIndexer) GetTotalActions() (uint64, error) { 254 x.mutex.RLock() 255 defer x.mutex.RUnlock() 256 return x.tac.Size(), nil 257 } 258 259 // GetActionHashFromIndex return hash of actions[start, start+count) 260 func (x *blockIndexer) GetActionHashFromIndex(start, count uint64) ([][]byte, error) { 261 x.mutex.RLock() 262 defer x.mutex.RUnlock() 263 264 return x.tac.Range(start, count) 265 } 266 267 // GetActionCountByAddress return total number of actions of an address 268 func (x *blockIndexer) GetActionCountByAddress(addrBytes hash.Hash160) (uint64, error) { 269 x.mutex.RLock() 270 defer x.mutex.RUnlock() 271 272 addr, err := db.GetCountingIndex(x.kvStore, addrBytes[:]) 273 if err != nil { 274 if errors.Cause(err) == db.ErrBucketNotExist || errors.Cause(err) == db.ErrNotExist { 275 return 0, nil 276 } 277 return 0, err 278 } 279 return addr.Size(), nil 280 } 281 282 // GetActionsByAddress return hash of an address's actions[start, start+count) 283 func (x *blockIndexer) GetActionsByAddress(addrBytes hash.Hash160, start, count uint64) ([][]byte, error) { 284 x.mutex.RLock() 285 defer x.mutex.RUnlock() 286 287 addr, err := db.GetCountingIndex(x.kvStore, addrBytes[:]) 288 if err != nil { 289 return nil, err 290 } 291 total := addr.Size() 292 if start >= total { 293 return nil, errors.Wrapf(db.ErrInvalid, "start = %d >= total = %d", start, total) 294 } 295 if start+count > total { 296 count = total - start 297 } 298 return addr.Range(start, count) 299 } 300 301 func (x *blockIndexer) putBlock(ctx context.Context, blk *block.Block) error { 302 // the block to be indexed must be exactly current top + 1, otherwise counting index would not work correctly 303 height := blk.Height() 304 if height != x.tbk.Size() { 305 return errors.Wrapf(db.ErrInvalid, "wrong block height %d, expecting %d", height, x.tbk.Size()) 306 } 307 308 // index hash --> height 309 hash := blk.HashBlock() 310 x.batch.Put(_blockHashToHeightNS, hash[_hashOffset:], byteutil.Uint64ToBytesBigEndian(height), "failed to put hash -> height mapping") 311 312 // index height --> block hash, number of actions, and total transfer amount 313 bd := &blockIndex{ 314 hash: hash[:], 315 numAction: uint32(len(blk.Actions)), 316 tsfAmount: blk.CalculateTransferAmount()} 317 if err := x.tbk.UseBatch(x.batch); err != nil { 318 return err 319 } 320 if err := x.tbk.Add(bd.Serialize(), true); err != nil { 321 return errors.Wrapf(err, "failed to put block %d index", height) 322 } 323 324 // store height of the block, so getReceiptByActionHash() can use height to directly pull receipts 325 ad := (&actionIndex{ 326 blkHeight: blk.Height()}).Serialize() 327 if err := x.tac.UseBatch(x.batch); err != nil { 328 return err 329 } 330 // index actions in the block 331 fCtx := protocol.MustGetFeatureCtx(protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 332 BlockHeight: blk.Height(), 333 }))) 334 for _, selp := range blk.Actions { 335 actHash, err := selp.Hash() 336 if err != nil { 337 return err 338 } 339 x.batch.Put(_actionToBlockHashNS, actHash[_hashOffset:], ad, fmt.Sprintf("failed to put action hash %x", actHash)) 340 // add to total account index 341 if err := x.tac.Add(actHash[:], true); err != nil { 342 return err 343 } 344 if err := x.indexAction(actHash, selp, true, fCtx.TolerateLegacyAddress); err != nil { 345 return err 346 } 347 } 348 return nil 349 } 350 351 // commit writes the changes 352 func (x *blockIndexer) commit() error { 353 var commitErr error 354 for k, v := range x.dirtyAddr { 355 if commitErr == nil { 356 if err := v.Finalize(); err != nil { 357 commitErr = err 358 } 359 } 360 delete(x.dirtyAddr, k) 361 } 362 if commitErr != nil { 363 return commitErr 364 } 365 // total block and total action index 366 if err := x.tbk.Finalize(); err != nil { 367 return err 368 } 369 if err := x.tac.Finalize(); err != nil { 370 return err 371 } 372 if err := x.kvStore.WriteBatch(x.batch); err != nil { 373 return err 374 } 375 x.batch.Clear() 376 return nil 377 } 378 379 // getIndexerForAddr returns the counting indexer for an address 380 // if batch is true, the indexer will be placed into a dirty map, to be committed later 381 func (x *blockIndexer) getIndexerForAddr(addr []byte, batch bool) (db.CountingIndex, error) { 382 if !batch { 383 return db.NewCountingIndexNX(x.kvStore, addr) 384 } 385 address := hash.BytesToHash160(addr) 386 indexer, ok := x.dirtyAddr[address] 387 if !ok { 388 // create indexer for addr if not exist 389 var err error 390 indexer, err = db.NewCountingIndexNX(x.kvStore, addr) 391 if err != nil { 392 return nil, err 393 } 394 if err := indexer.UseBatch(x.batch); err != nil { 395 return nil, err 396 } 397 x.dirtyAddr[address] = indexer 398 } 399 return indexer, nil 400 } 401 402 // indexAction builds index for an action 403 func (x *blockIndexer) indexAction(actHash hash.Hash256, elp *action.SealedEnvelope, insert, tolerateLegacyAddress bool) error { 404 // add to sender's index 405 callerAddrBytes := elp.SrcPubkey().Hash() 406 sender, err := x.getIndexerForAddr(callerAddrBytes, insert) 407 if err != nil { 408 return err 409 } 410 if insert { 411 err = sender.Add(actHash[:], insert) 412 } else { 413 err = sender.Revert(1) 414 } 415 if err != nil { 416 return err 417 } 418 419 dst, ok := elp.Destination() 420 if !ok || dst == "" { 421 return nil 422 } 423 424 var dstAddr address.Address 425 if tolerateLegacyAddress { 426 dstAddr, err = address.FromStringLegacy(dst) 427 } else { 428 dstAddr, err = address.FromString(dst) 429 } 430 if err != nil { 431 return err 432 } 433 dstAddrBytes := dstAddr.Bytes() 434 435 if bytes.Equal(dstAddrBytes, callerAddrBytes) { 436 // recipient is same as sender 437 return nil 438 } 439 440 // add to recipient's index 441 recipient, err := x.getIndexerForAddr(dstAddrBytes, insert) 442 if err != nil { 443 return err 444 } 445 if insert { 446 err = recipient.Add(actHash[:], insert) 447 } else { 448 err = recipient.Revert(1) 449 } 450 return err 451 }