github.com/aergoio/aergo@v1.3.1/consensus/impl/dpos/blockfactory.go (about)

     1  /**
     2   *  @file
     3   *  @copyright defined in aergo/LICENSE.txt
     4   */
     5  
     6  package dpos
     7  
     8  import (
     9  	"fmt"
    10  	"github.com/aergoio/aergo/p2p/p2pkey"
    11  	"runtime"
    12  	"time"
    13  
    14  	"github.com/aergoio/aergo-lib/log"
    15  	bc "github.com/aergoio/aergo/chain"
    16  	"github.com/aergoio/aergo/consensus/chain"
    17  	"github.com/aergoio/aergo/contract"
    18  	"github.com/aergoio/aergo/internal/enc"
    19  	"github.com/aergoio/aergo/pkg/component"
    20  	"github.com/aergoio/aergo/state"
    21  	"github.com/aergoio/aergo/types"
    22  	"github.com/davecgh/go-spew/spew"
    23  	"github.com/libp2p/go-libp2p-core/crypto"
    24  )
    25  
    26  const (
    27  	slotQueueMax = 100
    28  )
    29  
    30  type txExec struct {
    31  	execTx bc.TxExecFn
    32  }
    33  
    34  func newTxExec(cdb contract.ChainAccessor, blockNo types.BlockNo, ts int64, prevHash []byte, chainID []byte) chain.TxOp {
    35  	// Block hash not determined yet
    36  	return &txExec{
    37  		execTx: bc.NewTxExecutor(nil, cdb, blockNo, ts, prevHash, contract.BlockFactory, chainID),
    38  	}
    39  }
    40  
    41  func (te *txExec) Apply(bState *state.BlockState, tx types.Transaction) error {
    42  	err := te.execTx(bState, tx)
    43  	return err
    44  }
    45  
    46  // BlockFactory is the main data structure for DPoS block factory.
    47  type BlockFactory struct {
    48  	*component.ComponentHub
    49  	jobQueue         chan interface{}
    50  	workerQueue      chan *bpInfo
    51  	bpTimeoutC       chan interface{}
    52  	quit             <-chan interface{}
    53  	maxBlockBodySize uint32
    54  	ID               string
    55  	privKey          crypto.PrivKey
    56  	txOp             chain.TxOp
    57  	sdb              *state.ChainStateDB
    58  }
    59  
    60  // NewBlockFactory returns a new BlockFactory
    61  func NewBlockFactory(hub *component.ComponentHub, sdb *state.ChainStateDB, quitC <-chan interface{}) *BlockFactory {
    62  	bf := &BlockFactory{
    63  		ComponentHub:     hub,
    64  		jobQueue:         make(chan interface{}, slotQueueMax),
    65  		workerQueue:      make(chan *bpInfo),
    66  		bpTimeoutC:       make(chan interface{}, 1),
    67  		maxBlockBodySize: chain.MaxBlockBodySize(),
    68  		quit:             quitC,
    69  		ID:               p2pkey.NodeSID(),
    70  		privKey:          p2pkey.NodePrivKey(),
    71  		sdb:              sdb,
    72  	}
    73  
    74  	bf.txOp = chain.NewCompTxOp(
    75  		// timeout check
    76  		chain.TxOpFn(func(bState *state.BlockState, txIn types.Transaction) error {
    77  			return bf.checkBpTimeout()
    78  		}),
    79  	)
    80  
    81  	return bf
    82  }
    83  
    84  func (bf *BlockFactory) setStateDB(sdb *state.ChainStateDB) {
    85  	bf.sdb = sdb.Clone()
    86  }
    87  
    88  // Start run a DPoS block factory service.
    89  func (bf *BlockFactory) Start() {
    90  	go func() {
    91  		go bf.worker()
    92  		go bf.controller()
    93  	}()
    94  }
    95  
    96  // JobQueue returns the queue for block production triggering.
    97  func (bf *BlockFactory) JobQueue() chan<- interface{} {
    98  	return bf.jobQueue
    99  }
   100  
   101  func (bf *BlockFactory) controller() {
   102  	defer shutdownMsg("block factory controller")
   103  
   104  	beginBlock := func(bpi *bpInfo) error {
   105  		// This is only for draining an unconsumed message, which means
   106  		// the previous block is generated within timeout. This code
   107  		// is needed since an empty block will be generated without it.
   108  		if err := bf.checkBpTimeout(); err == chain.ErrQuit {
   109  			return err
   110  		}
   111  
   112  		timeLeft := bpi.slot.RemainingTimeMS()
   113  		if timeLeft <= 0 {
   114  			return chain.ErrTimeout{Kind: "slot", Timeout: timeLeft}
   115  		}
   116  
   117  		select {
   118  		case bf.workerQueue <- bpi:
   119  		default:
   120  			logger.Error().Msgf(
   121  				"skip block production for the slot %v (best block: %v) due to a pending job",
   122  				spew.Sdump(bpi.slot), bpi.bestBlock.ID())
   123  		}
   124  		return nil
   125  	}
   126  
   127  	notifyBpTimeout := func(bpi *bpInfo) {
   128  		timeout := bpi.slot.GetBpTimeout()
   129  		time.Sleep(time.Duration(timeout) * time.Millisecond)
   130  		// TODO: skip when the triggered block has already been genearted!
   131  		bf.bpTimeoutC <- struct{}{}
   132  		logger.Debug().Int64("timeout", timeout).Msg("block production timeout signaled")
   133  	}
   134  
   135  	for {
   136  		select {
   137  		case info := <-bf.jobQueue:
   138  			bpi := info.(*bpInfo)
   139  			logger.Debug().Msgf("received bpInfo: %v %v",
   140  				log.DoLazyEval(func() string { return bpi.bestBlock.ID() }),
   141  				log.DoLazyEval(func() string { return spew.Sdump(bpi.slot) }))
   142  
   143  			err := beginBlock(bpi)
   144  			if err == chain.ErrQuit {
   145  				return
   146  			} else if err != nil {
   147  				logger.Debug().Err(err).Msg("skip block production")
   148  				continue
   149  			}
   150  
   151  			notifyBpTimeout(bpi)
   152  
   153  		case <-bf.quit:
   154  			return
   155  		}
   156  	}
   157  }
   158  
   159  func (bf *BlockFactory) worker() {
   160  	defer shutdownMsg("the block factory worker")
   161  
   162  	runtime.LockOSThread()
   163  
   164  	lpbNo := bsLoader.lpbNo()
   165  	logger.Info().Uint64("lastly produced block", lpbNo).
   166  		Msg("start the block factory worker")
   167  
   168  	for {
   169  		select {
   170  		case bpi := <-bf.workerQueue:
   171  		retry:
   172  			block, blockState, err := bf.generateBlock(bpi, lpbNo)
   173  			if err == chain.ErrQuit {
   174  				return
   175  			}
   176  
   177  			if err == chain.ErrBestBlock {
   178  				time.Sleep(tickDuration())
   179  				// This means the best block is beging changed by the chain
   180  				// service. If the chain service quickly executes the
   181  				// block, there may be still some remaining time to produce
   182  				// block in the current slot, though. Thus retry block
   183  				// production.
   184  				logger.Info().Err(err).Msg("retry block production")
   185  				bpi.updateBestBLock()
   186  				goto retry
   187  			} else if err != nil {
   188  				logger.Info().Err(err).Msg("failed to produce block")
   189  				continue
   190  			}
   191  
   192  			err = chain.ConnectBlock(bf, block, blockState, time.Second)
   193  			if err == nil {
   194  				lpbNo = block.BlockNo()
   195  			} else {
   196  				logger.Error().Msg(err.Error())
   197  			}
   198  
   199  		case <-bf.quit:
   200  			return
   201  		}
   202  	}
   203  }
   204  
   205  func (bf *BlockFactory) generateBlock(bpi *bpInfo, lpbNo types.BlockNo) (block *types.Block, bs *state.BlockState, err error) {
   206  	defer func() {
   207  		if panicMsg := recover(); panicMsg != nil {
   208  			block = nil
   209  			bs = nil
   210  			err = fmt.Errorf("panic ocurred during block generation - %v", panicMsg)
   211  		}
   212  	}()
   213  
   214  	ts := bpi.slot.UnixNano()
   215  
   216  	bs = bf.sdb.NewBlockState(bpi.bestBlock.GetHeader().GetBlocksRootHash())
   217  
   218  	txOp := chain.NewCompTxOp(
   219  		bf.txOp,
   220  		newTxExec(contract.ChainAccessor(bpi.ChainDB), bpi.bestBlock.GetHeader().GetBlockNo()+1, ts, bpi.bestBlock.BlockHash(), bpi.bestBlock.GetHeader().ChainID),
   221  	)
   222  
   223  	block, err = chain.GenerateBlock(bf, bpi.bestBlock, bs, txOp, ts, false)
   224  	if err != nil {
   225  		return nil, nil, err
   226  	}
   227  
   228  	block.SetConfirms(block.BlockNo() - lpbNo)
   229  
   230  	if err = block.Sign(bf.privKey); err != nil {
   231  		return nil, nil, err
   232  	}
   233  
   234  	logger.Info().
   235  		Str("BP", bf.ID).Str("id", block.ID()).
   236  		Str("sroot", enc.ToString(block.GetHeader().GetBlocksRootHash())).
   237  		Uint64("no", block.BlockNo()).Uint64("confirms", block.Confirms()).
   238  		Uint64("lpb", lpbNo).
   239  		Msg("block produced")
   240  
   241  	return
   242  }
   243  
   244  func (bf *BlockFactory) checkBpTimeout() error {
   245  	select {
   246  	case <-bf.bpTimeoutC:
   247  		return chain.ErrTimeout{Kind: "block"}
   248  	case <-bf.quit:
   249  		return chain.ErrQuit
   250  	default:
   251  		return nil
   252  	}
   253  }
   254  
   255  func shutdownMsg(m string) {
   256  	logger.Info().Msgf("shutdown initiated. stop the %s", m)
   257  }