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 }