From 285ea48bfb7e6cb47f9c11b2ee11f5a4fd868680 Mon Sep 17 00:00:00 2001 From: Youssef Boukenken Date: Wed, 12 Feb 2020 23:32:24 -0500 Subject: [PATCH] multi: Decouple blockManager from mining. This removes coupling between the mining code, blockManager, and the main configuration. This change is a step towards moving the mining code into its own package. --- blockmanager.go | 33 ++++++++------ mining.go | 110 ++++++++++++++++++++++++----------------------- mining/policy.go | 13 +++++- rpcserver.go | 6 ++- server.go | 7 ++- 5 files changed, 98 insertions(+), 71 deletions(-) diff --git a/blockmanager.go b/blockmanager.go index ec2ef778..4357be6e 100644 --- a/blockmanager.go +++ b/blockmanager.go @@ -331,8 +331,6 @@ type blockManager struct { lotteryDataBroadcast map[chainhash.Hash]struct{} lotteryDataBroadcastMutex sync.RWMutex - AggressiveMining bool - // The following fields are used to filter duplicate block announcements. announcedBlockMtx sync.Mutex announcedBlock *chainhash.Hash @@ -359,6 +357,14 @@ func (b *blockManager) resetHeaderState(newestHash *chainhash.Hash, newestHeight } } +// NotifyWork passes new mining work to the notification manager +// for block notification processing. +func (b *blockManager) NotifyWork(templateNtfn *TemplateNtfn) { + if r := b.cfg.RpcServer(); r != nil { + r.ntfnMgr.NotifyWork(templateNtfn) + } +} + // SyncHeight returns latest known block being synced to. func (b *blockManager) SyncHeight() int64 { b.syncHeightMtx.Lock() @@ -2438,18 +2444,17 @@ func (b *blockManager) TicketPoolValue() (dcrutil.Amount, error) { // Use Start to begin processing asynchronous block and inv updates. func newBlockManager(config *blockManagerConfig) (*blockManager, error) { bm := blockManager{ - cfg: config, - rejectedTxns: make(map[chainhash.Hash]struct{}), - requestedTxns: make(map[chainhash.Hash]struct{}), - requestedBlocks: make(map[chainhash.Hash]struct{}), - peerStates: make(map[*peerpkg.Peer]*peerSyncState), - progressLogger: newBlockProgressLogger("Processed", bmgrLog), - msgChan: make(chan interface{}, cfg.MaxPeers*3), - headerList: list.New(), - AggressiveMining: !cfg.NonAggressive, - quit: make(chan struct{}), - orphans: make(map[chainhash.Hash]*orphanBlock), - prevOrphans: make(map[chainhash.Hash][]*orphanBlock), + cfg: config, + rejectedTxns: make(map[chainhash.Hash]struct{}), + requestedTxns: make(map[chainhash.Hash]struct{}), + requestedBlocks: make(map[chainhash.Hash]struct{}), + peerStates: make(map[*peerpkg.Peer]*peerSyncState), + progressLogger: newBlockProgressLogger("Processed", bmgrLog), + msgChan: make(chan interface{}, cfg.MaxPeers*3), + headerList: list.New(), + quit: make(chan struct{}), + orphans: make(map[chainhash.Hash]*orphanBlock), + prevOrphans: make(map[chainhash.Hash][]*orphanBlock), } best := bm.cfg.Chain.BestSnapshot() diff --git a/mining.go b/mining.go index 0622bcf6..c7f81b3f 100644 --- a/mining.go +++ b/mining.go @@ -67,10 +67,6 @@ const ( // incoming non vote transactions before template regeneration // is required. templateRegenSecs = 30 - - // merkleRootPairSize is the size in bytes of the merkle root + stake root - // of a block. - merkleRootPairSize = 64 ) // txPrioItem houses a transaction along with extra information that allows the @@ -608,7 +604,7 @@ func minimumMedianTime(best *blockchain.BestState) time.Time { // medianAdjustedTime returns the current time adjusted to ensure it is at least // one second after the median timestamp of the last several blocks per the // chain consensus rules. -func medianAdjustedTime(best *blockchain.BestState, timeSource blockchain.MedianTimeSource) time.Time { +func (g *BlkTmplGenerator) medianAdjustedTime(best *blockchain.BestState, timeSource blockchain.MedianTimeSource) time.Time { // The timestamp for the block must not be before the median timestamp // of the last several blocks. Thus, choose the maximum between the // current time and one second after the past median time. The current @@ -622,7 +618,7 @@ func medianAdjustedTime(best *blockchain.BestState, timeSource blockchain.Median // Adjust by the amount requested from the command line argument. newTimestamp = newTimestamp.Add( - time.Duration(-cfg.MiningTimeOffset) * time.Second) + time.Duration(-g.miningTimeOffset) * time.Second) return newTimestamp } @@ -631,10 +627,10 @@ func medianAdjustedTime(best *blockchain.BestState, timeSource blockchain.Median // valid from the perspective of the mainchain (not necessarily // the mempool or block) before inserting into a tx tree. // If it fails the check, it returns false; otherwise true. -func maybeInsertStakeTx(bm *blockManager, stx *dcrutil.Tx, treeValid bool) bool { +func (g *BlkTmplGenerator) maybeInsertStakeTx(stx *dcrutil.Tx, treeValid bool) bool { missingInput := false - view, err := bm.cfg.Chain.FetchUtxoView(stx, treeValid) + view, err := g.chain.FetchUtxoView(stx, treeValid) if err != nil { minrLog.Warnf("Unable to fetch transaction store for "+ "stx %s: %v", stx.Hash(), err) @@ -673,19 +669,19 @@ func maybeInsertStakeTx(bm *blockManager, stx *dcrutil.Tx, treeValid bool) bool // work off of is present, it will return a copy of that template to pass to the // miner. // Safe for concurrent access. -func handleTooFewVoters(subsidyCache *standalone.SubsidyCache, nextHeight int64, miningAddress dcrutil.Address, bm *blockManager) (*BlockTemplate, error) { - timeSource := bm.cfg.TimeSource - stakeValidationHeight := bm.cfg.ChainParams.StakeValidationHeight +func (g *BlkTmplGenerator) handleTooFewVoters(subsidyCache *standalone.SubsidyCache, nextHeight int64, miningAddress dcrutil.Address) (*BlockTemplate, error) { + timeSource := g.timeSource + stakeValidationHeight := g.chainParams.StakeValidationHeight // Handle not enough voters being present if we're set to mine aggressively // (default behavior). - best := bm.cfg.Chain.BestSnapshot() - if nextHeight >= stakeValidationHeight && bm.AggressiveMining { + best := g.chain.BestSnapshot() + if nextHeight >= stakeValidationHeight && g.policy.AggressiveMining { // 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 we should have the option of reading some transactions // from this block, too. - topBlock, err := bm.cfg.Chain.BlockByHash(&best.Hash) + topBlock, err := g.chain.BlockByHash(&best.Hash) if err != nil { str := fmt.Sprintf("unable to get tip block %s", best.PrevHash) return nil, miningRuleError(ErrGetTopBlock, str) @@ -709,7 +705,7 @@ func handleTooFewVoters(subsidyCache *standalone.SubsidyCache, nextHeight int64, } coinbaseTx, err := createCoinbaseTx(subsidyCache, coinbaseScript, opReturnPkScript, topBlock.Height(), miningAddress, - tipHeader.Voters, bm.cfg.ChainParams) + tipHeader.Voters, g.chainParams) if err != nil { return nil, err } @@ -721,16 +717,16 @@ func handleTooFewVoters(subsidyCache *standalone.SubsidyCache, nextHeight int64, } // Set a fresh timestamp. - ts := medianAdjustedTime(best, timeSource) + ts := g.medianAdjustedTime(best, timeSource) 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.cfg.ChainParams.ReduceMinDifficulty { + if g.chainParams.ReduceMinDifficulty { parentHash := topBlock.MsgBlock().Header.PrevBlock requiredDifficulty, err := - bm.cfg.Chain.CalcNextRequiredDifficulty(&parentHash, ts) + g.chain.CalcNextRequiredDifficulty(&parentHash, ts) if err != nil { return nil, miningRuleError(ErrGettingDifficulty, err.Error()) @@ -753,7 +749,7 @@ func handleTooFewVoters(subsidyCache *standalone.SubsidyCache, nextHeight int64, // Calculate the merkle root depending on the result of the header // commitments agenda vote. prevHash := &tipHeader.PrevBlock - hdrCmtActive, err := bm.cfg.Chain.IsHeaderCommitmentsAgendaActive(prevHash) + hdrCmtActive, err := g.chain.IsHeaderCommitmentsAgendaActive(prevHash) if err != nil { return nil, err } @@ -766,7 +762,7 @@ func handleTooFewVoters(subsidyCache *standalone.SubsidyCache, nextHeight int64, if hdrCmtActive { // Load all of the previous output scripts the block references as // inputs since they are needed to create the filter commitment. - blockUtxos, err := bm.cfg.Chain.FetchUtxoViewParentTemplate(&block) + blockUtxos, err := g.chain.FetchUtxoViewParentTemplate(&block) if err != nil { str := fmt.Sprintf("failed to fetch inputs when making new "+ "block template: %v", err) @@ -786,7 +782,7 @@ func handleTooFewVoters(subsidyCache *standalone.SubsidyCache, nextHeight int64, // Make sure the block validates. btBlock := dcrutil.NewBlockDeepCopyCoinbase(&block) - err = bm.cfg.Chain.CheckConnectBlockTemplate(btBlock) + err = g.chain.CheckConnectBlockTemplate(btBlock) if err != nil { str := fmt.Sprintf("failed to check template: %v while "+ "constructing a new parent", err.Error()) @@ -796,7 +792,7 @@ func handleTooFewVoters(subsidyCache *standalone.SubsidyCache, nextHeight int64, return bt, nil } - bmgrLog.Debugf("Not enough voters on top block to generate " + + minrLog.Debugf("Not enough voters on top block to generate " + "new block template") return nil, nil @@ -811,14 +807,15 @@ func handleTooFewVoters(subsidyCache *standalone.SubsidyCache, nextHeight int64, // 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 - subsidyCache *standalone.SubsidyCache - chainParams *chaincfg.Params - chain *blockchain.BlockChain - blockManager *blockManager - timeSource blockchain.MedianTimeSource + policy *mining.Policy + txSource mining.TxSource + sigCache *txscript.SigCache + subsidyCache *standalone.SubsidyCache + chainParams *chaincfg.Params + chain *blockchain.BlockChain + blockManager *blockManager + timeSource blockchain.MedianTimeSource + miningTimeOffset int } // newBlkTmplGenerator returns a new block template generator for the given @@ -826,18 +823,19 @@ type BlkTmplGenerator struct { func newBlkTmplGenerator(policy *mining.Policy, txSource mining.TxSource, timeSource blockchain.MedianTimeSource, sigCache *txscript.SigCache, subsidyCache *standalone.SubsidyCache, chainParams *chaincfg.Params, - chain *blockchain.BlockChain, - blockManager *blockManager) *BlkTmplGenerator { + chain *blockchain.BlockChain, blockManager *blockManager, + miningTimeOffset int) *BlkTmplGenerator { return &BlkTmplGenerator{ - policy: policy, - txSource: txSource, - sigCache: sigCache, - subsidyCache: subsidyCache, - chainParams: chainParams, - chain: chain, - blockManager: blockManager, - timeSource: timeSource, + policy: policy, + txSource: txSource, + sigCache: sigCache, + subsidyCache: subsidyCache, + chainParams: chainParams, + chain: chain, + blockManager: blockManager, + timeSource: timeSource, + miningTimeOffset: miningTimeOffset, } } @@ -925,7 +923,7 @@ func newBlkTmplGenerator(policy *mining.Policy, txSource mining.TxSource, func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress dcrutil.Address) (*BlockTemplate, error) { // All transaction scripts are verified using the more strict standard // flags. - scriptFlags, err := standardScriptVerifyFlags(g.chain) + scriptFlags, err := g.policy.StandardVerifyFlags() if err != nil { return nil, err } @@ -959,7 +957,7 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress dcrutil.Address) (*Bloc if nextBlockHeight >= stakeValidationHeight { // Obtain the entire generation of blocks stemming from this parent. - children, err := g.blockManager.TipGeneration() + children, err := g.chain.TipGeneration() if err != nil { return nil, miningRuleError(ErrFailedToGetGeneration, err.Error()) } @@ -972,8 +970,8 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress dcrutil.Address) (*Bloc if len(eligibleParents) == 0 { minrLog.Debugf("Too few voters found on any HEAD block, " + "recycling a parent block to mine on") - return handleTooFewVoters(g.subsidyCache, nextBlockHeight, - payToAddress, g.blockManager) + return g.handleTooFewVoters(g.subsidyCache, nextBlockHeight, + payToAddress) } minrLog.Debugf("Found eligible parent %v with enough votes to build "+ @@ -1422,7 +1420,7 @@ mempoolLoop: if stake.IsSSGen(msgTx) { txCopy := dcrutil.NewTxDeepTxIns(msgTx) - if maybeInsertStakeTx(g.blockManager, txCopy, !knownDisapproved) { + if g.maybeInsertStakeTx(txCopy, !knownDisapproved) { vb := stake.SSGenVoteBits(txCopy.MsgTx()) voteBitsVoters = append(voteBitsVoters, vb) blockTxnsStake = append(blockTxnsStake, txCopy) @@ -1527,7 +1525,7 @@ mempoolLoop: // Quick check for difficulty here. if msgTx.TxOut[0].Value >= best.NextStakeDiff { txCopy := dcrutil.NewTxDeepTxIns(msgTx) - if maybeInsertStakeTx(g.blockManager, txCopy, !knownDisapproved) { + if g.maybeInsertStakeTx(txCopy, !knownDisapproved) { blockTxnsStake = append(blockTxnsStake, txCopy) freshStake++ } @@ -1550,7 +1548,7 @@ mempoolLoop: msgTx := tx.MsgTx() if tx.Tree() == wire.TxTreeStake && stake.IsSSRtx(msgTx) { txCopy := dcrutil.NewTxDeepTxIns(msgTx) - if maybeInsertStakeTx(g.blockManager, txCopy, !knownDisapproved) { + if g.maybeInsertStakeTx(txCopy, !knownDisapproved) { blockTxnsStake = append(blockTxnsStake, txCopy) revocations++ } @@ -1679,7 +1677,7 @@ mempoolLoop: // Calculate the required difficulty for the block. The timestamp // is potentially adjusted to ensure it comes after the median time of // the last several blocks per the chain consensus rules. - ts := medianAdjustedTime(best, g.timeSource) + ts := g.medianAdjustedTime(best, g.timeSource) reqDifficulty, err := g.chain.CalcNextRequiredDifficulty(&prevHash, ts) if err != nil { return nil, miningRuleError(ErrGettingDifficulty, err.Error()) @@ -1694,8 +1692,8 @@ mempoolLoop: voters < minimumVotesRequired { minrLog.Warnf("incongruent number of voters in mempool " + "vs mempool.voters; not enough voters found") - return handleTooFewVoters(g.subsidyCache, nextBlockHeight, payToAddress, - g.blockManager) + return g.handleTooFewVoters(g.subsidyCache, nextBlockHeight, + payToAddress) } // Correct transaction index fraud proofs for any transactions that @@ -1883,7 +1881,7 @@ func (g *BlkTmplGenerator) UpdateBlockTime(header *wire.BlockHeader) 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. - newTimestamp := medianAdjustedTime(g.chain.BestSnapshot(), g.timeSource) + newTimestamp := g.medianAdjustedTime(g.chain.BestSnapshot(), g.timeSource) header.Timestamp = newTimestamp // If running on a network that requires recalculating the difficulty, @@ -1900,6 +1898,12 @@ func (g *BlkTmplGenerator) UpdateBlockTime(header *wire.BlockHeader) error { return nil } +// UpdateBlockTime invokes `UpdateBlockTime` on the underlying +// BgBlkTmplGenerator. +func (g *BgBlkTmplGenerator) UpdateBlockTime(header *wire.BlockHeader) error { + return g.tg.UpdateBlockTime(header) +} + // regenEventType represents the type of a template regeneration event message. type regenEventType int @@ -2263,9 +2267,7 @@ func (g *BgBlkTmplGenerator) notifySubscribersHandler(ctx context.Context) { for { select { case templateNtfn := <-g.notifySubscribers: - if r := g.tg.blockManager.cfg.RpcServer(); r != nil { - r.ntfnMgr.NotifyWork(templateNtfn) - } + g.tg.blockManager.NotifyWork(templateNtfn) g.subscriptionMtx.Lock() for subscription := range g.subscriptions { diff --git a/mining/policy.go b/mining/policy.go index 99a7c9c8..dde962e4 100644 --- a/mining/policy.go +++ b/mining/policy.go @@ -1,5 +1,5 @@ // Copyright (c) 2014-2016 The btcsuite developers -// Copyright (c) 2016-2019 The Decred developers +// Copyright (c) 2016-2020 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -7,6 +7,7 @@ package mining import ( "github.com/decred/dcrd/dcrutil/v3" + "github.com/decred/dcrd/txscript/v3" "github.com/decred/dcrd/wire" ) @@ -37,6 +38,16 @@ type Policy struct { // required for a transaction to be treated as free for mining purposes // (block template generation). TxMinFreeFee dcrutil.Amount + + AggressiveMining bool + + // StandardVerifyFlags defines the function to retrieve the flags to + // use for verifying scripts for the block after the current best block. + // It must set the verification flags properly depending on the result + // of any agendas that affect them. + // + // This function must be safe for concurrent access. + StandardVerifyFlags func() (txscript.ScriptFlags, error) } // minInt is a helper function to return the minimum of two ints. This avoids diff --git a/rpcserver.go b/rpcserver.go index e1b0cd6a..cc78a318 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -95,6 +95,10 @@ const ( // sstxCommitmentString is the string to insert when a verbose // transaction output's pkscript type is a ticket commitment. sstxCommitmentString = "sstxcommitment" + + // merkleRootPairSize is the size in bytes of the merkle root + stake root + // of a block. + merkleRootPairSize = 64 ) var ( @@ -3313,7 +3317,7 @@ func handleGetWorkRequest(s *rpcServer) (interface{}, error) { // consensus rules. Note that the header is copied to avoid mutating the // shared block template. headerCopy := template.Block.Header - err := bgTmplGenerator.tg.UpdateBlockTime(&headerCopy) + err := bgTmplGenerator.UpdateBlockTime(&headerCopy) if err != nil { context := "Failed to update block time" return nil, rpcInternalError(err.Error(), context) diff --git a/server.go b/server.go index a463394b..09228fd5 100644 --- a/server.go +++ b/server.go @@ -3115,9 +3115,14 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, chainP BlockMaxSize: cfg.BlockMaxSize, BlockPrioritySize: cfg.BlockPrioritySize, TxMinFreeFee: cfg.minRelayTxFee, + AggressiveMining: !cfg.NonAggressive, + StandardVerifyFlags: func() (txscript.ScriptFlags, error) { + return standardScriptVerifyFlags(s.chain) + }, } tg := newBlkTmplGenerator(&policy, s.txMemPool, s.timeSource, s.sigCache, - s.subsidyCache, s.chainParams, s.chain, s.blockManager) + s.subsidyCache, s.chainParams, s.chain, s.blockManager, + cfg.MiningTimeOffset) // Create the background block template generator if the config has a // mining address.