mining: Introduce a block template generator.

This introduces a new type named BlkTmplGenerator which encapsulates the
various state needed to generate block templates.

This is useful since it means code that needs to generate block
templates can simply accept the generator rather than needing access to
all of the additional state which in turn will ultimately make it easier
to split the mining code into its own package.
This commit is contained in:
Dave Collins 2016-10-25 18:52:24 -05:00
parent 2274d36333
commit 74fe2a4dfd
No known key found for this signature in database
GPG key ID: B8904D9D9C93D1F2
4 changed files with 81 additions and 43 deletions

View file

@ -14,7 +14,6 @@ import (
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/mining"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
@ -54,8 +53,7 @@ var (
// system which is typically sufficient.
type CPUMiner struct {
sync.Mutex
policy *mining.Policy
txSource mining.TxSource
g *BlkTmplGenerator
server *server
numWorkers uint32
started bool
@ -125,7 +123,7 @@ func (m *CPUMiner) submitBlock(block *btcutil.Block) bool {
// detected and all work on the stale block is halted to start work on
// a new block, but the check only happens periodically, so it is
// possible a block was found and submitted in between.
latestHash := m.server.blockManager.chain.BestSnapshot().Hash
latestHash := m.g.blockManager.chain.BestSnapshot().Hash
msgBlock := block.MsgBlock()
if !msgBlock.Header.PrevBlock.IsEqual(latestHash) {
minrLog.Debugf("Block submitted via CPU miner with previous "+
@ -135,7 +133,7 @@ func (m *CPUMiner) submitBlock(block *btcutil.Block) bool {
// Process this block using the same rules as blocks coming from other
// nodes. This will in turn relay it to the network like normal.
isOrphan, err := m.server.blockManager.ProcessBlock(block, blockchain.BFNone)
isOrphan, err := m.g.blockManager.ProcessBlock(block, blockchain.BFNone)
if err != nil {
// Anything other than a rule violation is an unexpected error,
// so log that error as an internal error.
@ -187,7 +185,7 @@ func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, blockHeight int32,
// Initial state.
lastGenerated := time.Now()
lastTxUpdate := m.txSource.LastUpdated()
lastTxUpdate := m.g.txSource.LastUpdated()
hashesCompleted := uint64(0)
// Note that the entire extra nonce range is iterated and the offset is
@ -197,7 +195,7 @@ func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, blockHeight int32,
// Update the extra nonce in the block template with the
// new value by regenerating the coinbase script and
// setting the merkle root to the new value. The
UpdateExtraNonce(msgBlock, blockHeight, extraNonce+enOffset)
m.g.UpdateExtraNonce(msgBlock, blockHeight, extraNonce+enOffset)
// Search through the entire nonce range for a solution while
// periodically checking for early quit and stale block
@ -213,7 +211,7 @@ func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, blockHeight int32,
// The current block is stale if the best block
// has changed.
best := m.server.blockManager.chain.BestSnapshot()
best := m.g.blockManager.chain.BestSnapshot()
if !header.PrevBlock.IsEqual(best.Hash) {
return false
}
@ -222,13 +220,13 @@ func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, blockHeight int32,
// has been updated since the block template was
// generated and it has been at least one
// minute.
if lastTxUpdate != m.txSource.LastUpdated() &&
if lastTxUpdate != m.g.txSource.LastUpdated() &&
time.Now().After(lastGenerated.Add(time.Minute)) {
return false
}
UpdateBlockTime(msgBlock, m.server.blockManager)
m.g.UpdateBlockTime(msgBlock)
default:
// Non-blocking select to fall through
@ -292,8 +290,8 @@ out:
// this would otherwise end up building a new block template on
// a block that is in the process of becoming stale.
m.submitBlockLock.Lock()
curHeight := m.server.blockManager.chain.BestSnapshot().Height
if curHeight != 0 && !m.server.blockManager.IsCurrent() {
curHeight := m.g.blockManager.chain.BestSnapshot().Height
if curHeight != 0 && !m.g.blockManager.IsCurrent() {
m.submitBlockLock.Unlock()
time.Sleep(time.Second)
continue
@ -306,7 +304,7 @@ out:
// Create a new block template using the available transactions
// in the memory pool as a source of transactions to potentially
// include in the block.
template, err := NewBlockTemplate(m.policy, m.server, payToAddr)
template, err := m.g.NewBlockTemplate(payToAddr)
m.submitBlockLock.Unlock()
if err != nil {
errStr := fmt.Sprintf("Failed to create new block "+
@ -559,7 +557,7 @@ func (m *CPUMiner) GenerateNBlocks(n uint32) ([]*chainhash.Hash, error) {
// be changing and this would otherwise end up building a new block
// template on a block that is in the process of becoming stale.
m.submitBlockLock.Lock()
curHeight := m.server.blockManager.chain.BestSnapshot().Height
curHeight := m.g.blockManager.chain.BestSnapshot().Height
// Choose a payment address at random.
rand.Seed(time.Now().UnixNano())
@ -568,7 +566,7 @@ func (m *CPUMiner) GenerateNBlocks(n uint32) ([]*chainhash.Hash, error) {
// Create a new block template using the available transactions
// in the memory pool as a source of transactions to potentially
// include in the block.
template, err := NewBlockTemplate(m.policy, m.server, payToAddr)
template, err := m.g.NewBlockTemplate(payToAddr)
m.submitBlockLock.Unlock()
if err != nil {
errStr := fmt.Sprintf("Failed to create new block "+
@ -603,10 +601,9 @@ func (m *CPUMiner) GenerateNBlocks(n uint32) ([]*chainhash.Hash, error) {
// newCPUMiner returns a new instance of a CPU miner for the provided server.
// Use Start to begin the mining process. See the documentation for CPUMiner
// type for more details.
func newCPUMiner(policy *mining.Policy, s *server) *CPUMiner {
func newCPUMiner(generator *BlkTmplGenerator, s *server) *CPUMiner {
return &CPUMiner{
policy: policy,
txSource: s.txMemPool,
g: generator,
server: s,
numWorkers: defaultNumWorkers,
updateNumWorkers: make(chan struct{}),

View file

@ -303,6 +303,40 @@ func medianAdjustedTime(chainState *blockchain.BestState, timeSource blockchain.
return newTimestamp
}
// BlkTmplGenerator provides a type that can be used to generate block templates
// based on a given mining policy and source of transactions to choose from.
// It also houses additional state required in order to ensure the templates
// are built on top of the current best chain and adhere to the consensus rules.
//
// See the NewBlockTemplate method for a detailed description of how the block
// template is generated.
type BlkTmplGenerator struct {
policy *mining.Policy
txSource mining.TxSource
sigCache *txscript.SigCache
blockManager *blockManager
timeSource blockchain.MedianTimeSource
}
// newBlkTmplGenerator returns a new block template generator for the given
// policy using transactions from the provided transaction source.
//
// The additional state-related fields are required in order to ensure the
// templates are built on top of the current best chain and adhere to the
// consensus rules.
func newBlkTmplGenerator(policy *mining.Policy, txSource mining.TxSource,
timeSource blockchain.MedianTimeSource, sigCache *txscript.SigCache,
blockManager *blockManager) *BlkTmplGenerator {
return &BlkTmplGenerator{
policy: policy,
txSource: txSource,
sigCache: sigCache,
blockManager: blockManager,
timeSource: timeSource,
}
}
// NewBlockTemplate returns a new block template that is ready to be solved
// using the transactions from the passed transaction source pool and a coinbase
// that either pays to the passed address if it is not nil, or a coinbase that
@ -365,10 +399,12 @@ func medianAdjustedTime(chainState *blockchain.BestState, timeSource blockchain.
// | transactions (while block size | |
// | <= policy.BlockMinSize) | |
// ----------------------------------- --
func NewBlockTemplate(policy *mining.Policy, server *server, payToAddress btcutil.Address) (*BlockTemplate, error) {
var txSource mining.TxSource = server.txMemPool
blockManager := server.blockManager
timeSource := server.timeSource
func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress btcutil.Address) (*BlockTemplate, error) {
// Locals for faster access.
policy := g.policy
blockManager := g.blockManager
timeSource := g.timeSource
sigCache := g.sigCache
// Extend the most recently known best block.
best := blockManager.chain.BestSnapshot()
@ -401,7 +437,7 @@ func NewBlockTemplate(policy *mining.Policy, server *server, payToAddress btcuti
// number of items that are available for the priority queue. Also,
// choose the initial sort order for the priority queue based on whether
// or not there is an area allocated for high-priority transactions.
sourceTxns := txSource.MiningDescs()
sourceTxns := g.txSource.MiningDescs()
sortedByFee := policy.BlockPrioritySize == 0
priorityQueue := newTxPriorityQueue(len(sourceTxns), sortedByFee)
@ -471,7 +507,7 @@ mempoolLoop:
originIndex := txIn.PreviousOutPoint.Index
utxoEntry := utxos.LookupEntry(originHash)
if utxoEntry == nil || utxoEntry.IsOutputSpent(originIndex) {
if !txSource.HaveTransaction(originHash) {
if !g.txSource.HaveTransaction(originHash) {
minrLog.Tracef("Skipping tx %s because "+
"it references unspent output "+
"%s which is not available",
@ -636,7 +672,7 @@ mempoolLoop:
continue
}
err = blockchain.ValidateTransactionScripts(tx, blockUtxos,
txscript.StandardVerifyFlags, server.sigCache)
txscript.StandardVerifyFlags, sigCache)
if err != nil {
minrLog.Tracef("Skipping tx %s due to error in "+
"ValidateTransactionScripts: %v", tx.Hash(), err)
@ -738,19 +774,19 @@ mempoolLoop:
// consensus rules. Finally, it will update the target difficulty if needed
// based on the new time for the test networks since their target difficulty can
// change based upon time.
func UpdateBlockTime(msgBlock *wire.MsgBlock, bManager *blockManager) error {
func (g *BlkTmplGenerator) UpdateBlockTime(msgBlock *wire.MsgBlock) error {
// The new timestamp is potentially adjusted to ensure it comes after
// the median time of the last several blocks per the chain consensus
// rules.
best := bManager.chain.BestSnapshot()
newTimestamp := medianAdjustedTime(best, bManager.server.timeSource)
best := g.blockManager.chain.BestSnapshot()
newTimestamp := medianAdjustedTime(best, g.timeSource)
msgBlock.Header.Timestamp = newTimestamp
// If running on a network that requires recalculating the difficulty,
// do so now.
if activeNetParams.ReduceMinDifficulty {
difficulty, err := bManager.chain.CalcNextRequiredDifficulty(
newTimestamp)
chain := g.blockManager.chain
difficulty, err := chain.CalcNextRequiredDifficulty(newTimestamp)
if err != nil {
return err
}
@ -764,7 +800,7 @@ func UpdateBlockTime(msgBlock *wire.MsgBlock, bManager *blockManager) error {
// block by regenerating the coinbase script with the passed value and block
// height. It also recalculates and updates the new merkle root that results
// from changing the coinbase script.
func UpdateExtraNonce(msgBlock *wire.MsgBlock, blockHeight int32, extraNonce uint64) error {
func (g *BlkTmplGenerator) UpdateExtraNonce(msgBlock *wire.MsgBlock, blockHeight int32, extraNonce uint64) error {
coinbaseScript, err := standardCoinbaseScript(blockHeight, extraNonce)
if err != nil {
return err

View file

@ -34,7 +34,6 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/database"
"github.com/btcsuite/btcd/mempool"
"github.com/btcsuite/btcd/mining"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
@ -1389,7 +1388,7 @@ func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bo
// block template doesn't include the coinbase, so the caller
// will ultimately create their own coinbase which pays to the
// appropriate address(es).
blkTemplate, err := NewBlockTemplate(s.policy, s.server, payAddr)
blkTemplate, err := s.generator.NewBlockTemplate(payAddr)
if err != nil {
return internalRPCError("Failed to create new block "+
"template: "+err.Error(), "")
@ -1462,7 +1461,7 @@ func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bo
// Update the time of the block template to the current time
// while accounting for the median time of the past several
// blocks per the chain consensus rules.
UpdateBlockTime(msgBlock, s.server.blockManager)
s.generator.UpdateBlockTime(msgBlock)
msgBlock.Header.Nonce = 0
rpcsLog.Debugf("Updated block template (timestamp %v, "+
@ -2558,7 +2557,7 @@ func handleGetWorkRequest(s *rpcServer) (interface{}, error) {
// Choose a payment address at random.
payToAddr := cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))]
template, err := NewBlockTemplate(s.policy, s.server, payToAddr)
template, err := s.generator.NewBlockTemplate(payToAddr)
if err != nil {
context := "Failed to create new block template"
return nil, internalRPCError(err.Error(), context)
@ -2591,13 +2590,14 @@ func handleGetWorkRequest(s *rpcServer) (interface{}, error) {
// Update the time of the block template to the current time
// while accounting for the median time of the past several
// blocks per the chain consensus rules.
UpdateBlockTime(msgBlock, s.server.blockManager)
s.generator.UpdateBlockTime(msgBlock)
// Increment the extra nonce and update the block template
// with the new value by regenerating the coinbase script and
// setting the merkle root to the new value.
state.extraNonce++
err := UpdateExtraNonce(msgBlock, latestHeight+1, state.extraNonce)
err := s.generator.UpdateExtraNonce(msgBlock, latestHeight+1,
state.extraNonce)
if err != nil {
errStr := fmt.Sprintf("Failed to update extra nonce: "+
"%v", err)
@ -3654,7 +3654,7 @@ func handleVerifyMessage(s *rpcServer, cmd interface{}, closeChan <-chan struct{
type rpcServer struct {
started int32
shutdown int32
policy *mining.Policy
generator *BlkTmplGenerator
server *server
chain *blockchain.BlockChain
authsha [fastsha256.Size]byte
@ -4160,10 +4160,10 @@ func genCertPair(certFile, keyFile string) error {
}
// newRPCServer returns a new instance of the rpcServer struct.
func newRPCServer(listenAddrs []string, policy *mining.Policy, s *server) (*rpcServer, error) {
func newRPCServer(listenAddrs []string, generator *BlkTmplGenerator, s *server) (*rpcServer, error) {
rpc := rpcServer{
policy: policy,
server: s,
generator: generator,
chain: s.blockManager.chain,
statusLines: make(map[int]string),
workState: newWorkState(),

View file

@ -2365,7 +2365,9 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param
}
s.txMemPool = mempool.New(&txC)
// Create the mining policy based on the configuration options.
// Create the mining policy and block template generator based on the
// configuration options.
//
// NOTE: The CPU miner relies on the mempool, so the mempool has to be
// created before calling the function to create the CPU miner.
policy := mining.Policy{
@ -2374,7 +2376,9 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param
BlockPrioritySize: cfg.BlockPrioritySize,
TxMinFreeFee: cfg.minRelayTxFee,
}
s.cpuMiner = newCPUMiner(&policy, &s)
blockTemplateGenerator := newBlkTmplGenerator(&policy, s.txMemPool,
s.timeSource, s.sigCache, bm)
s.cpuMiner = newCPUMiner(blockTemplateGenerator, &s)
// Only setup a function to return new addresses to connect to when
// not running in connect-only mode. The simulation network is always
@ -2449,7 +2453,8 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param
}
if !cfg.DisableRPC {
s.rpcServer, err = newRPCServer(cfg.RPCListeners, &policy, &s)
s.rpcServer, err = newRPCServer(cfg.RPCListeners,
blockTemplateGenerator, &s)
if err != nil {
return nil, err
}