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  }