From d2d3c2f39727ab058db785256513488386c02965 Mon Sep 17 00:00:00 2001 From: Donald Adu-Poku Date: Mon, 27 Aug 2018 10:38:14 +0000 Subject: [PATCH] multi: use BestState & generated block template in rpc handlers. --- mining.go | 2 +- rpcserver.go | 359 ++++++++++----------------------------------------- server.go | 12 +- 3 files changed, 76 insertions(+), 297 deletions(-) diff --git a/mining.go b/mining.go index cec76dc803..ce4a6f2063 100644 --- a/mining.go +++ b/mining.go @@ -2057,7 +2057,7 @@ func UpdateBlockTime(header *wire.BlockHeader, bManager *blockManager) error { // the median time of the last several blocks per the chain consensus // rules. newTimestamp, err := medianAdjustedTime(bManager.chain.BestSnapshot(), - bManager.server.timeSource) + bManager.server.bg.tg.timeSource) if err != nil { return miningRuleError(ErrGettingMedianTime, err.Error()) } diff --git a/rpcserver.go b/rpcserver.go index 63f017cb3f..00d881286a 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -438,23 +438,6 @@ type workStateBlockInfo struct { pkScript []byte } -// workState houses state that is used in between multiple RPC invocations to -// getwork. -type workState struct { - sync.Mutex - lastTxUpdate time.Time - lastGenerated time.Time - prevHash *chainhash.Hash - msgBlock *wire.MsgBlock - extraNonce uint64 -} - -// newWorkState returns a new instance of a workState with all internal fields -// initialized and ready to use. -func newWorkState() *workState { - return &workState{} -} - // gbtWorkState houses state that is used in between multiple RPC invocations to // getblocktemplate. type gbtWorkState struct { @@ -463,17 +446,14 @@ type gbtWorkState struct { lastGenerated time.Time prevHash *chainhash.Hash minTimestamp time.Time - template *BlockTemplate notifyMap map[chainhash.Hash]map[int64]chan struct{} - timeSource blockchain.MedianTimeSource } // newGbtWorkState returns a new instance of a gbtWorkState with all internal // fields initialized and ready to use. -func newGbtWorkState(timeSource blockchain.MedianTimeSource) *gbtWorkState { +func newGbtWorkState() *gbtWorkState { return &gbtWorkState{ - notifyMap: make(map[chainhash.Hash]map[int64]chan struct{}), - timeSource: timeSource, + notifyMap: make(map[chainhash.Hash]map[int64]chan struct{}), } } @@ -2341,137 +2321,35 @@ 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.chain.WaitForChain() + updateChan := s.server.bg.RequestTemplateUpdate() + template := <-updateChan + minrLog.Debugf("block template received from background generator "+ + "(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 - best := s.server.blockManager.chain.BestSnapshot() - template := state.template - if template == nil || state.prevHash == nil || - !state.prevHash.IsEqual(&best.Hash) || - (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 := s.generator.NewBlockTemplate(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. - minTimestamp, err := minimumMedianTime(best) - 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 = &best.Hash - 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(&best.Hash, 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.Header, s.server.blockManager) - if err != nil { - context := "Failed to update timestamp" - return rpcInternalError(err.Error(), context) - } - msgBlock.Header.Nonce = 0 - - 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. + best := s.chain.BestSnapshot() + minTimestamp, err := minimumMedianTime(best) + if err != nil { + context := "Failed to get minimum median time" + return rpcInternalError(err.Error(), context) } + state.lastGenerated = time.Now() + state.lastTxUpdate = lastTxUpdate + state.prevHash = &best.Hash + state.minTimestamp = minTimestamp + state.notifyLongPollers(&best.Hash, lastTxUpdate) + return nil } @@ -2481,15 +2359,21 @@ func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bo // // This function MUST be called with the state locked. func (state *gbtWorkState) blockTemplateResult(bm *blockManager, useCoinbaseValue bool, submitOld *bool) (*dcrjson.GetBlockTemplateResult, error) { + bm.server.bg.templateMtx.RLock() + msgBlock := bm.server.bg.currentTemplate.Block + fees := bm.server.bg.currentTemplate.Fees + sigOpCounts := bm.server.bg.currentTemplate.SigOpCounts + validPayAddress := bm.server.bg.currentTemplate.ValidPayAddress + adjustedTime := bm.server.bg.tg.timeSource.AdjustedTime() + bm.server.bg.templateMtx.RUnlock() + + header := &msgBlock.Header + maxTime := adjustedTime.Add(time.Second * blockchain.MaxTimeOffsetSeconds) + // Ensure the timestamps are still in valid range for the template. // This should really only ever happen if the local clock is changed // after the template is generated, but it's important to avoid serving // invalid block templates. - template := deepCopyBlockTemplate(state.template) - msgBlock := template.Block - header := &msgBlock.Header - adjustedTime := state.timeSource.AdjustedTime() - maxTime := adjustedTime.Add(time.Second * blockchain.MaxTimeOffsetSeconds) if header.Timestamp.After(maxTime) { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCOutOfRange, @@ -2553,8 +2437,8 @@ func (state *gbtWorkState) blockTemplateResult(bm *blockManager, useCoinbaseValu txTypeStr = "error" } - fee := template.Fees[i] - sigOps := template.SigOpCounts[i] + fee := fees[i] + sigOps := sigOpCounts[i] resultTx := dcrjson.GetBlockTemplateResultTx{ Data: hex.EncodeToString(txBuf.Bytes()), Hash: txHash.String(), @@ -2610,8 +2494,8 @@ func (state *gbtWorkState) blockTemplateResult(bm *blockManager, useCoinbaseValu txTypeStr = "revocation" } - fee := template.Fees[i+len(msgBlock.Transactions)] - sigOps := template.SigOpCounts[i+len(msgBlock.Transactions)] + fee := fees[i+len(msgBlock.Transactions)] + sigOps := sigOpCounts[i+len(msgBlock.Transactions)] resultTx := dcrjson.GetBlockTemplateResultTx{ Data: hex.EncodeToString(txBuf.Bytes()), Hash: stxHash.String(), @@ -2665,7 +2549,7 @@ func (state *gbtWorkState) blockTemplateResult(bm *blockManager, useCoinbaseValu } else { // Ensure the template has a valid payment address associated // with it when a full coinbase is requested. - if !template.ValidPayAddress { + if !validPayAddress { context := "Invalid blocksize" errStr := fmt.Sprintf("A coinbase transaction has " + "been requested, but the server has not " + @@ -2686,8 +2570,8 @@ func (state *gbtWorkState) blockTemplateResult(bm *blockManager, useCoinbaseValu Data: hex.EncodeToString(txBuf.Bytes()), Hash: tx.TxHash().String(), Depends: []int64{}, - Fee: template.Fees[0], - SigOps: template.SigOpCounts[0], + Fee: fees[0], + SigOps: sigOpCounts[0], } reply.CoinbaseTxn = &resultTx @@ -2713,7 +2597,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 } @@ -2736,7 +2620,10 @@ func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbase // Return the block template now if the specific block template // 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 + s.server.bg.templateMtx.RLock() + prevTemplateHash := &s.server.bg.currentTemplate. + Block.Header.PrevBlock + s.server.bg.templateMtx.RUnlock() if !prevHash.IsEqual(prevTemplateHash) || lastGenerated != state.lastGenerated.Unix() { @@ -2773,18 +2660,20 @@ 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 } // 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. - submitOld := prevHash.IsEqual(&state.template.Block.Header.PrevBlock) + submitOld := prevHash.IsEqual(&s.server.blockManager.chain. + BestSnapshot().Hash) + result, err := state.blockTemplateResult(s.server.blockManager, useCoinbaseValue, &submitOld) if err != nil { @@ -2871,7 +2760,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) @@ -4027,7 +3916,7 @@ func handleGetTxOut(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i } // pruneOldBlockTemplates prunes all old block templates from the templatePool -// map. Must be called with the RPC workstate locked to avoid races to the map. +// map. func pruneOldBlockTemplates(s *rpcServer, bestHeight int64) { pool := s.templatePool for rootHash, blkData := range pool { @@ -4040,122 +3929,10 @@ func pruneOldBlockTemplates(s *rpcServer, bestHeight int64) { // handleGetWorkRequest is a helper for handleGetWork which deals with // generating 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() - best := s.server.blockManager.chain.BestSnapshot() - 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(&best.Hash) || - (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(&best.Hash) { - state.extraNonce = 0 - pruneOldBlockTemplates(s, best.Height) - } - - // 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 := s.generator.NewBlockTemplate(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 = &best.Hash - - 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.Header, 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, best.Height+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) - } + s.server.bg.templateMtx.RLock() + msgBlock := s.server.bg.currentTemplate.Block + s.server.bg.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 @@ -4222,8 +3999,6 @@ func handleGetWorkRequest(s *rpcServer) (interface{}, error) { // handleGetWorkSubmission is a helper for handleGetWork which deals with // the calling submitting work to be verified and processed. -// -// This function MUST be called with the RPC workstate locked. func handleGetWorkSubmission(s *rpcServer, hexData string) (interface{}, error) { // Ensure the provided data is sane. if len(hexData)%2 != 0 { @@ -4361,12 +4136,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.bg.templateMtx.RLock() + notGenerated := s.server.bg.currentTemplate == nil + s.server.bg.templateMtx.RUnlock() + if notGenerated { + return nil, &dcrjson.RPCError{ + Code: dcrjson.ErrRPCClientInInitialDownload, + Message: "Template yet to be 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 @@ -5799,7 +5580,6 @@ type rpcServer struct { statusLock sync.RWMutex wg sync.WaitGroup listeners []net.Listener - workState *workState gbtWorkState *gbtWorkState templatePool map[[merkleRootPairSize]byte]*workStateBlockInfo helpCacher *helpCacher @@ -6438,9 +6218,8 @@ func newRPCServer(listenAddrs []string, generator *BlkTmplGenerator, s *server) generator: generator, chain: s.blockManager.chain, statusLines: make(map[int]string), - workState: newWorkState(), templatePool: make(map[[merkleRootPairSize]byte]*workStateBlockInfo), - gbtWorkState: newGbtWorkState(s.timeSource), + gbtWorkState: newGbtWorkState(), helpCacher: newHelpCacher(), requestProcessShutdown: make(chan struct{}), quit: make(chan int), diff --git a/server.go b/server.go index 1c3847c613..d1a67d0241 100644 --- a/server.go +++ b/server.go @@ -163,7 +163,7 @@ type server struct { sigCache *txscript.SigCache rpcServer *rpcServer blockManager *blockManager - bgBlkTmplGenerator *BgBlkTmplGenerator + bg *BgBlkTmplGenerator txMemPool *mempool.TxPool cpuMiner *CPUMiner modifyRebroadcastInv chan interface{} @@ -2539,17 +2539,17 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param // Start the background template generator. if len(cfg.miningAddrs) > 0 { minrLog.Debug("Starting background block generator") - s.bgBlkTmplGenerator = newBgBlkTmplGenerator(blockTemplateGenerator) + s.bg = newBgBlkTmplGenerator(blockTemplateGenerator) // Set signalling functions for the mempool. - s.txMemPool.SetRegenTemplateFunc(s.bgBlkTmplGenerator.RegenTemplate) - s.txMemPool.SetNonVoteTxFunc(s.bgBlkTmplGenerator.ReceiveNonVoteTx) + s.txMemPool.SetRegenTemplateFunc(s.bg.RegenTemplate) + s.txMemPool.SetNonVoteTxFunc(s.bg.ReceiveNonVoteTx) - s.bgBlkTmplGenerator.start(s.context) + s.bg.start(s.context) } s.cpuMiner = newCPUMiner(&cpuminerConfig{ - bg: s.bgBlkTmplGenerator, + bg: s.bg, ProcessBlock: bm.ProcessBlock, ConnectedCount: s.ConnectedCount, IsCurrent: bm.IsCurrent,