github.com/iotexproject/iotex-core@v1.14.1-rc1/blockchain/blockchain.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 blockchain
     7  
     8  import (
     9  	"context"
    10  	"strconv"
    11  	"sync"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	"github.com/facebookgo/clock"
    16  	"github.com/iotexproject/go-pkgs/hash"
    17  	"github.com/iotexproject/iotex-address/address"
    18  	"github.com/pkg/errors"
    19  	"github.com/prometheus/client_golang/prometheus"
    20  	"go.uber.org/zap"
    21  
    22  	"github.com/iotexproject/iotex-core/action"
    23  	"github.com/iotexproject/iotex-core/action/protocol"
    24  	"github.com/iotexproject/iotex-core/blockchain/block"
    25  	"github.com/iotexproject/iotex-core/blockchain/blockdao"
    26  	"github.com/iotexproject/iotex-core/blockchain/filedao"
    27  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    28  	"github.com/iotexproject/iotex-core/pkg/lifecycle"
    29  	"github.com/iotexproject/iotex-core/pkg/log"
    30  	"github.com/iotexproject/iotex-core/pkg/prometheustimer"
    31  )
    32  
    33  // const
    34  const (
    35  	SigP256k1  = "secp256k1"
    36  	SigP256sm2 = "p256sm2"
    37  )
    38  
    39  var (
    40  	_blockMtc = prometheus.NewGaugeVec(
    41  		prometheus.GaugeOpts{
    42  			Name: "iotex_block_metrics",
    43  
    44  			Help: "Block metrics.",
    45  		},
    46  		[]string{"type"},
    47  	)
    48  	// ErrInvalidTipHeight is the error returned when the block height is not valid
    49  	ErrInvalidTipHeight = errors.New("invalid tip height")
    50  	// ErrInvalidBlock is the error returned when the block is not valid
    51  	ErrInvalidBlock = errors.New("failed to validate the block")
    52  	// ErrActionNonce is the error when the nonce of the action is wrong
    53  	ErrActionNonce = errors.New("invalid action nonce")
    54  	// ErrInsufficientGas indicates the error of insufficient gas value for data storage
    55  	ErrInsufficientGas = errors.New("insufficient intrinsic gas value")
    56  	// ErrBalance indicates the error of balance
    57  	ErrBalance = errors.New("invalid balance")
    58  )
    59  
    60  func init() {
    61  	prometheus.MustRegister(_blockMtc)
    62  }
    63  
    64  type (
    65  	// Blockchain represents the blockchain data structure and hosts the APIs to access it
    66  	Blockchain interface {
    67  		lifecycle.StartStopper
    68  
    69  		// For exposing blockchain states
    70  		// BlockHeaderByHeight return block header by height
    71  		BlockHeaderByHeight(height uint64) (*block.Header, error)
    72  		// BlockFooterByHeight return block footer by height
    73  		BlockFooterByHeight(height uint64) (*block.Footer, error)
    74  		// ChainID returns the chain ID
    75  		ChainID() uint32
    76  		// EvmNetworkID returns the evm network ID
    77  		EvmNetworkID() uint32
    78  		// ChainAddress returns chain address on parent chain, the root chain return empty.
    79  		ChainAddress() string
    80  		// TipHash returns tip block's hash
    81  		TipHash() hash.Hash256
    82  		// TipHeight returns tip block's height
    83  		TipHeight() uint64
    84  		// Genesis returns the genesis
    85  		Genesis() genesis.Genesis
    86  		// Context returns current context
    87  		Context(context.Context) (context.Context, error)
    88  
    89  		// For block operations
    90  		// MintNewBlock creates a new block with given actions
    91  		// Note: the coinbase transfer will be added to the given transfers when minting a new block
    92  		MintNewBlock(timestamp time.Time) (*block.Block, error)
    93  		// CommitBlock validates and appends a block to the chain
    94  		CommitBlock(blk *block.Block) error
    95  		// ValidateBlock validates a new block before adding it to the blockchain
    96  		ValidateBlock(blk *block.Block) error
    97  
    98  		// AddSubscriber make you listen to every single produced block
    99  		AddSubscriber(BlockCreationSubscriber) error
   100  
   101  		// RemoveSubscriber make you listen to every single produced block
   102  		RemoveSubscriber(BlockCreationSubscriber) error
   103  	}
   104  
   105  	// BlockBuilderFactory is the factory interface of block builder
   106  	BlockBuilderFactory interface {
   107  		// NewBlockBuilder creates block builder
   108  		NewBlockBuilder(context.Context, func(action.Envelope) (*action.SealedEnvelope, error)) (*block.Builder, error)
   109  	}
   110  
   111  	// blockchain implements the Blockchain interface
   112  	blockchain struct {
   113  		mu             sync.RWMutex // mutex to protect utk, tipHeight and tipHash
   114  		dao            blockdao.BlockDAO
   115  		config         Config
   116  		genesis        genesis.Genesis
   117  		blockValidator block.Validator
   118  		lifecycle      lifecycle.Lifecycle
   119  		clk            clock.Clock
   120  		pubSubManager  PubSubManager
   121  		timerFactory   *prometheustimer.TimerFactory
   122  
   123  		// used by account-based model
   124  		bbf BlockBuilderFactory
   125  	}
   126  )
   127  
   128  // Productivity returns the map of the number of blocks produced per delegate in given epoch
   129  func Productivity(bc Blockchain, startHeight uint64, endHeight uint64) (map[string]uint64, error) {
   130  	stats := make(map[string]uint64)
   131  	for i := startHeight; i <= endHeight; i++ {
   132  		header, err := bc.BlockHeaderByHeight(i)
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  		producer := header.ProducerAddress()
   137  		stats[producer]++
   138  	}
   139  
   140  	return stats, nil
   141  }
   142  
   143  // Option sets blockchain construction parameter
   144  type Option func(*blockchain) error
   145  
   146  // BlockValidatorOption sets block validator
   147  func BlockValidatorOption(blockValidator block.Validator) Option {
   148  	return func(bc *blockchain) error {
   149  		bc.blockValidator = blockValidator
   150  		return nil
   151  	}
   152  }
   153  
   154  // ClockOption overrides the default clock
   155  func ClockOption(clk clock.Clock) Option {
   156  	return func(bc *blockchain) error {
   157  		bc.clk = clk
   158  		return nil
   159  	}
   160  }
   161  
   162  // NewBlockchain creates a new blockchain and DB instance
   163  func NewBlockchain(cfg Config, g genesis.Genesis, dao blockdao.BlockDAO, bbf BlockBuilderFactory, opts ...Option) Blockchain {
   164  	// create the Blockchain
   165  	chain := &blockchain{
   166  		config:        cfg,
   167  		genesis:       g,
   168  		dao:           dao,
   169  		bbf:           bbf,
   170  		clk:           clock.New(),
   171  		pubSubManager: NewPubSub(cfg.StreamingBlockBufferSize),
   172  	}
   173  	for _, opt := range opts {
   174  		if err := opt(chain); err != nil {
   175  			log.S().Panicf("Failed to execute blockchain creation option %p: %v", opt, err)
   176  		}
   177  	}
   178  	timerFactory, err := prometheustimer.New(
   179  		"iotex_blockchain_perf",
   180  		"Performance of blockchain module",
   181  		[]string{"topic", "chainID"},
   182  		[]string{"default", strconv.FormatUint(uint64(cfg.ID), 10)},
   183  	)
   184  	if err != nil {
   185  		log.L().Panic("Failed to generate prometheus timer factory.", zap.Error(err))
   186  	}
   187  	chain.timerFactory = timerFactory
   188  	if chain.dao == nil {
   189  		log.L().Panic("blockdao is nil")
   190  	}
   191  	chain.lifecycle.Add(chain.dao)
   192  	chain.lifecycle.Add(chain.pubSubManager)
   193  
   194  	return chain
   195  }
   196  
   197  func (bc *blockchain) ChainID() uint32 {
   198  	return atomic.LoadUint32(&bc.config.ID)
   199  }
   200  
   201  func (bc *blockchain) EvmNetworkID() uint32 {
   202  	return atomic.LoadUint32(&bc.config.EVMNetworkID)
   203  }
   204  
   205  func (bc *blockchain) ChainAddress() string {
   206  	return bc.config.Address
   207  }
   208  
   209  // Start starts the blockchain
   210  func (bc *blockchain) Start(ctx context.Context) error {
   211  	bc.mu.Lock()
   212  	defer bc.mu.Unlock()
   213  
   214  	// pass registry to be used by state factory's initialization
   215  	ctx, err := bc.context(ctx, false)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	return bc.lifecycle.OnStart(ctx)
   220  }
   221  
   222  // Stop stops the blockchain.
   223  func (bc *blockchain) Stop(ctx context.Context) error {
   224  	bc.mu.Lock()
   225  	defer bc.mu.Unlock()
   226  	return bc.lifecycle.OnStop(ctx)
   227  }
   228  
   229  func (bc *blockchain) BlockHeaderByHeight(height uint64) (*block.Header, error) {
   230  	return bc.dao.HeaderByHeight(height)
   231  }
   232  
   233  func (bc *blockchain) BlockFooterByHeight(height uint64) (*block.Footer, error) {
   234  	return bc.dao.FooterByHeight(height)
   235  }
   236  
   237  // TipHash returns tip block's hash
   238  func (bc *blockchain) TipHash() hash.Hash256 {
   239  	tipHeight, err := bc.dao.Height()
   240  	if err != nil {
   241  		return hash.ZeroHash256
   242  	}
   243  	tipHash, err := bc.dao.GetBlockHash(tipHeight)
   244  	if err != nil {
   245  		return hash.ZeroHash256
   246  	}
   247  	return tipHash
   248  }
   249  
   250  // TipHeight returns tip block's height
   251  func (bc *blockchain) TipHeight() uint64 {
   252  	tipHeight, err := bc.dao.Height()
   253  	if err != nil {
   254  		log.L().Panic("failed to get tip height", zap.Error(err))
   255  	}
   256  	return tipHeight
   257  }
   258  
   259  // ValidateBlock validates a new block before adding it to the blockchain
   260  func (bc *blockchain) ValidateBlock(blk *block.Block) error {
   261  	bc.mu.RLock()
   262  	defer bc.mu.RUnlock()
   263  	timer := bc.timerFactory.NewTimer("ValidateBlock")
   264  	defer timer.End()
   265  	if blk == nil {
   266  		return ErrInvalidBlock
   267  	}
   268  	tip, err := bc.tipInfo()
   269  	if err != nil {
   270  		return err
   271  	}
   272  	// verify new block has height incremented by 1
   273  	if blk.Height() != 0 && blk.Height() != tip.Height+1 {
   274  		return errors.Wrapf(
   275  			ErrInvalidTipHeight,
   276  			"wrong block height %d, expecting %d",
   277  			blk.Height(),
   278  			tip.Height+1,
   279  		)
   280  	}
   281  	// verify new block has correctly linked to current tip
   282  	if blk.PrevHash() != tip.Hash {
   283  		blk.HeaderLogger(log.L()).Error("Previous block hash doesn't match.",
   284  			log.Hex("expectedBlockHash", tip.Hash[:]))
   285  		return errors.Wrapf(
   286  			ErrInvalidBlock,
   287  			"wrong prev hash %x, expecting %x",
   288  			blk.PrevHash(),
   289  			tip.Hash,
   290  		)
   291  	}
   292  
   293  	if !blk.Header.VerifySignature() {
   294  		return errors.Errorf("failed to verify block's signature with public key: %x", blk.PublicKey())
   295  	}
   296  	if err := blk.VerifyTxRoot(); err != nil {
   297  		return err
   298  	}
   299  
   300  	producerAddr := blk.PublicKey().Address()
   301  	if producerAddr == nil {
   302  		return errors.New("failed to get address")
   303  	}
   304  	ctx, err := bc.context(context.Background(), true)
   305  	if err != nil {
   306  		return err
   307  	}
   308  	ctx = protocol.WithBlockCtx(ctx,
   309  		protocol.BlockCtx{
   310  			BlockHeight:    blk.Height(),
   311  			BlockTimeStamp: blk.Timestamp(),
   312  			GasLimit:       bc.genesis.BlockGasLimitByHeight(blk.Height()),
   313  			Producer:       producerAddr,
   314  		},
   315  	)
   316  	ctx = protocol.WithFeatureCtx(ctx)
   317  	if bc.blockValidator == nil {
   318  		return nil
   319  	}
   320  
   321  	return bc.blockValidator.Validate(ctx, blk)
   322  }
   323  
   324  func (bc *blockchain) Context(ctx context.Context) (context.Context, error) {
   325  	bc.mu.RLock()
   326  	defer bc.mu.RUnlock()
   327  
   328  	return bc.context(ctx, true)
   329  }
   330  
   331  func (bc *blockchain) contextWithBlock(ctx context.Context, producer address.Address, height uint64, timestamp time.Time) context.Context {
   332  	return protocol.WithBlockCtx(
   333  		ctx,
   334  		protocol.BlockCtx{
   335  			BlockHeight:    height,
   336  			BlockTimeStamp: timestamp,
   337  			Producer:       producer,
   338  			GasLimit:       bc.genesis.BlockGasLimitByHeight(height),
   339  		})
   340  }
   341  
   342  func (bc *blockchain) context(ctx context.Context, tipInfoFlag bool) (context.Context, error) {
   343  	var tip protocol.TipInfo
   344  	if tipInfoFlag {
   345  		if tipInfoValue, err := bc.tipInfo(); err == nil {
   346  			tip = *tipInfoValue
   347  		} else {
   348  			return nil, err
   349  		}
   350  	}
   351  
   352  	ctx = genesis.WithGenesisContext(
   353  		protocol.WithBlockchainCtx(
   354  			ctx,
   355  			protocol.BlockchainCtx{
   356  				Tip:          tip,
   357  				ChainID:      bc.ChainID(),
   358  				EvmNetworkID: bc.EvmNetworkID(),
   359  			},
   360  		),
   361  		bc.genesis,
   362  	)
   363  	return protocol.WithFeatureWithHeightCtx(ctx), nil
   364  }
   365  
   366  func (bc *blockchain) MintNewBlock(timestamp time.Time) (*block.Block, error) {
   367  	bc.mu.RLock()
   368  	defer bc.mu.RUnlock()
   369  	mintNewBlockTimer := bc.timerFactory.NewTimer("MintNewBlock")
   370  	defer mintNewBlockTimer.End()
   371  	tipHeight, err := bc.dao.Height()
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  	newblockHeight := tipHeight + 1
   376  	ctx, err := bc.context(context.Background(), true)
   377  	if err != nil {
   378  		return nil, err
   379  	}
   380  	ctx = bc.contextWithBlock(ctx, bc.config.ProducerAddress(), newblockHeight, timestamp)
   381  	ctx = protocol.WithFeatureCtx(ctx)
   382  	// run execution and update state trie root hash
   383  	minterPrivateKey := bc.config.ProducerPrivateKey()
   384  	blockBuilder, err := bc.bbf.NewBlockBuilder(
   385  		ctx,
   386  		func(elp action.Envelope) (*action.SealedEnvelope, error) {
   387  			return action.Sign(elp, minterPrivateKey)
   388  		},
   389  	)
   390  	if err != nil {
   391  		return nil, errors.Wrapf(err, "failed to create block builder at new block height %d", newblockHeight)
   392  	}
   393  	blk, err := blockBuilder.SignAndBuild(minterPrivateKey)
   394  	if err != nil {
   395  		return nil, errors.Wrapf(err, "failed to create block")
   396  	}
   397  
   398  	return &blk, nil
   399  }
   400  
   401  // CommitBlock validates and appends a block to the chain
   402  func (bc *blockchain) CommitBlock(blk *block.Block) error {
   403  	bc.mu.Lock()
   404  	defer bc.mu.Unlock()
   405  	timer := bc.timerFactory.NewTimer("CommitBlock")
   406  	defer timer.End()
   407  	return bc.commitBlock(blk)
   408  }
   409  
   410  func (bc *blockchain) AddSubscriber(s BlockCreationSubscriber) error {
   411  	log.L().Info("Add a subscriber.")
   412  	if s == nil {
   413  		return errors.New("subscriber could not be nil")
   414  	}
   415  
   416  	return bc.pubSubManager.AddBlockListener(s)
   417  }
   418  
   419  func (bc *blockchain) RemoveSubscriber(s BlockCreationSubscriber) error {
   420  	return bc.pubSubManager.RemoveBlockListener(s)
   421  }
   422  
   423  //======================================
   424  // internal functions
   425  //=====================================
   426  
   427  func (bc *blockchain) Genesis() genesis.Genesis {
   428  	return bc.genesis
   429  }
   430  
   431  //======================================
   432  // private functions
   433  //=====================================
   434  
   435  func (bc *blockchain) tipInfo() (*protocol.TipInfo, error) {
   436  	tipHeight, err := bc.dao.Height()
   437  	if err != nil {
   438  		return nil, err
   439  	}
   440  	if tipHeight == 0 {
   441  		return &protocol.TipInfo{
   442  			Height:    0,
   443  			Hash:      bc.genesis.Hash(),
   444  			Timestamp: time.Unix(bc.genesis.Timestamp, 0),
   445  		}, nil
   446  	}
   447  	header, err := bc.dao.HeaderByHeight(tipHeight)
   448  	if err != nil {
   449  		return nil, err
   450  	}
   451  
   452  	return &protocol.TipInfo{
   453  		Height:    tipHeight,
   454  		Hash:      header.HashBlock(),
   455  		Timestamp: header.Timestamp(),
   456  	}, nil
   457  }
   458  
   459  // commitBlock commits a block to the chain
   460  func (bc *blockchain) commitBlock(blk *block.Block) error {
   461  	ctx, err := bc.context(context.Background(), true)
   462  	if err != nil {
   463  		return err
   464  	}
   465  
   466  	// write block into DB
   467  	putTimer := bc.timerFactory.NewTimer("putBlock")
   468  	err = bc.dao.PutBlock(ctx, blk)
   469  	putTimer.End()
   470  	switch {
   471  	case errors.Cause(err) == filedao.ErrAlreadyExist:
   472  		return nil
   473  	case err != nil:
   474  		return err
   475  	}
   476  	blkHash := blk.HashBlock()
   477  	if blk.Height()%100 == 0 {
   478  		blk.HeaderLogger(log.L()).Info("Committed a block.", log.Hex("tipHash", blkHash[:]))
   479  	}
   480  	_blockMtc.WithLabelValues("numActions").Set(float64(len(blk.Actions)))
   481  	// emit block to all block subscribers
   482  	bc.emitToSubscribers(blk)
   483  	return nil
   484  }
   485  
   486  func (bc *blockchain) emitToSubscribers(blk *block.Block) {
   487  	if bc.pubSubManager == nil {
   488  		return
   489  	}
   490  	bc.pubSubManager.SendBlockToSubscribers(blk)
   491  }