OSDN Git Service

Merge pull request #201 from Bytom/v0.1
[bytom/vapor.git] / proposal / blockproposer / blockproposer.go
diff --git a/proposal/blockproposer/blockproposer.go b/proposal/blockproposer/blockproposer.go
new file mode 100644 (file)
index 0000000..32105db
--- /dev/null
@@ -0,0 +1,157 @@
+package blockproposer
+
+import (
+       "encoding/hex"
+       "sync"
+       "time"
+
+       log "github.com/sirupsen/logrus"
+
+       "github.com/vapor/account"
+       "github.com/vapor/config"
+       "github.com/vapor/consensus"
+       "github.com/vapor/event"
+       "github.com/vapor/proposal"
+       "github.com/vapor/protocol"
+)
+
+const (
+       logModule = "blockproposer"
+)
+
+// BlockProposer propose several block in specified time range
+type BlockProposer struct {
+       sync.Mutex
+       chain           *protocol.Chain
+       accountManager  *account.Manager
+       txPool          *protocol.TxPool
+       started         bool
+       quit            chan struct{}
+       eventDispatcher *event.Dispatcher
+}
+
+// generateBlocks is a worker that is controlled by the proposeWorkerController.
+// It is self contained in that it creates block templates and attempts to solve
+// them while detecting when it is performing stale work and reacting
+// accordingly by generating a new block template.  When a block is verified, it
+// is submitted.
+//
+// It must be run as a goroutine.
+func (b *BlockProposer) generateBlocks() {
+       xpub := config.CommonConfig.PrivateKey().XPub()
+       xpubStr := hex.EncodeToString(xpub[:])
+       ticker := time.NewTicker(consensus.BlockTimeInterval * time.Millisecond)
+       defer ticker.Stop()
+
+       for {
+               select {
+               case <-b.quit:
+                       return
+               case <-ticker.C:
+               }
+
+               bestBlockHeader := b.chain.BestBlockHeader()
+               bestBlockHash := bestBlockHeader.Hash()
+
+               now := uint64(time.Now().UnixNano() / 1e6)
+               base := now
+               if now < bestBlockHeader.Timestamp {
+                       base = bestBlockHeader.Timestamp
+               }
+               minTimeToNextBlock := consensus.BlockTimeInterval - base%consensus.BlockTimeInterval
+               nextBlockTime := base + minTimeToNextBlock
+               if (nextBlockTime - now) < consensus.BlockTimeInterval/10 {
+                       nextBlockTime += consensus.BlockTimeInterval
+               }
+
+               isBlocker, err := b.chain.IsBlocker(&bestBlockHash, xpubStr, nextBlockTime)
+               if err != nil {
+                       log.WithFields(log.Fields{"module": logModule, "error": err, "pubKey": xpubStr}).Error("fail on check is next blocker")
+                       continue
+               }
+
+               if !isBlocker {
+                       continue
+               }
+
+               block, err := proposal.NewBlockTemplate(b.chain, b.txPool, b.accountManager, nextBlockTime)
+               if err != nil {
+                       log.WithFields(log.Fields{"module": logModule, "error": err}).Error("failed on create NewBlockTemplate")
+                       continue
+               }
+
+               isOrphan, err := b.chain.ProcessBlock(block)
+               if err != nil {
+                       log.WithFields(log.Fields{"module": logModule, "height": block.BlockHeader.Height, "error": err}).Error("proposer fail on ProcessBlock")
+                       continue
+               }
+
+               log.WithFields(log.Fields{"module": logModule, "height": block.BlockHeader.Height, "isOrphan": isOrphan, "tx": len(block.Transactions)}).Info("proposer processed block")
+               // Broadcast the block and announce chain insertion event
+               if err = b.eventDispatcher.Post(event.NewProposedBlockEvent{Block: *block}); err != nil {
+                       log.WithFields(log.Fields{"module": logModule, "height": block.BlockHeader.Height, "error": err}).Error("proposer fail on post block")
+               }
+       }
+}
+
+// Start begins the block propose process as well as the speed monitor used to
+// track hashing metrics.  Calling this function when the block proposer has
+// already been started will have no effect.
+//
+// This function is safe for concurrent access.
+func (b *BlockProposer) Start() {
+       b.Lock()
+       defer b.Unlock()
+
+       // Nothing to do if the miner is already running
+       if b.started {
+               return
+       }
+
+       b.quit = make(chan struct{})
+       go b.generateBlocks()
+
+       b.started = true
+       log.Infof("block proposer started")
+}
+
+// Stop gracefully stops the proposal process by signalling all workers, and the
+// speed monitor to quit.  Calling this function when the block proposer has not
+// already been started will have no effect.
+//
+// This function is safe for concurrent access.
+func (b *BlockProposer) Stop() {
+       b.Lock()
+       defer b.Unlock()
+
+       // Nothing to do if the miner is not currently running
+       if !b.started {
+               return
+       }
+
+       close(b.quit)
+       b.started = false
+       log.Info("block proposer stopped")
+}
+
+// IsProposing returns whether the block proposer has been started.
+//
+// This function is safe for concurrent access.
+func (b *BlockProposer) IsProposing() bool {
+       b.Lock()
+       defer b.Unlock()
+
+       return b.started
+}
+
+// NewBlockProposer returns a new instance of a block proposer for the provided configuration.
+// Use Start to begin the proposal process.  See the documentation for BlockProposer
+// type for more details.
+func NewBlockProposer(c *protocol.Chain, accountManager *account.Manager, txPool *protocol.TxPool, dispatcher *event.Dispatcher) *BlockProposer {
+       return &BlockProposer{
+               chain:           c,
+               accountManager:  accountManager,
+               txPool:          txPool,
+               eventDispatcher: dispatcher,
+       }
+}