diff --git a/blockchain/process.go b/blockchain/process.go index 693b4b8fab..57171831ab 100644 --- a/blockchain/process.go +++ b/blockchain/process.go @@ -91,6 +91,12 @@ func (b *BlockChain) processOrphans(hash *chainhash.Hash, flags BehaviorFlags) e return nil } +// WaitForChain blocks if the chain is processing a block or handling a reorg. +func (b *BlockChain) WaitForChain() { + b.chainLock.RLock() + b.chainLock.RUnlock() +} + // ProcessBlock is the main workhorse for handling insertion of new blocks into // the block chain. It includes functionality such as rejecting duplicate // blocks, ensuring blocks follow all rules, orphan handling, and insertion into diff --git a/blockmanager.go b/blockmanager.go index 0f11bf1a46..1b72e8d6fb 100644 --- a/blockmanager.go +++ b/blockmanager.go @@ -67,6 +67,15 @@ const ( // otherwise arise from sending old orphan blocks and forcing nodes to // do expensive lottery data calculations for them. maxReorgDepthNotify = 6 + + // templateRegenSeconds is the required number of seconds elapsed, + // with incoming non vote transactions, before template + // regeneration is required. + templateRegenSeconds = 30 + + // maxTemplateRegenSeconds is the maximum number of seconds elapsed + // without incoming transactions before template regeneration is required. + maxTemplateRegenSeconds = 60 ) // zeroHash is the zero value hash (all zeros). It is defined as a convenience. @@ -235,6 +244,13 @@ type processTransactionMsg struct { reply chan processTransactionResponse } +// regenMsg handles a request for block template regeneration. +type regenMsg struct{} + +// nonVoteMsg handles a request signalling the receipt of a non vote +// transaction. +type nonVoteMsg struct{} + // isCurrentMsg is a message type to be sent across the message channel for // requesting whether or not the block manager believes it is synced with // the currently connected peers. @@ -242,53 +258,6 @@ type isCurrentMsg struct { reply chan bool } -// getCurrentTemplateMsg handles a request for the current mining block template. -type getCurrentTemplateMsg struct { - reply chan getCurrentTemplateResponse -} - -// getCurrentTemplateResponse is a response sent to the reply channel of a -// getCurrentTemplateMsg. -type getCurrentTemplateResponse struct { - Template *BlockTemplate -} - -// setCurrentTemplateMsg handles a request to change the current mining block -// template. -type setCurrentTemplateMsg struct { - Template *BlockTemplate - reply chan setCurrentTemplateResponse -} - -// setCurrentTemplateResponse is a response sent to the reply channel of a -// setCurrentTemplateMsg. -type setCurrentTemplateResponse struct { -} - -// getParentTemplateMsg handles a request for the current parent mining block -// template. -type getParentTemplateMsg struct { - reply chan getParentTemplateResponse -} - -// getParentTemplateResponse is a response sent to the reply channel of a -// getParentTemplateMsg. -type getParentTemplateResponse struct { - Template *BlockTemplate -} - -// setParentTemplateMsg handles a request to change the parent mining block -// template. -type setParentTemplateMsg struct { - Template *BlockTemplate - reply chan setParentTemplateResponse -} - -// setParentTemplateResponse is a response sent to the reply channel of a -// setParentTemplateMsg. -type setParentTemplateResponse struct { -} - // headerNode is used as a node in a list of headers that are linked together // between checkpoints. type headerNode struct { @@ -407,6 +376,18 @@ type blockManager struct { lotteryDataBroadcast map[chainhash.Hash]struct{} lotteryDataBroadcastMutex sync.RWMutex + // the following fields handle block template regeneration and updating + // registered clients. + mining bool + lastRegen time.Time + nonVoteTxCountLastInterval uint64 + nonVoteTxCount uint64 + registeredClients map[chan *BlockTemplate]struct{} + regenMtx sync.Mutex + templateMtx sync.RWMutex + nonVoteTxCountMtx sync.RWMutex + registerMtx sync.RWMutex + cachedCurrentTemplate *BlockTemplate cachedParentTemplate *BlockTemplate AggressiveMining bool @@ -764,12 +745,33 @@ func (b *blockManager) current() bool { return true } +// NotifyRegisteredTemplateClients broadcasts the provided block template +// to all registered clients. +func (b *blockManager) NotifyRegisteredTemplateClients(t *BlockTemplate) { + b.registerMtx.Lock() + for c := range b.registeredClients { + c <- t + // Delete the client after updating it. + delete(b.registeredClients, c) + } + b.registerMtx.Unlock() +} + +// RequestTemplateUpdate registers a client for block template updates. +func (b *blockManager) RequestTemplateUpdate() chan *BlockTemplate { + updateChan := make(chan *BlockTemplate) + b.registerMtx.Lock() + b.registeredClients[updateChan] = struct{}{} + b.registerMtx.Unlock() + return updateChan +} + // checkBlockForHiddenVotes checks to see if a newly added block contains // any votes that were previously unknown to our daemon. If it does, it // adds these votes to the cached parent block template. // // This is UNSAFE for concurrent access. It must be called in single threaded -// access through the block mananger. All template access must also be routed +// access through the block manager. All template access must also be routed // through the block manager. func (b *blockManager) checkBlockForHiddenVotes(block *dcrutil.Block) { var votesFromBlock []*dcrutil.Tx @@ -1592,6 +1594,15 @@ func (b *blockManager) limitMap(m map[chainhash.Hash]struct{}, limit int) { // the fetching should proceed. func (b *blockManager) blockHandler() { candidatePeers := list.New() + b.lastRegen = b.server.txMemPool.LastUpdated() + updateNonVoteCounters := func() { + b.nonVoteTxCountMtx.Lock() + b.nonVoteTxCountLastInterval, b.nonVoteTxCount = b.nonVoteTxCount, 0 + b.nonVoteTxCountMtx.Unlock() + } + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + out: for { select { @@ -1805,31 +1816,92 @@ out: case isCurrentMsg: msg.reply <- b.current() - case getCurrentTemplateMsg: - cur := deepCopyBlockTemplate(b.cachedCurrentTemplate) - msg.reply <- getCurrentTemplateResponse{ - Template: cur, - } - - case setCurrentTemplateMsg: - b.cachedCurrentTemplate = deepCopyBlockTemplate(msg.Template) - msg.reply <- setCurrentTemplateResponse{} - - case getParentTemplateMsg: - par := deepCopyBlockTemplate(b.cachedParentTemplate) - msg.reply <- getParentTemplateResponse{ - Template: par, + case nonVoteMsg: + if b.mining { + // Increment the non vote transaction counter. + b.nonVoteTxCountMtx.Lock() + b.nonVoteTxCount++ + b.nonVoteTxCountMtx.Unlock() } - case setParentTemplateMsg: - b.cachedParentTemplate = deepCopyBlockTemplate(msg.Template) - msg.reply <- setParentTemplateResponse{} + case regenMsg: + b.chain.WaitForChain() + go func() { + if b.mining { + // Pick a mining address at random. + rand.Seed(time.Now().UnixNano()) + payToAddr := + cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] + + // Regenerate the block template. + b.templateMtx.Lock() + template, err := + NewBlockTemplate(b.server.rpcServer.policy, b.server, + payToAddr) + if err != nil { + bmgrLog.Infof( + "block template generation failed: %v", err) + } + if template != nil { + // Update the current and parent templates and + // notify all registered template clients. + b.cachedParentTemplate, b.cachedCurrentTemplate = + b.cachedCurrentTemplate, template + + b.NotifyRegisteredTemplateClients(b.cachedCurrentTemplate) + bmgrLog.Debugf("block template generated "+ + "(height: %v, hash: %v)", + template.Block.Header.Height, + template.Block.BlockHash()) + + // Update the last regen time. + b.regenMtx.Lock() + b.lastRegen = time.Now() + b.regenMtx.Unlock() + } + b.templateMtx.Unlock() + } + }() default: bmgrLog.Warnf("Invalid message type in block "+ "handler: %T", msg) } + case <-ticker.C: + go func() { + if b.mining { + b.nonVoteTxCountMtx.RLock() + hasNewNonVoteTxs := b.nonVoteTxCount > 0 + b.nonVoteTxCountMtx.RUnlock() + + b.regenMtx.Lock() + timeDiff := time.Since(b.lastRegen) + regen := b.server.txMemPool.LastUpdated().After(b.lastRegen. + Add(templateRegenSeconds * time.Second)) + b.regenMtx.Unlock() + + // Signal template regeneration if a minute has elapsed + // since the last regeneration and there is at least one + // registered subscriber for block template updates. + if timeDiff.Seconds() > maxTemplateRegenSeconds { + b.RegenTemplate() + updateNonVoteCounters() + return + } + + // Signal template regeneration if thirty seconds have + // elapsed and at least a new non vote transaction has + // been accepted by the mempool nd there is at least one + // registered subscriber for block template updates. + if regen && hasNewNonVoteTxs { + b.RegenTemplate() + } + + updateNonVoteCounters() + } + }() + case <-b.quit: break out } @@ -2119,7 +2191,9 @@ func (b *blockManager) handleNotifyMsg(notification *blockchain.Notification) { // Drop the associated mining template from the old chain, since it // will be no longer valid. + b.templateMtx.Lock() b.cachedCurrentTemplate = nil + b.templateMtx.Unlock() } } @@ -2413,39 +2487,19 @@ func (b *blockManager) TicketPoolValue() (dcrutil.Amount, error) { return b.chain.TicketPoolValue() } -// GetCurrentTemplate gets the current block template for mining. -func (b *blockManager) GetCurrentTemplate() *BlockTemplate { - reply := make(chan getCurrentTemplateResponse) - b.msgChan <- getCurrentTemplateMsg{reply: reply} - response := <-reply - return response.Template -} - -// SetCurrentTemplate sets the current block template for mining. -func (b *blockManager) SetCurrentTemplate(bt *BlockTemplate) { - reply := make(chan setCurrentTemplateResponse) - b.msgChan <- setCurrentTemplateMsg{Template: bt, reply: reply} - <-reply -} - -// GetParentTemplate gets the current parent block template for mining. -func (b *blockManager) GetParentTemplate() *BlockTemplate { - reply := make(chan getParentTemplateResponse) - b.msgChan <- getParentTemplateMsg{reply: reply} - response := <-reply - return response.Template +// RegenTemplate regenerates the block template. +func (b *blockManager) RegenTemplate() { + b.msgChan <- regenMsg{} } -// SetParentTemplate sets the current parent block template for mining. -func (b *blockManager) SetParentTemplate(bt *BlockTemplate) { - reply := make(chan setParentTemplateResponse) - b.msgChan <- setParentTemplateMsg{Template: bt, reply: reply} - <-reply +// ReceiveNonVoteTx signals the receipt of a non vote transaction. +func (b *blockManager) ReceiveNonVoteTx() { + b.msgChan <- nonVoteMsg{} } // newBlockManager returns a new Decred block manager. // Use Start to begin processing asynchronous block and inv updates. -func newBlockManager(s *server, indexManager blockchain.IndexManager, interrupt <-chan struct{}) (*blockManager, error) { +func newBlockManager(s *server, indexManager blockchain.IndexManager, mining bool, interrupt <-chan struct{}) (*blockManager, error) { bm := blockManager{ server: s, rejectedTxns: make(map[chainhash.Hash]struct{}), @@ -2456,6 +2510,12 @@ func newBlockManager(s *server, indexManager blockchain.IndexManager, interrupt progressLogger: newBlockProgressLogger("Processed", bmgrLog), msgChan: make(chan interface{}, cfg.MaxPeers*3), headerList: list.New(), + mining: mining, + regenMtx: sync.Mutex{}, + templateMtx: sync.RWMutex{}, + nonVoteTxCountMtx: sync.RWMutex{}, + registerMtx: sync.RWMutex{}, + registeredClients: make(map[chan *BlockTemplate]struct{}), AggressiveMining: !cfg.NonAggressive, quit: make(chan struct{}), } diff --git a/cpuminer.go b/cpuminer.go index dcbeac2979..be176052d6 100644 --- a/cpuminer.go +++ b/cpuminer.go @@ -8,8 +8,6 @@ package main import ( "encoding/binary" "errors" - "fmt" - "math/rand" "sync" "time" @@ -192,7 +190,8 @@ func (m *CPUMiner) submitBlock(block *dcrutil.Block) bool { // This function will return early with false when conditions that trigger a // stale block such as a new block showing up or periodically when there are // new transactions and enough time has elapsed without finding a solution. -func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, ticker *time.Ticker, quit chan struct{}) bool { +// This should be called with the +func (m *CPUMiner) solveBlock(header *wire.BlockHeader, ticker *time.Ticker, quit chan struct{}) bool { // Choose a random extra nonce offset for this block template and // worker. enOffset, err := wire.RandomUint64() @@ -203,7 +202,6 @@ func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, ticker *time.Ticker, quit } // Create a couple of convenience variables. - header := &msgBlock.Header targetDifficulty := blockchain.CompactToBig(header.Bits) // Initial state. @@ -242,7 +240,7 @@ func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, ticker *time.Ticker, quit return false } - err = UpdateBlockTime(msgBlock, m.server.blockManager) + err = UpdateBlockTime(header, m.server.blockManager) if err != nil { minrLog.Warnf("CPU miner unable to update block template "+ "time: %v", err) @@ -295,14 +293,6 @@ out: // Non-blocking select to fall through } - // No point in searching for a solution before the chain is - // synced. Also, grab the same lock as used for block - // submission, since the current block will 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() - time.Sleep(100 * time.Millisecond) - // Hacks to make dcr work with Decred PoC (simnet only) // TODO Remove before production. if cfg.SimNet { @@ -317,26 +307,17 @@ out: } } - // Choose a payment address at random. - rand.Seed(time.Now().UnixNano()) - payToAddr := cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] - - // 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) - m.submitBlockLock.Unlock() - if err != nil { - errStr := fmt.Sprintf("Failed to create new block "+ - "template: %v", err) - minrLog.Errorf(errStr) - continue - } + m.server.blockManager.chain.WaitForChain() + updateChan := m.server.blockManager.RequestTemplateUpdate() + template := <-updateChan + minrLog.Debugf("block template received "+ + "(height: %v, hash: %v)", + template.Block.Header.Height, + template.Block.BlockHash()) - // Not enough voters. - if template == nil { - continue - } + m.server.blockManager.templateMtx.RLock() + header := template.Block.Header + m.server.blockManager.templateMtx.RUnlock() // This prevents you from causing memory exhaustion issues // when mining aggressively in a simulation network. @@ -354,7 +335,7 @@ out: // with false when conditions that trigger a stale block, so // a new block template can be generated. When the return is // true a solution was found, so submit the solved block. - if m.solveBlock(template.Block, ticker, quit) { + if m.solveBlock(&header, ticker, quit) { block := dcrutil.NewBlock(template.Block) m.submitBlock(block) m.minedOnParents[template.Block.Header.PrevBlock]++ @@ -591,42 +572,30 @@ func (m *CPUMiner) GenerateNBlocks(n uint32) ([]*chainhash.Hash, error) { default: } - // Grab the lock used for block submission, since the current block will - // 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() - - // Choose a payment address at random. - rand.Seed(time.Now().UnixNano()) - payToAddr := cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] - - // 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) - m.submitBlockLock.Unlock() - if err != nil { - errStr := fmt.Sprintf("Failed to create new block "+ - "template: %v", err) - minrLog.Errorf(errStr) - continue - } - if template == nil { - errStr := fmt.Sprintf("Not enough voters on parent block " + - "and failed to pull parent template") - minrLog.Debugf(errStr) - continue - } + m.server.blockManager.chain.WaitForChain() + updateChan := m.server.blockManager.RequestTemplateUpdate() + m.server.blockManager.RegenTemplate() + template := <-updateChan + minrLog.Debugf("block template received "+ + "(height: %v, hash: %v)", + template.Block.Header.Height, + template.Block.BlockHash()) + + m.server.blockManager.templateMtx.RLock() + header := template.Block.Header + m.server.blockManager.templateMtx.RUnlock() // Attempt to solve the block. The function will exit early // with false when conditions that trigger a stale block, so // a new block template can be generated. When the return is // true a solution was found, so submit the solved block. - if m.solveBlock(template.Block, ticker, nil) { + if m.solveBlock(&header, ticker, nil) { block := dcrutil.NewBlock(template.Block) - m.submitBlock(block) - blockHashes[i] = block.Hash() - i++ + accepted := m.submitBlock(block) + if accepted { + blockHashes[i] = block.Hash() + i++ + } if i == n { minrLog.Tracef("Generated %d blocks", i) m.Lock() diff --git a/mempool/mempool.go b/mempool/mempool.go index f2985b7b8e..22428e8501 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -120,6 +120,14 @@ type Config struct { // to use for indexing the unconfirmed transactions in the memory pool. // This can be nil if the address index is not enabled. ExistsAddrIndex *indexers.ExistsAddrIndex + + // RegenTemplate defines the function to use in regenerating a + // block template. + RegenTemplate func() + + // NonVoteTx defines the function to use in signalling the receipt of a non + // vota transaction. + NonVoteTx func() } // Policy houses the policy (configuration parameters) which is used to @@ -640,6 +648,21 @@ func (mp *TxPool) RemoveDoubleSpends(tx *dcrutil.Tx) { // This function MUST be called with the mempool lock held (for writes). func (mp *TxPool) addTransaction(utxoView *blockchain.UtxoViewpoint, tx *dcrutil.Tx, txType stake.TxType, height int64, fee int64) { + // Signal block template regeneration when a vote transaction is received + // or a non vote transaction if the received transaction is not a vote. + if txType == stake.TxTypeSSGen { + if mp.cfg.RegenTemplate != nil { + mp.cfg.RegenTemplate() + } else { + log.Debug("provided regen template function is nil") + } + } else { + if mp.cfg.NonVoteTx != nil { + mp.cfg.NonVoteTx() + } else { + log.Debug("provided non vote tx function is nil") + } + } // Add the transaction to the pool and mark the referenced outpoints // as spent by the pool. diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index 158705178c..57aa689e45 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -611,6 +611,8 @@ func newPoolHarness(chainParams *chaincfg.Params) (*poolHarness, []spendableOutp SigCache: nil, AddrIndex: nil, ExistsAddrIndex: nil, + RegenTemplate: nil, + NonVoteTx: nil, }), } diff --git a/mining.go b/mining.go index e20045fb30..aaa89b1fdf 100644 --- a/mining.go +++ b/mining.go @@ -760,24 +760,23 @@ func deepCopyBlockTemplate(blockTemplate *BlockTemplate) *BlockTemplate { // of the blockchain. If there are too few voters and a cached parent template to // work off of is present, it will return a copy of that template to pass to the // miner. -// Safe for concurrent access. +// This should be called with the template mutex held. func handleTooFewVoters(subsidyCache *blockchain.SubsidyCache, nextHeight int64, miningAddress dcrutil.Address, bm *blockManager) (*BlockTemplate, error) { timeSource := bm.server.timeSource chainState := &bm.chainState stakeValidationHeight := bm.server.chainParams.StakeValidationHeight - curTemplate := bm.GetCurrentTemplate() + template := bm.cachedCurrentTemplate // Check to see if we've fallen off the chain, for example if a // reorganization had recently occurred. If this is the case, // nuke the templates. prevBlockHash := chainState.GetTopPrevHash() - if curTemplate != nil { - if !prevBlockHash.IsEqual( - &curTemplate.Block.Header.PrevBlock) { + if template != nil { + if !prevBlockHash.IsEqual(&(template.Block.Header.PrevBlock)) { minrLog.Debugf("Cached mining templates are no longer current, " + "resetting") - bm.SetCurrentTemplate(nil) - bm.SetParentTemplate(nil) + bm.cachedCurrentTemplate = nil + bm.cachedParentTemplate = nil } } @@ -785,20 +784,18 @@ func handleTooFewVoters(subsidyCache *blockchain.SubsidyCache, nextHeight int64, // (default behaviour). if nextHeight >= stakeValidationHeight { if bm.AggressiveMining { - if curTemplate != nil { - cptCopy := deepCopyBlockTemplate(curTemplate) - + if template != nil { // Update the timestamp of the old template. ts, err := medianAdjustedTime(chainState, timeSource) if err != nil { return nil, err } - cptCopy.Block.Header.Timestamp = ts + template.Block.Header.Timestamp = ts // If we're on testnet, the time since this last block // listed as the parent must be taken into consideration. if bm.server.chainParams.ReduceMinDifficulty { - parentHash := cptCopy.Block.Header.PrevBlock + parentHash := template.Block.Header.PrevBlock requiredDifficulty, err := bm.CalcNextRequiredDiffNode(&parentHash, ts) @@ -807,27 +804,27 @@ func handleTooFewVoters(subsidyCache *blockchain.SubsidyCache, nextHeight int64, err.Error()) } - cptCopy.Block.Header.Bits = requiredDifficulty + template.Block.Header.Bits = requiredDifficulty } // Choose a new extra nonce value that is one greater // than the previous extra nonce, so we don't remine the // same block and choose the same winners as before. - en := cptCopy.extractCoinbaseExtraNonce() + 1 - err = UpdateExtraNonce(cptCopy.Block, cptCopy.Height, en) + en := template.extractCoinbaseExtraNonce() + 1 + err = UpdateExtraNonce(template.Block, template.Height, en) if err != nil { return nil, err } // Update extranonce of the original template too, so // we keep getting unique numbers. - err = UpdateExtraNonce(curTemplate.Block, curTemplate.Height, en) + err = UpdateExtraNonce(template.Block, template.Height, en) if err != nil { return nil, err } // Make sure the block validates. - block := dcrutil.NewBlockDeepCopyCoinbase(cptCopy.Block) + block := dcrutil.NewBlockDeepCopyCoinbase(template.Block) err = bm.chain.CheckConnectBlockTemplate(block) if err != nil { minrLog.Errorf("failed to check template while "+ @@ -836,12 +833,12 @@ func handleTooFewVoters(subsidyCache *blockchain.SubsidyCache, nextHeight int64, err.Error()) } - return cptCopy, nil + return template, nil } // We may have just started mining and stored the current block // template, so we don't have a parent. - if curTemplate == nil { + if template == nil { // Fetch the latest block and head and begin working // off of it with an empty transaction tree regular // and the contents of that stake tree. In the future @@ -939,10 +936,7 @@ func handleTooFewVoters(subsidyCache *blockchain.SubsidyCache, nextHeight int64, str) } - // Make a copy to return. - cptCopy := deepCopyBlockTemplate(bt) - - return cptCopy, nil + return bt, nil } } } @@ -953,43 +947,6 @@ func handleTooFewVoters(subsidyCache *blockchain.SubsidyCache, nextHeight int64, return nil, nil } -// handleCreatedBlockTemplate stores a successfully created block template to -// the appropriate cache if needed, then returns the template to the miner to -// work on. The stored template is a copy of the template, to prevent races -// from occurring in case the template is mined on by the CPUminer. -func handleCreatedBlockTemplate(blockTemplate *BlockTemplate, bm *blockManager) (*BlockTemplate, error) { - curTemplate := bm.GetCurrentTemplate() - - nextBlockHeight := blockTemplate.Height - stakeValidationHeight := bm.server.chainParams.StakeValidationHeight - // This is where we begin storing block templates, when either the - // program is freshly started or the chain is matured to stake - // validation height. - if curTemplate == nil && nextBlockHeight >= stakeValidationHeight-2 { - bm.SetCurrentTemplate(blockTemplate) - } - - // We're at the height where the next block needs to include SSGens, - // so we check to if CachedCurrentTemplate is out of date. If it is, - // we store it as the cached parent template, and store the new block - // template as the currenct template. - if curTemplate != nil && nextBlockHeight >= stakeValidationHeight-1 { - if curTemplate.Height < nextBlockHeight { - bm.SetParentTemplate(curTemplate) - bm.SetCurrentTemplate(blockTemplate) - } - } - - // Overwrite the old cached block if it's out of date. - if curTemplate != nil { - if curTemplate.Height == nextBlockHeight { - bm.SetCurrentTemplate(blockTemplate) - } - } - - return blockTemplate, nil -} - // 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 @@ -2040,7 +1997,8 @@ mempoolLoop: ValidPayAddress: payToAddress != nil, } - return handleCreatedBlockTemplate(blockTemplate, server.blockManager) + return blockTemplate, nil + // return handleCreatedBlockTemplate(blockTemplate, server.blockManager) } // UpdateBlockTime updates the timestamp in the header of the passed block to @@ -2049,7 +2007,7 @@ 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 UpdateBlockTime(header *wire.BlockHeader, bManager *blockManager) 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. @@ -2058,7 +2016,7 @@ func UpdateBlockTime(msgBlock *wire.MsgBlock, bManager *blockManager) error { if err != nil { return miningRuleError(ErrGettingMedianTime, err.Error()) } - msgBlock.Header.Timestamp = newTimestamp + header.Timestamp = newTimestamp // If running on a network that requires recalculating the difficulty, // do so now. @@ -2068,7 +2026,7 @@ func UpdateBlockTime(msgBlock *wire.MsgBlock, bManager *blockManager) error { if err != nil { return miningRuleError(ErrGettingDifficulty, err.Error()) } - msgBlock.Header.Bits = difficulty + header.Bits = difficulty } return nil diff --git a/rpcserver.go b/rpcserver.go index 4c64c7511a..8e8c51dcc1 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2342,138 +2342,40 @@ func (state *gbtWorkState) templateUpdateChan(prevHash *chainhash.Hash, lastGene // addresses. // // This function MUST be called with the state locked. -func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bool) error { +func (state *gbtWorkState) updateBlockTemplate(s *rpcServer) error { + s.server.blockManager.chain.WaitForChain() + updateChan := s.server.blockManager.RequestTemplateUpdate() + template := <-updateChan + minrLog.Debugf("block template received "+ + "(height: %v, hash: %v)", + template.Block.Header.Height, + template.Block.BlockHash()) + lastTxUpdate := s.server.txMemPool.LastUpdated() if lastTxUpdate.IsZero() { lastTxUpdate = time.Now() } - // Generate a new block template when the current best block has - // changed or the transactions in the memory pool have been updated and - // it has been at least gbtRegenerateSecond since the last template was - // generated. - var msgBlock *wire.MsgBlock - var targetDifficulty string + s.server.blockManager.chainState.Lock() latestHash, _ := s.server.blockManager.chainState.Best() - template := state.template - if template == nil || state.prevHash == nil || - !state.prevHash.IsEqual(latestHash) || - (state.lastTxUpdate != lastTxUpdate && - time.Now().After(state.lastGenerated.Add(time.Second* - gbtRegenerateSeconds))) { - - // Reset the previous best hash the block template was generated - // against so any errors below cause the next invocation to try - // again. - state.prevHash = nil - - // Choose a payment address at random if the caller requests a - // full coinbase as opposed to only the pertinent details needed - // to create their own coinbase. - var payAddr dcrutil.Address - if !useCoinbaseValue { - payAddr = cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] - } - - // Create a new block template that has a coinbase which anyone - // can redeem. This is only acceptable because the returned - // 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) - if err != nil { - return rpcInternalError("Failed to create new block "+ - "template: "+err.Error(), "") - } - if blkTemplate == nil { - return rpcInternalError("Failed to create new block "+ - "template: not enough voters on parent and no "+ - "suitable cached template", "") - } - template = blkTemplate - msgBlock = template.Block - targetDifficulty = fmt.Sprintf("%064x", - blockchain.CompactToBig(msgBlock.Header.Bits)) - - // Find the minimum allowed timestamp for the block based on the - // median timestamp of the last several blocks per the chain - // consensus rules. - chainState := &s.server.blockManager.chainState - minTimestamp, err := minimumMedianTime(chainState) - if err != nil { - context := "Failed to get minimum median time" - return rpcInternalError(err.Error(), context) - } - - // Update work state to ensure another block template isn't - // generated until needed. - state.template = deepCopyBlockTemplate(template) - state.lastGenerated = time.Now() - state.lastTxUpdate = lastTxUpdate - state.prevHash = latestHash - state.minTimestamp = minTimestamp - - rpcsLog.Debugf("Generated block template (timestamp %v, "+ - "target %s, merkle root %s)", - msgBlock.Header.Timestamp, targetDifficulty, - msgBlock.Header.MerkleRoot) - - // Notify any clients that are long polling about the new - // template. - state.notifyLongPollers(latestHash, lastTxUpdate) - } else { - // At this point, there is a saved block template and another - // request for a template was made, but either the available - // transactions haven't change or it hasn't been long enough to - // trigger a new block template to be generated. So, update the - // existing block template. - - // When the caller requires a full coinbase as opposed to only - // the pertinent details needed to create their own coinbase, - // add a payment address to the output of the coinbase of the - // template if it doesn't already have one. Since this requires - // mining addresses to be specified via the config, an error is - // returned if none have been specified. - if !useCoinbaseValue && !template.ValidPayAddress { - // Choose a payment address at random. - payToAddr := cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] - - // Update the block coinbase output of the template to - // pay to the randomly selected payment address. - pkScript, err := txscript.PayToAddrScript(payToAddr) - if err != nil { - context := "Failed to create pay-to-addr script" - return rpcInternalError(err.Error(), context) - } - template.Block.Transactions[0].TxOut[0].PkScript = pkScript - template.ValidPayAddress = true - - // Update the merkle root. - block := dcrutil.NewBlock(template.Block) - merkles := blockchain.BuildMerkleTreeStore(block.Transactions()) - template.Block.Header.MerkleRoot = *merkles[len(merkles)-1] - } - - // Set locals for convenience. - msgBlock = template.Block - targetDifficulty = fmt.Sprintf("%064x", - blockchain.CompactToBig(msgBlock.Header.Bits)) - - // 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. - err := UpdateBlockTime(msgBlock, s.server.blockManager) - if err != nil { - context := "Failed to update timestamp" - return rpcInternalError(err.Error(), context) - } - msgBlock.Header.Nonce = 0 + s.server.blockManager.chainState.Unlock() - rpcsLog.Debugf("Updated block template (timestamp %v, "+ - "target %s)", msgBlock.Header.Timestamp, - targetDifficulty) + // Find the minimum allowed timestamp for the block based on the + // median timestamp of the last several blocks per the chain + // consensus rules. + chainState := &s.server.blockManager.chainState + minTimestamp, err := minimumMedianTime(chainState) + if err != nil { + context := "Failed to get minimum median time" + return rpcInternalError(err.Error(), context) } + state.lastGenerated = time.Now() + state.lastTxUpdate = lastTxUpdate + state.prevHash = latestHash + state.minTimestamp = minTimestamp + state.notifyLongPollers(latestHash, lastTxUpdate) + return nil } @@ -2715,7 +2617,7 @@ func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbase // be manually unlocked before waiting for a notification about block // template changes. - if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil { + if err := state.updateBlockTemplate(s); err != nil { state.Unlock() return nil, err } @@ -2739,9 +2641,7 @@ func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbase // identified by the long poll ID no longer matches the current block // template as this means the provided template is stale. prevTemplateHash := &state.template.Block.Header.PrevBlock - if !prevHash.IsEqual(prevTemplateHash) || - lastGenerated != state.lastGenerated.Unix() { - + if !prevHash.IsEqual(prevTemplateHash) { // Include whether or not it is valid to submit work against the // old block template depending on whether or not a solution has // already been found and added to the block chain. @@ -2775,11 +2675,11 @@ func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbase // Fallthrough } - // Get the lastest block template + // Get the lastest block template. state.Lock() defer state.Unlock() - if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil { + if err := state.updateBlockTemplate(s); err != nil { return nil, err } @@ -2873,7 +2773,7 @@ func handleGetBlockTemplateRequest(s *rpcServer, request *dcrjson.TemplateReques // least five seconds since the last template was generated. // Otherwise, the timestamp for the existing block template is updated // (and possibly the difficulty on testnet per the consesus rules). - if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil { + if err := state.updateBlockTemplate(s); err != nil { return nil, err } return state.blockTemplateResult(s.server.blockManager, useCoinbaseValue, nil) @@ -4033,123 +3933,15 @@ func pruneOldBlockTemplates(s *rpcServer, bestHeight int64) { } // handleGetWorkRequest is a helper for handleGetWork which deals with -// generating and returning work to the caller. +// fetching and returning work to the caller. // // This function MUST be called with the RPC workstate locked. func handleGetWorkRequest(s *rpcServer) (interface{}, error) { - state := s.workState - - // Generate a new block template when the current best block has - // changed or the transactions in the memory pool have been updated and - // it has been at least one minute since the last template was - // generated. - lastTxUpdate := s.server.txMemPool.LastUpdated() - latestHash, latestHeight := s.server.blockManager.chainState.Best() - msgBlock := state.msgBlock - - // The current code pulls down a new template every second, however - // with a large mempool this will be pretty excruciating sometimes. It - // should examine whether or not a new template needs to be created - // based on the votes present every second or so, and then, if needed, - // generate a new block template. TODO cj - if msgBlock == nil || state.prevHash == nil || - !state.prevHash.IsEqual(latestHash) || - (state.lastTxUpdate != lastTxUpdate && - time.Now().After(state.lastGenerated.Add(time.Second))) { - // Reset the extra nonce and clear all expired cached template - // variations if the best block changed. - if state.prevHash != nil && !state.prevHash.IsEqual(latestHash) { - state.extraNonce = 0 - pruneOldBlockTemplates(s, latestHeight) - } - - // Reset the previous best hash the block template was - // generated against so any errors below cause the next - // invocation to try again. - state.prevHash = nil - - // Choose a payment address at random. - payToAddr := cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] - - template, err := NewBlockTemplate(s.policy, s.server, payToAddr) - if err != nil { - context := "Failed to create new block template" - return nil, rpcInternalError(err.Error(), context) - } - if template == nil { - // This happens if the template is returned nil because - // there are not enough voters on HEAD and there is - // currently an unsuitable parent cached template to - // try building off of. - context := "Failed to create new block template: not " + - "enough voters and failed to find a suitable " + - "parent template to build from" - return nil, rpcInternalError("internal error", context) - } - templateCopy := deepCopyBlockTemplate(template) - msgBlock = templateCopy.Block - - // Update work state to ensure another block template isn't - // generated until needed. - state.msgBlock = msgBlock - state.lastGenerated = time.Now() - state.lastTxUpdate = lastTxUpdate - state.prevHash = latestHash - - rpcsLog.Debugf("Generated block template (timestamp %v, extra "+ - "nonce %d, target %064x, merkle root %s)", - msgBlock.Header.Timestamp, state.extraNonce, - blockchain.CompactToBig(msgBlock.Header.Bits), - msgBlock.Header.MerkleRoot) - } else { - if msgBlock == nil { - context := "Failed to create new block template, " + - "no previous state" - return nil, rpcInternalError("internal error", context) - } - - // At this point, there is a saved block template and a new - // request for work was made, but either the available - // transactions haven't change or it hasn't been long enough to - // trigger a new block template to be generated. So, update the - // existing block template and track the variations so each - // variation can be regenerated if a caller finds an answer and - // makes a submission against it. - templateCopy := deepCopyBlockTemplate(&BlockTemplate{ - Block: msgBlock, - }) - msgBlock = templateCopy.Block - - // 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. - err := UpdateBlockTime(msgBlock, s.server.blockManager) - if err != nil { - return nil, rpcInternalError(err.Error(), - "Failed to update block time") - } - - if templateCopy.Height > 1 { - // 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. - en := extractCoinbaseExtraNonce(msgBlock) + 1 - state.extraNonce++ - err := UpdateExtraNonce(msgBlock, latestHeight+1, en) - if err != nil { - errStr := fmt.Sprintf("Failed to update extra nonce: "+ - "%v", err) - return nil, rpcInternalError(errStr, "") - } - } - - rpcsLog.Debugf("Updated block template (timestamp %v, extra "+ - "nonce %d, target %064x, merkle root %s)", - msgBlock.Header.Timestamp, - state.extraNonce, - blockchain.CompactToBig(msgBlock.Header.Bits), - msgBlock.Header.MerkleRoot) - } + var msgBlock *wire.MsgBlock + s.server.blockManager.chain.WaitForChain() + s.server.blockManager.templateMtx.RLock() + msgBlock = s.server.blockManager.cachedCurrentTemplate.Block + s.server.blockManager.templateMtx.RUnlock() // In order to efficiently store the variations of block templates that // have been provided to callers, save a pointer to the block as well @@ -4355,12 +4147,18 @@ func handleGetWork(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (in } } - c := cmd.(*dcrjson.GetWorkCmd) + // Exit early if the template has not been generated yet. + s.server.blockManager.templateMtx.RLock() + templateNotGenerated := s.server.blockManager.cachedCurrentTemplate == nil + s.server.blockManager.templateMtx.RUnlock() + if templateNotGenerated { + return nil, &dcrjson.RPCError{ + Code: dcrjson.ErrRPCClientInInitialDownload, + Message: "Template is being generated...", + } + } - // Protect concurrent access from multiple RPC invocations for work - // requests and submission. - s.workState.Lock() - defer s.workState.Unlock() + c := cmd.(*dcrjson.GetWorkCmd) // When the caller provides data, it is a submission of a supposedly // solved block that needs to be checked and submitted to the network @@ -5796,7 +5594,6 @@ type rpcServer struct { statusLock sync.RWMutex wg sync.WaitGroup listeners []net.Listener - workState *workState gbtWorkState *gbtWorkState templatePool map[[merkleRootPairSize]byte]*workStateBlockInfo helpCacher *helpCacher @@ -6435,7 +6232,6 @@ func newRPCServer(listenAddrs []string, policy *mining.Policy, s *server) (*rpcS server: s, chain: s.blockManager.chain, statusLines: make(map[int]string), - workState: newWorkState(), templatePool: make(map[[merkleRootPairSize]byte]*workStateBlockInfo), gbtWorkState: newGbtWorkState(s.timeSource), helpCacher: newHelpCacher(), diff --git a/rpcserver_test.go b/rpcserver_test.go index a0496daf28..e748bb5110 100644 --- a/rpcserver_test.go +++ b/rpcserver_test.go @@ -39,7 +39,7 @@ func testGetBestBlock(r *rpctest.Harness, t *testing.T) { // Hash should be the same as the newly submitted block. if !bytes.Equal(bestHash[:], generatedBlockHashes[0][:]) { t.Fatalf("Block hashes do not match. Returned hash %v, wanted "+ - "hash %v", bestHash, generatedBlockHashes[0][:]) + "hash %v", bestHash, generatedBlockHashes[0]) } // Block height should now reflect newest height. @@ -91,7 +91,7 @@ func testGetBlockHash(r *rpctest.Harness, t *testing.T) { // Block hashes should match newly created block. if !bytes.Equal(generatedBlockHashes[0][:], blockHash[:]) { t.Fatalf("Block hashes do not match. Returned hash %v, wanted "+ - "hash %v", blockHash, generatedBlockHashes[0][:]) + "hash %v", blockHash, generatedBlockHashes[0]) } } diff --git a/server.go b/server.go index 137193571d..deaafe4c52 100644 --- a/server.go +++ b/server.go @@ -2459,7 +2459,14 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param if len(indexes) > 0 { indexManager = indexers.NewManager(db, indexes, chainParams) } - bm, err := newBlockManager(&s, indexManager, interrupt) + + // A config with mining address(es) connotes a mining node. + var isMining bool + if len(cfg.miningAddrs) > 0 { + isMining = true + } + + bm, err := newBlockManager(&s, indexManager, isMining, interrupt) if err != nil { return nil, err } @@ -2497,6 +2504,8 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param PastMedianTime: func() time.Time { return bm.chain.BestSnapshot().MedianTime }, AddrIndex: s.addrIndex, ExistsAddrIndex: s.existsAddrIndex, + RegenTemplate: func() { bm.RegenTemplate() }, + NonVoteTx: func() { bm.ReceiveNonVoteTx() }, } s.txMemPool = mempool.New(&txC)