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

View file

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

View file

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

View file

@ -2365,7 +2365,9 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param
} }
s.txMemPool = mempool.New(&txC) 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 // NOTE: The CPU miner relies on the mempool, so the mempool has to be
// created before calling the function to create the CPU miner. // created before calling the function to create the CPU miner.
policy := mining.Policy{ policy := mining.Policy{
@ -2374,7 +2376,9 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param
BlockPrioritySize: cfg.BlockPrioritySize, BlockPrioritySize: cfg.BlockPrioritySize,
TxMinFreeFee: cfg.minRelayTxFee, 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 // Only setup a function to return new addresses to connect to when
// not running in connect-only mode. The simulation network is always // 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 { if !cfg.DisableRPC {
s.rpcServer, err = newRPCServer(cfg.RPCListeners, &policy, &s) s.rpcServer, err = newRPCServer(cfg.RPCListeners,
blockTemplateGenerator, &s)
if err != nil { if err != nil {
return nil, err return nil, err
} }