diff --git a/internal/mining/bgblktmplgenerator.go b/internal/mining/bgblktmplgenerator.go new file mode 100644 index 0000000000..dbcf303807 --- /dev/null +++ b/internal/mining/bgblktmplgenerator.go @@ -0,0 +1,1438 @@ +// Copyright (c) 2019-2020 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package mining + +import ( + "context" + "math" + "math/rand" + "sort" + "sync" + "time" + + "github.com/decred/dcrd/blockchain/stake/v3" + "github.com/decred/dcrd/blockchain/v3" + "github.com/decred/dcrd/chaincfg/chainhash" + "github.com/decred/dcrd/dcrutil/v3" + "github.com/decred/dcrd/lru" + "github.com/decred/dcrd/wire" +) + +const ( + // minVotesTimeoutDuration is the duration that must elapse after a new tip + // block has been received before other variants that also extend the same + // parent and received later are considered for the base of new templates. + minVotesTimeoutDuration = time.Second * 3 + + // maxVoteTimeoutDuration is the duration elapsed after the minimum number + // of votes for a new tip block has been received that a new template with + // less than the maximum number of votes will be generated. + maxVoteTimeoutDuration = time.Millisecond * 2500 // 2.5 seconds + + // templateRegenSecs is the required number of seconds elapsed with + // incoming non vote transactions before template regeneration + // is required. + templateRegenSecs = 30 +) + +// regenEventType represents the type of a template regeneration event message. +type regenEventType int + +// Constants for the type of template regeneration event messages. +const ( + // rtReorgStarted indicates a chain reorganization has been started. + rtReorgStarted regenEventType = iota + + // rtReorgDone indicates a chain reorganization has completed. + rtReorgDone + + // rtBlockAccepted indicates a new block has been accepted to the block + // chain which does not necessarily mean it was added to the main chain. + // That case is rtBlockConnected. + rtBlockAccepted + + // rtBlockConnected indicates a new block has been connected to the main + // chain. + rtBlockConnected + + // rtBlockDisconnected indicates the current tip block of the best chain has + // been disconnected. + rtBlockDisconnected + + // rtVote indicates a new vote has been received. It applies to all votes + // and therefore may or may not be relevant. + rtVote + + // rtTemplateUpdated indicates the current template associated with the + // generator has been updated. + rtTemplateUpdated + + // rtForceRegen indicates the template should be regenerated even if + // it's not yet time for it to be regenerated. + rtForceRegen +) + +// TemplateUpdateReason represents the type of a reason why a template is +// being updated. +type TemplateUpdateReason int + +// Constants for the type of template update reasons. +const ( + // TURNewParent indicates the associated template has been updated because + // it builds on a new block as compared to the previous template. + TURNewParent TemplateUpdateReason = iota + + // TURNewVotes indicates the associated template has been updated because a + // new vote for the block it builds on has been received. + TURNewVotes + + // TURNewTxns indicates the associated template has been updated because new + // non-vote transactions are available and have potentially been included. + TURNewTxns + + // turUnknown indicates the associated template has either been updated due + // to an error or cleared for a chain reorg. It is only used internally to + // the background template generator. + turUnknown +) + +// TemplateNtfn represents a notification of a new template along with the +// reason it was generated. It is sent to subscribers on the channel obtained +// from the TemplateSubscription instance returned by Subscribe. +type TemplateNtfn struct { + Template *BlockTemplate + Reason TemplateUpdateReason +} + +// templateUpdate defines a type which is used to signal the regen event handler +// that a new template and relevant error have been associated with the +// generator. +type templateUpdate struct { + template *BlockTemplate + err error +} + +// regenEvent defines an event which will potentially result in regenerating a +// block template and consists of a regen event type as well as associated data +// that depends on the type as follows: +// - rtReorgStarted: nil +// - rtReorgDone: nil +// - rtBlockAccepted: *dcrutil.Block +// - rtBlockConnected: *dcrutil.Block +// - rtBlockDisconnected: *dcrutil.Block +// - rtVote: *dcrutil.Tx +// - rtTemplateUpdated: templateUpdate +type regenEvent struct { + reason regenEventType + value interface{} +} + +// BgBlkTmplGenerator provides facilities for asynchronously generating block +// templates in response to various relevant events and allowing clients to +// subscribe for updates when new templates are generated as well as access the +// most recently-generated template in a concurrency-safe manner. +// +// An example of some of the events that trigger a new block template to be +// generated are modifications to the current best chain, receiving relevant +// votes, and periodic timeouts to allow inclusion of new transactions. +// +// The templates are generated based on a given block template generator +// instance which itself is based on a given mining policy and transaction +// source. See the NewBlockTemplate method for a detailed description of how +// the block template is generated. +// +// The background generation makes use of three main goroutines -- a regen event +// queue to allow asynchronous non-blocking signalling, a regen event handler to +// process the aforementioned queue and react accordingly, and a subscriber +// notification controller. In addition, the templates themselves are generated +// in their own goroutines with a cancellable context. +// +// A high level overview of the semantics are as follows: +// - Ignore all vote handling when prior to stake validation height +// - Generate templates building on the current tip at startup with a fall back +// to generate a template on its parent if the current tip does not receive +// enough votes within a timeout +// - Continue monitoring for votes on any blocks that extend said parent to +// potentially switch to them and generate a template building on them when +// possible +// - Generate new templates building on new best chain tip blocks once they have +// received the minimum votes after a timeout to provide the additional votes +// an opportunity to propagate, except when it is an intermediate block in a +// chain reorganization +// - In the event the current tip fails to receive the minimum number of +// required votes, monitor side chain blocks which are siblings of it for +// votes in order to potentially switch to them and generate a template +// building on them when possible +// - Generate new templates on blocks disconnected from the best chain tip, +// except when it is an intermediate block in a chain reorganization +// - Generate new templates periodically when there are new regular transactions +// to include +// - Bias templates towards building on the first seen block when possible in +// order to prevent PoW miners from being able to gain an advantage through +// vote withholding +// - Schedule retries in the rare event template generation fails +// - Allow clients to subscribe for updates every time a new template is +// successfully generated along with a reason why it was generated +// - Provide direct access to the most-recently generated template +// - Block while generating new templates that will make the current template +// stale (e.g. new parent or new votes) +type BgBlkTmplGenerator struct { + wg sync.WaitGroup + quit chan struct{} + + // These fields are provided by the caller when the generator is created and + // are either independently safe for concurrent access or do not change after + // initialization. + // + // chain is the blockchain instance that is used to build the block and + // validate the block templates. + // + // tg is a block template generator instance that is used to actually create + // the block templates the background block template generator stores. + // + // allowUnsyncedMining indicates block templates should be created even when + // the chain is not fully synced. + // + // maxVotesPerBlock is the maximum number of votes per block and comes from + // the chain parameters. It is defined separately for convenience. + // + // minVotesRequired is the minimum number of votes required for a block to + // be built on. It is derived from the chain parameters and is defined + // separately for convenience. + chain *blockchain.BlockChain + tg *BlkTmplGenerator + allowUnsyncedMining bool + miningAddrs []dcrutil.Address + maxVotesPerBlock uint16 + minVotesRequired uint16 + + // These fields deal with providing a stream of template updates to + // subscribers. + // + // subscriptions tracks all template update subscriptions. It is protected + // for concurrent access by subscriptionMtx. + // + // notifySubscribers delivers template updates to the separate subscriber + // notification goroutine so it can in turn asynchronously deliver + // notifications to all subscribers. + subscriptionMtx sync.Mutex + subscriptions map[*TemplateSubscription]struct{} + notifySubscribers chan *TemplateNtfn + notifiedParents lru.Cache + + // These fields deal with the template regeneration event queue. This is + // implemented as a concurrent queue with immediate passthrough when + // possible to ensure the order of events is maintained and the related + // callbacks never block. + // + // queueRegenEvent either immediately forwards regen events to the + // regenEventMsgs channel when it would not block or adds the event to a + // queue that is processed asynchronously as soon as the receiver becomes + // available. + // + // regenEventMsgs delivers relevant regen events to which the generator + // reacts to the separate regen goroutine so it can in turn asynchronously + // process the events and regenerate templates as needed. + queueRegenEvent chan regenEvent + regenEventMsgs chan regenEvent + + // staleTemplateWg is used to allow template retrieval to block callers when + // a new template that will make the current template stale is being + // generated. Stale, in this context, means either the parent has changed + // or there are new votes available. + staleTemplateWg sync.WaitGroup + + // These fields track the current best template and are protected by the + // template mutex. The template will be nil when there is a template error + // set. + templateMtx sync.Mutex + template *BlockTemplate + templateReason TemplateUpdateReason + templateErr error + + // These fields are used to provide the ability to cancel a template that + // is in the process of being asynchronously generated in favor of + // generating a new one. + // + // cancelTemplate is a function which will cancel the current template that + // is in the process of being asynchronously generated. It will have no + // effect if no template generation is in progress. It is protected for + // concurrent access by cancelTemplateMtx. + cancelTemplateMtx sync.Mutex + cancelTemplate func() +} + +// NewBgBlkTmplGenerator initializes a background block template generator with +// the provided parameters. The returned instance must be started with the Run +// method to allowing processing. +func NewBgBlkTmplGenerator(tg *BlkTmplGenerator, addrs []dcrutil.Address, allowUnsynced bool) *BgBlkTmplGenerator { + return &BgBlkTmplGenerator{ + quit: make(chan struct{}), + chain: tg.chain, + tg: tg, + allowUnsyncedMining: allowUnsynced, + miningAddrs: addrs, + maxVotesPerBlock: tg.chainParams.TicketsPerBlock, + minVotesRequired: (tg.chainParams.TicketsPerBlock / 2) + 1, + subscriptions: make(map[*TemplateSubscription]struct{}), + notifySubscribers: make(chan *TemplateNtfn), + notifiedParents: lru.NewCache(3), + queueRegenEvent: make(chan regenEvent), + regenEventMsgs: make(chan regenEvent), + cancelTemplate: func() {}, + } +} + +// UpdateBlockTime updates the timestamp in the passed header to the current +// time while taking into account the median time of the last several blocks to +// ensure the new time is after that time per the chain 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 (g *BgBlkTmplGenerator) UpdateBlockTime(header *wire.BlockHeader) error { + return g.tg.UpdateBlockTime(header) +} + +// sendQueueRegenEvent sends the provided regen event on the internal queue +// regen event channel while respecting the quit channel. The allows orderly +// shutdown when the generator is shutdown. +func (g *BgBlkTmplGenerator) sendQueueRegenEvent(event regenEvent) { + select { + case g.queueRegenEvent <- event: + case <-g.quit: + } +} + +// setCurrentTemplate sets the current template and error associated with the +// background block template generator and notifies the regen event handler +// about the update. +// +// This function is safe for concurrent access. +func (g *BgBlkTmplGenerator) setCurrentTemplate(template *BlockTemplate, reason TemplateUpdateReason, err error) { + g.templateMtx.Lock() + g.template, g.templateReason, g.templateErr = template, reason, err + g.templateMtx.Unlock() + + tplUpdate := templateUpdate{template: template, err: err} + g.sendQueueRegenEvent(regenEvent{rtTemplateUpdated, tplUpdate}) +} + +// currentTemplate returns the current template associated with the background +// template generator along with the associated reason and error. +// +// NOTE: The returned template and block that it contains MUST be treated as +// immutable since they are shared by all callers. +// +// NOTE: The returned template might be nil even if there is no error. It is +// the responsibility of the caller to properly handle nil templates. +// +// This function differs from the exported version in that it also returns the +// reason associated with the template that is used in notifications. +// +// This function is safe for concurrent access. +func (g *BgBlkTmplGenerator) currentTemplate() (*BlockTemplate, TemplateUpdateReason, error) { + g.staleTemplateWg.Wait() + g.templateMtx.Lock() + template, reason, err := g.template, g.templateReason, g.templateErr + g.templateMtx.Unlock() + return template, reason, err +} + +// CurrentTemplate returns the current template associated with the background +// template generator along with any associated error. +// +// NOTE: The returned template and block that it contains MUST be treated as +// immutable since they are shared by all callers. +// +// NOTE: The returned template might be nil even if there is no error. It is +// the responsibility of the caller to properly handle nil templates. +// +// This function is safe for concurrent access. +func (g *BgBlkTmplGenerator) CurrentTemplate() (*BlockTemplate, error) { + template, _, err := g.currentTemplate() + return template, err +} + +// TemplateSubscription defines a subscription to receive block template updates +// from the background block template generator. The caller must call Stop on +// the subscription when it is no longer needed to free resources. +// +// NOTE: Notifications are dropped to make up for slow receivers to ensure +// notifications to other subscribers, as well as senders, are not blocked +// indefinitely. Since templates are typically only generated infrequently and +// receives must fall several templates behind before new ones are dropped, this +// should not affect callers in practice, however, if a caller wishes to +// guarantee that no templates are being dropped, they will need to ensure the +// channel is always processed quickly. +type TemplateSubscription struct { + g *BgBlkTmplGenerator + privC chan *TemplateNtfn +} + +// C returns a channel that produces a stream of block templates as each new +// template is generated. Successive calls to C return the same channel. +// +// NOTE: Notifications are dropped to make up for slow receivers. See the +// template subscription type documentation for more details. +func (s *TemplateSubscription) C() <-chan *TemplateNtfn { + return s.privC +} + +// Stop prevents any future template updates from being delivered and +// unsubscribes the associated subscription. +// +// NOTE: The channel is not closed to prevent a read from the channel succeeding +// incorrectly. +func (s *TemplateSubscription) Stop() { + s.g.subscriptionMtx.Lock() + delete(s.g.subscriptions, s) + s.g.subscriptionMtx.Unlock() +} + +// publishTemplateNtfn sends the provided template notification on the channel +// associated with the subscription. +func (s *TemplateSubscription) publishTemplateNtfn(templateNtfn *TemplateNtfn) { + // Make use of a non-blocking send along with the buffered channel to allow + // notifications to be dropped to make up for slow receivers. + select { + case s.privC <- templateNtfn: + default: + } +} + +// notifySubscribersHandler updates subscribers with newly created block +// templates. +// +// This must be run as a goroutine. +func (g *BgBlkTmplGenerator) notifySubscribersHandler(ctx context.Context) { + for { + select { + case templateNtfn := <-g.notifySubscribers: + g.tg.blockManager.NotifyWork(templateNtfn) + + g.subscriptionMtx.Lock() + for subscription := range g.subscriptions { + subscription.publishTemplateNtfn(templateNtfn) + } + g.subscriptionMtx.Unlock() + + case <-ctx.Done(): + g.wg.Done() + return + } + } +} + +// Subscribe subscribes a client for block template updates. The returned +// template subscription contains functions to retrieve a channel that produces +// the stream of block templates and to stop the stream when the caller no +// longer wishes to receive new templates. +// +// The current template associated with the background block template generator, +// if any, is immediately sent to the returned subscription stream. +func (g *BgBlkTmplGenerator) Subscribe() *TemplateSubscription { + // Create the subscription with a buffered channel that is large enough to + // handle twice the number of templates that can be induced due votes in + // order to provide a reasonable amount of buffering before dropping + // notifications due to a slow receiver. + maxVoteInducedRegens := g.maxVotesPerBlock - g.minVotesRequired + 1 + c := make(chan *TemplateNtfn, maxVoteInducedRegens*2) + subscription := &TemplateSubscription{ + g: g, + privC: c, + } + g.subscriptionMtx.Lock() + g.subscriptions[subscription] = struct{}{} + g.subscriptionMtx.Unlock() + + // Send existing valid template immediately. + template, reason, err := g.currentTemplate() + if err == nil && template != nil { + subscription.publishTemplateNtfn(&TemplateNtfn{template, reason}) + } + + return subscription +} + +// regenQueueHandler immediately forwards items from the regen event queue +// channel to the regen event messages channel when it would not block or adds +// the event to an internal queue to be processed as soon as the receiver +// becomes available. This ensures that queueing regen events never blocks +// despite how busy the regen handler might become during a burst of events. +// +// This must be run as a goroutine. +func (g *BgBlkTmplGenerator) regenQueueHandler(ctx context.Context) { + var q []regenEvent + var out, dequeue chan<- regenEvent = g.regenEventMsgs, nil + skipQueue := out + var next regenEvent + for { + select { + case n := <-g.queueRegenEvent: + // Either send to destination channel immediately when skipQueue is + // non-nil (queue is empty) and reader is ready, or append to the + // queue and send later. + select { + case skipQueue <- n: + default: + q = append(q, n) + dequeue = out + skipQueue = nil + next = q[0] + } + + case dequeue <- next: + copy(q, q[1:]) + q = q[:len(q)-1] + if len(q) == 0 { + dequeue = nil + skipQueue = out + } else { + next = q[0] + } + + case <-ctx.Done(): + g.wg.Done() + return + } + } +} + +// regenHandlerState houses the state used in the regen event handler goroutine. +// It is separated from the background template generator to ensure it is only +// available within the scope of the goroutine. +type regenHandlerState struct { + // isReorganizing indicates the chain is currently undergoing a + // reorganization and therefore the generator should not attempt to create + // new templates until the reorganization has completed. + isReorganizing bool + + // These fields are used to implement a periodic regeneration timeout that + // can be reset at any time without needing to create a new one and the + // associated extra garbage. + // + // regenTimer is an underlying timer that is used to implement the timeout. + // + // regenChanDrained indicates whether or not the channel for the regen timer + // has already been read and is used when resetting the timer to ensure the + // channel is drained when the timer is stopped as described in the timer + // documentation. + // + // lastGeneratedTime specifies the timestamp the current template was + // generated. + regenTimer *time.Timer + regenChanDrained bool + lastGeneratedTime int64 + + // These fields are used to control the various generation states when a new + // block that requires votes has been received. + // + // awaitingMinVotesHash is selectively set when a new tip block has been + // received that requires votes until the minimum number of required votes + // has been received. + // + // maxVotesTimeout is selectively enabled once the minimum number of + // required votes for the current tip block has been received and is + // disabled once the maximum number of votes has been received. This + // effectively sets a timeout to give the remaining votes an opportunity to + // propagate prior to forcing a template with less than the maximum number + // of votes. + awaitingMinVotesHash *chainhash.Hash + maxVotesTimeout <-chan time.Time + + // These fields are used to handle detection of side chain votes and + // potentially reorganizing the chain to a variant of the current tip when + // it is unable to obtain the minimum required votes. + // + // awaitingSideChainMinVotes houses the known blocks that build from the + // same parent as the current tip and will only be selectively populated + // when none of the current possible tips have the minimum number of + // required votes. + // + // trackSideChainsTimeout is selectively enabled when a new tip block has + // been received in order to give the minimum number of required votes + // needed to build a block template on it an opportunity to propagate before + // attempting to find any other variants that extend the same parent as the + // current tip with enough votes to force a reorganization. This ensures the + // first block that is seen is chosen to build templates on so long as it + // receives the minimum required votes in order to prevent PoW miners from + // being able to gain an advantage through vote withholding. It is disabled + // if the minimum number of votes is received prior to the timeout. + awaitingSideChainMinVotes map[chainhash.Hash]struct{} + trackSideChainsTimeout <-chan time.Time + + // failedGenRetryTimeout is selectively enabled in the rare case a template + // fails to generate so it can be regenerated again after a delay. A + // template should never fail to generate in practice, however, future code + // changes might break that assumption and thus it is important to handle + // the case properly. + failedGenRetryTimeout <-chan time.Time + + // These fields track the block and height that the next template to be + // generated will build on. This may not be the same as the current tip in + // the case it has not yet received the minimum number of required votes + // needed to build a template on it. + // + // baseBlockHash is the hash of the block the next template to be generated + // will build on. + // + // baseBlockHeight is the height of the block identified by the base block + // hash. + baseBlockHash chainhash.Hash + baseBlockHeight uint32 +} + +// makeRegenHandlerState returns a regen handler state that is ready to use. +func makeRegenHandlerState() regenHandlerState { + regenTimer := time.NewTimer(math.MaxInt64) + regenTimer.Stop() + return regenHandlerState{ + regenTimer: regenTimer, + regenChanDrained: true, + awaitingSideChainMinVotes: make(map[chainhash.Hash]struct{}), + } +} + +// stopRegenTimer stops the regen timer while ensuring to read from the timer's +// channel in the case the timer already expired which can happen due to the +// fact the stop happens in between channel reads. This behavior is well +// documented in the Timer docs. +// +// NOTE: This function must not be called concurrent with any other receives on +// the timer's channel. +func (state *regenHandlerState) stopRegenTimer() { + t := state.regenTimer + if !t.Stop() && !state.regenChanDrained { + <-t.C + } + state.regenChanDrained = true +} + +// resetRegenTimer resets the regen timer to the given duration while ensuring +// to read from the timer's channel in the case the timer already expired which +// can happen due to the fact the reset happens in between channel reads. This +// behavior is well documented in the Timer docs. +// +// NOTE: This function must not be called concurrent with any other receives on +// the timer's channel. +func (state *regenHandlerState) resetRegenTimer(d time.Duration) { + state.stopRegenTimer() + state.regenTimer.Reset(d) + state.regenChanDrained = false +} + +// clearSideChainTracking removes all tracking for minimum required votes on +// side chain blocks as well as clears the associated timeout that must +// transpire before said tracking is enabled. +func (state *regenHandlerState) clearSideChainTracking() { + for hash := range state.awaitingSideChainMinVotes { + delete(state.awaitingSideChainMinVotes, hash) + } + state.trackSideChainsTimeout = nil +} + +// genTemplateAsync cancels any asynchronous block template that is already +// currently being generated and launches a new goroutine to asynchronously +// generate a new one with the provided reason. It also handles updating the +// current template and error associated with the generator with the results in +// a concurrent safe fashion and, in the case a successful template is +// generated, notifies the subscription handler goroutine with the new template. +func (g *BgBlkTmplGenerator) genTemplateAsync(ctx context.Context, reason TemplateUpdateReason) { + // Cancel any other templates that might currently be in the process of + // being generated and create a new context that can be cancelled for the + // new template that is about to be generated. + g.cancelTemplateMtx.Lock() + g.cancelTemplate() + ctx, g.cancelTemplate = context.WithCancel(ctx) + g.cancelTemplateMtx.Unlock() + + // Ensure that attempts to retrieve the current template block until the + // new template is generated when it is because the parent has changed or + // new votes are available in order to avoid handing out a template that + // is guaranteed to be stale soon after. + blockRetrieval := reason == TURNewParent || reason == TURNewVotes + if blockRetrieval { + g.staleTemplateWg.Add(1) + } + go func(ctx context.Context, reason TemplateUpdateReason, blockRetrieval bool) { + if blockRetrieval { + defer g.staleTemplateWg.Done() + } + + // Pick a mining address at random and generate a block template that + // pays to it. + prng := rand.New(rand.NewSource(time.Now().Unix())) + payToAddr := g.miningAddrs[prng.Intn(len(g.miningAddrs))] + template, err := g.tg.NewBlockTemplate(payToAddr) + // NOTE: err is handled below. + if err != nil { + log.Tracef("NewBlockTemplate: %v", err) + } + + // Don't update the state or notify subscribers when the template + // generation was cancelled. + if ctx.Err() != nil { + return + } + + // Update the current template state with the results and notify + // subscribed clients of the new template so long as it's valid. + if err != nil { + reason = turUnknown + } + g.setCurrentTemplate(template, reason, err) + if err == nil && template != nil { + // It is possible for a new vote to show up while the template for + // a new parent is still being generated which causes that template + // to be canceled in favor of the the new one with the vote. So, + // ensure the first notification sent for a new parent has that + // reason. + header := &template.Block.Header + if reason == TURNewVotes { + if !g.notifiedParents.Contains(header.PrevBlock) { + reason = TURNewParent + } + } + if reason == TURNewParent { + g.notifiedParents.Add(header.PrevBlock) + } + + // Ensure the goroutine exits cleanly during shutdown. + select { + case <-ctx.Done(): + return + + case g.notifySubscribers <- &TemplateNtfn{template, reason}: + } + } + }(ctx, reason, blockRetrieval) +} + +// curTplHasNumVotes returns whether or not the current template is valid, +// builds on the provided hash, and contains the specified number of votes. +func (g *BgBlkTmplGenerator) curTplHasNumVotes(votedOnHash *chainhash.Hash, numVotes uint16) bool { + g.templateMtx.Lock() + template, err := g.template, g.templateErr + g.templateMtx.Unlock() + if template == nil || err != nil { + return false + } + if template.Block.Header.PrevBlock != *votedOnHash { + return false + } + return template.Block.Header.Voters == numVotes +} + +// numVotesForBlock returns the number of votes on the provided block hash that +// are known. +func (g *BgBlkTmplGenerator) numVotesForBlock(votedOnBlock *chainhash.Hash) uint16 { + return uint16(len(g.tg.txSource.VoteHashesForBlock(votedOnBlock))) +} + +// handleBlockConnected handles the rtBlockConnected event by either immediately +// generating a new template building on the block when it will still be prior +// to stake validation height or selectively setting up timeouts to give the +// votes a chance to propagate once the template will be at or after stake +// validation height. +// +// This function is only intended for use by the regen handler goroutine. +func (g *BgBlkTmplGenerator) handleBlockConnected(ctx context.Context, state *regenHandlerState, block *dcrutil.Block, chainTip *blockchain.BestState) { + // Clear all vote tracking when the current chain tip changes. + state.awaitingMinVotesHash = nil + state.clearSideChainTracking() + + // Nothing more to do if the connected block is not the current chain tip. + // This can happen in rare cases such as if more than one new block shows up + // while generating a template. Due to the requirement for votes later in + // the chain, it should almost never happen in practice once the chain has + // progressed that far, however, it is required for correctness. It is also + // worth noting that it happens more frequently earlier in the chain before + // voting starts, particularly in simulation networks with low difficulty. + blockHeight := block.MsgBlock().Header.Height + blockHash := block.Hash() + if int64(blockHeight) != chainTip.Height || *blockHash != chainTip.Hash { + return + } + + // Generate a new template immediately when it will be prior to stake + // validation height which means no votes are required. + newTemplateHeight := blockHeight + 1 + if newTemplateHeight < uint32(g.tg.chainParams.StakeValidationHeight) { + state.stopRegenTimer() + state.failedGenRetryTimeout = nil + state.baseBlockHash = *blockHash + state.baseBlockHeight = blockHeight + g.genTemplateAsync(ctx, TURNewParent) + return + } + + // At this point the template will be at or after stake validation height, + // and therefore requires the inclusion of votes on the previous block to be + // valid. + + // Generate a new template immediately when the maximum number of votes + // for the block are already known. + numVotes := g.numVotesForBlock(blockHash) + if numVotes >= g.maxVotesPerBlock { + state.stopRegenTimer() + state.failedGenRetryTimeout = nil + state.baseBlockHash = *blockHash + state.baseBlockHeight = blockHeight + g.genTemplateAsync(ctx, TURNewParent) + return + } + + // Update the state so the next template generated will build on the block + // and set a timeout to give the remaining votes an opportunity to propagate + // when the minimum number of required votes for the block are already + // known. This provides a balance between preferring to generate block + // templates with max votes and not waiting too long before starting work on + // the next block. + if numVotes >= g.minVotesRequired { + state.stopRegenTimer() + state.failedGenRetryTimeout = nil + state.baseBlockHash = *blockHash + state.baseBlockHeight = blockHeight + state.maxVotesTimeout = time.After(maxVoteTimeoutDuration) + return + } + + // Mark the state as waiting for the minimum number of required votes needed + // to build a template on the block to be received and set a timeout to give + // them an opportunity to propagate before attempting to find any other + // variants that extend the same parent with enough votes to force a + // reorganization. This ensures the first block that is seen is chosen to + // build templates on so long as it receives the minimum required votes in + // order to prevent PoW miners from being able to gain an advantage through + // vote withholding. + // + // Also, the regen timer for the current template is stopped since chances + // are high that the votes will be received and it is ideal to avoid + // regenerating a template that will likely be stale shortly. The regen + // timer is reset after the timeout if needed. + state.stopRegenTimer() + state.awaitingMinVotesHash = blockHash + state.trackSideChainsTimeout = time.After(minVotesTimeoutDuration) +} + +// handleBlockDisconnected handles the rtBlockDisconnected event by immediately +// generating a new template based on the new tip since votes for it are +// either necessarily already known due to being included in the block being +// disconnected or not required due to moving before stake validation height. +// +// This function is only intended for use by the regen handler goroutine. +func (g *BgBlkTmplGenerator) handleBlockDisconnected(ctx context.Context, state *regenHandlerState, block *dcrutil.Block, chainTip *blockchain.BestState) { + // Clear all vote tracking when the current chain tip changes. + state.awaitingMinVotesHash = nil + state.clearSideChainTracking() + + // Nothing more to do if the current chain tip is not the block prior to the + // block that was disconnected. This can happen in rare cases such as when + // forcing disconnects via block invalidation. In practice, disconnects + // happen as a result of chain reorganizations and thus this code will not + // be executed, however, it is required for correctness. + prevHeight := block.MsgBlock().Header.Height - 1 + prevHash := &block.MsgBlock().Header.PrevBlock + if int64(prevHeight) != chainTip.Height || *prevHash != chainTip.Hash { + return + } + + // NOTE: The block being disconnected necessarily has votes for the block + // that is becoming the new tip and they should ideally be extracted here to + // ensure they are available for use when building the template. However, + // the underlying template generator currently relies on pulling the votes + // out of the mempool and performs this task itself. In the future, the + // template generator should ideally accept the votes to include directly. + + // Generate a new template building on the new tip. + state.stopRegenTimer() + state.failedGenRetryTimeout = nil + state.baseBlockHash = *prevHash + state.baseBlockHeight = prevHeight + g.genTemplateAsync(ctx, TURNewParent) +} + +// handleBlockAccepted handles the rtBlockAccepted event by establishing vote +// tracking for the block when it is a variant that extends the same parent as +// the current tip, the current tip does not have the minimum number of required +// votes, and the initial timeout to provide them an opportunity to propagate +// has already expired. +// +// This function is only intended for use by the regen handler goroutine. +func (g *BgBlkTmplGenerator) handleBlockAccepted(_ context.Context, state *regenHandlerState, block *dcrutil.Block, chainTip *blockchain.BestState) { + // Ignore side chain blocks while still waiting for the side chain tracking + // timeout to expire. This provides a bias towards the first block that is + // seen in order to prevent PoW miners from being able to gain an advantage + // through vote withholding. + if state.trackSideChainsTimeout != nil { + return + } + + // Ignore side chain blocks when building on it would produce a block prior + // to stake validation height which means no votes are required and + // therefore no additional handling is necessary. + blockHeight := block.MsgBlock().Header.Height + newTemplateHeight := blockHeight + 1 + if newTemplateHeight < uint32(g.tg.chainParams.StakeValidationHeight) { + return + } + + // Ignore side chain blocks when the current tip already has enough votes + // for a template to be built on it. This ensures the first block that is + // seen is chosen to build templates on so long as it receives the minimum + // required votes in order to prevent PoW miners from being able to gain an + // advantage through vote withholding. + if state.awaitingMinVotesHash == nil { + return + } + + // Ignore blocks that are prior to the current tip. + if blockHeight < uint32(chainTip.Height) { + return + } + + // Ignore main chain tip block since it is handled by the connect path. + blockHash := block.Hash() + if *blockHash == chainTip.Hash { + return + } + + // Ignore side chain blocks when the current template is already building on + // the current tip or the accepted block is not a sibling of the current + // best chain tip. + alreadyBuildingOnCurTip := state.baseBlockHash == chainTip.Hash + acceptedPrevHash := &block.MsgBlock().Header.PrevBlock + if alreadyBuildingOnCurTip || *acceptedPrevHash != chainTip.PrevHash { + return + } + + // Setup tracking for votes on the block. + state.awaitingSideChainMinVotes[*blockHash] = struct{}{} +} + +// handleVote handles the rtVote event by determining if the vote is for a block +// the current state is monitoring and reacting accordingly. At a high level, +// this entails either establishing a timeout once the minimum number of +// required votes for the current tip have been received to provide the +// remaining votes an opportunity to propagate, regenerating the current +// template as a result of the vote, or potentially reorganizing the chain to a +// new tip that has enough votes in the case the current tip is unable to obtain +// the required votes. +// +// This function is only intended for use by the regen handler goroutine. +func (g *BgBlkTmplGenerator) handleVote(ctx context.Context, state *regenHandlerState, voteTx *dcrutil.Tx, chainTip *blockchain.BestState) { + votedOnHash, _ := stake.SSGenBlockVotedOn(voteTx.MsgTx()) + + // The awaiting min votes hash is selectively set once a block is connected + // such that a new template that builds on it will be at or after stake + // validation height until the minimum number of votes required to build a + // template are received. + // + // Update the state so the next template generated will build on the current + // tip once at least the minimum number of required votes for it has been + // received and either set a timeout to give the remaining votes an + // opportunity to propagate if the maximum number of votes is not already + // known or generate a new template immediately when they are. This + // provides a balance between preferring to generate block templates with + // max votes and not waiting too long before starting work on the next + // block. + minVotesHash := state.awaitingMinVotesHash + if minVotesHash != nil && votedOnHash == *minVotesHash { + numVotes := g.numVotesForBlock(minVotesHash) + log.Debugf("Received vote %s for tip block %s (%d total)", + voteTx.Hash(), minVotesHash, numVotes) + if numVotes >= g.minVotesRequired { + // Ensure the next template generated builds on the tip and clear + // all vote tracking to lock the current tip in now that it + // has the minimum required votes. + state.stopRegenTimer() + state.failedGenRetryTimeout = nil + state.baseBlockHash = *minVotesHash + state.baseBlockHeight = uint32(chainTip.Height) + state.awaitingMinVotesHash = nil + state.clearSideChainTracking() + + // Generate a new template immediately when the maximum number of + // votes for the block are already known. + if numVotes >= g.maxVotesPerBlock { + g.genTemplateAsync(ctx, TURNewParent) + return + } + + // Set a timeout to give the remaining votes an opportunity to + // propagate. + state.maxVotesTimeout = time.After(maxVoteTimeoutDuration) + } + return + } + + // Generate a template on new votes for the block the current state is + // configured to build the next block template on when either the maximum + // number of votes is received for it or once the minimum number of required + // votes has been received and the propagation delay timeout that is started + // upon receipt of said minimum votes has expired. + // + // Note that the base block hash is only updated to the current tip once it + // has received the minimum number of required votes, so this will continue + // to detect votes for the parent of the current tip prior to the point the + // new tip has received enough votes. + // + // This ensures new templates that include the new votes are generated + // immediately upon receiving the maximum number of votes as well as any + // additional votes that arrive after the initial timeout. + if votedOnHash == state.baseBlockHash { + // Avoid regenerating the current template if it is already building on + // the expected block and already has the maximum number of votes. + if g.curTplHasNumVotes(&votedOnHash, g.maxVotesPerBlock) { + state.maxVotesTimeout = nil + return + } + + numVotes := g.numVotesForBlock(&votedOnHash) + log.Debugf("Received vote %s for current template %s (%d total)", + voteTx.Hash(), votedOnHash, numVotes) + if numVotes >= g.maxVotesPerBlock || state.maxVotesTimeout == nil { + // The template needs to be updated due to a new parent the first + // time it is generated and due to new votes on subsequent votes. + // The max votes timeout is only non-nil before the first time it is + // generated. + tplUpdateReason := TURNewVotes + if state.maxVotesTimeout != nil { + tplUpdateReason = TURNewParent + } + + // Cancel the max votes timeout (if set). + state.maxVotesTimeout = nil + + state.stopRegenTimer() + state.failedGenRetryTimeout = nil + g.genTemplateAsync(ctx, tplUpdateReason) + return + } + } + + // Reorganize to an alternative chain tip when it receives at least the + // minimum required number of votes in the case the current chain tip does + // not receive the minimum number of required votes within an initial + // timeout period. + // + // Note that the potential side chain blocks to consider are only populated + // in the aforementioned case. + if _, ok := state.awaitingSideChainMinVotes[votedOnHash]; ok { + numVotes := g.numVotesForBlock(&votedOnHash) + log.Debugf("Received vote %s for side chain block %s (%d total)", + voteTx.Hash(), votedOnHash, numVotes) + if numVotes >= g.minVotesRequired { + err := g.chain.ForceHeadReorganization(chainTip.Hash, votedOnHash) + if err != nil { + return + } + + // Prevent votes on other tip candidates from causing reorg again + // since the new chain tip has enough votes. + state.clearSideChainTracking() + return + } + } +} + +// handleTemplateUpdate handles the rtTemplateUpdate event by updating the state +// accordingly. +// +// This function is only intended for use by the regen handler goroutine. +func (g *BgBlkTmplGenerator) handleTemplateUpdate(state *regenHandlerState, tplUpdate templateUpdate) { + // Schedule a template regen if it failed to generate for some reason. This + // should be exceedingly rare in practice. + if tplUpdate.err != nil && state.failedGenRetryTimeout == nil { + state.failedGenRetryTimeout = time.After(time.Second) + return + } + if tplUpdate.template == nil { + return + } + + // Ensure the base block details match the template. + state.baseBlockHash = tplUpdate.template.Block.Header.PrevBlock + state.baseBlockHeight = tplUpdate.template.Block.Header.Height - 1 + + // Update the state related to template regeneration due to new regular + // transactions. + state.lastGeneratedTime = time.Now().Unix() + state.resetRegenTimer(templateRegenSecs * time.Second) +} + +// handleForceRegen handles the rtForceRegen event by initiating the generation +// of a new template. +// +// This function is only intended for use by the regen handler goroutine. +func (g *BgBlkTmplGenerator) handleForceRegen(ctx context.Context, state *regenHandlerState) { + // Ignore requests to force regeneration if the minimum amount of votes + // has been received and it's just waiting for the last ones to arrive. + // The template will be regenerated shortly in that case. + if state.maxVotesTimeout != nil { + return + } + + state.stopRegenTimer() + state.failedGenRetryTimeout = nil + g.genTemplateAsync(ctx, turUnknown) +} + +// handleRegenEvent handles all regen events by determining the event reason and +// reacting accordingly. For example, it calls the appropriate associated event +// handler for the events that have one and prevents templates from being +// generating in the middle of reorgs. +// +// This function is only intended for use by the regen handler goroutine. +func (g *BgBlkTmplGenerator) handleRegenEvent(ctx context.Context, state *regenHandlerState, event regenEvent) { + // Handle chain reorg messages up front since all of the following logic + // only applies when not in the middle of reorganizing. + switch event.reason { + case rtReorgStarted: + // Ensure that attempts to retrieve the current template block until the + // new template after the reorg is generated. + g.staleTemplateWg.Add(1) + + // Mark the state as reorganizing. + state.isReorganizing = true + + // Stop all timeouts and clear all vote tracking. + state.stopRegenTimer() + state.failedGenRetryTimeout = nil + state.awaitingMinVotesHash = nil + state.maxVotesTimeout = nil + state.clearSideChainTracking() + + // Clear the current template and associated base block for the next + // generated template. + g.setCurrentTemplate(nil, turUnknown, nil) + state.baseBlockHash = zeroHash + state.baseBlockHeight = 0 + return + + case rtReorgDone: + state.isReorganizing = false + + // Treat the tip block as if it was just connected when a reorganize + // finishes so the existing code paths are run. + // + // An error should be impossible here since the request is for the block + // the chain believes is the current tip which means it must exist. + chainTip := g.chain.BestSnapshot() + tipBlock, err := g.chain.BlockByHash(&chainTip.Hash) + if err != nil { + g.setCurrentTemplate(nil, turUnknown, err) + } else { + g.handleBlockConnected(ctx, state, tipBlock, chainTip) + } + + g.staleTemplateWg.Done() + return + } + + // Do not generate block templates when the chain is in the middle of + // reorganizing. + if state.isReorganizing { + return + } + + // Do not generate block templates when the chain is not synced unless + // specifically requested to. + if !g.allowUnsyncedMining && !g.tg.blockManager.IsCurrent() { + return + } + + chainTip := g.chain.BestSnapshot() + switch event.reason { + case rtBlockConnected: + block := event.value.(*dcrutil.Block) + g.handleBlockConnected(ctx, state, block, chainTip) + + case rtBlockDisconnected: + block := event.value.(*dcrutil.Block) + g.handleBlockDisconnected(ctx, state, block, chainTip) + + case rtBlockAccepted: + block := event.value.(*dcrutil.Block) + g.handleBlockAccepted(ctx, state, block, chainTip) + + case rtVote: + voteTx := event.value.(*dcrutil.Tx) + g.handleVote(ctx, state, voteTx, chainTip) + + case rtTemplateUpdated: + tplUpdate := event.value.(templateUpdate) + g.handleTemplateUpdate(state, tplUpdate) + + case rtForceRegen: + g.handleForceRegen(ctx, state) + } +} + +// tipSiblingsSortedByVotes returns all blocks other than the current tip block +// that also extend its parent sorted by the number of votes each has in +// descending order. +func (g *BgBlkTmplGenerator) tipSiblingsSortedByVotes(state *regenHandlerState) []*blockWithNumVotes { + // Obtain all of the current blocks that extend the same parent as the + // current tip. The error is ignored here because it is deprecated. + generation, _ := g.chain.TipGeneration() + + // Nothing else to consider if there is only a single block which will be + // the current tip itself. + if len(generation) <= 1 { + return nil + } + + siblings := make([]*blockWithNumVotes, 0, len(generation)-1) + for i := range generation { + hash := &generation[i] + if *hash == *state.awaitingMinVotesHash { + continue + } + + numVotes := g.numVotesForBlock(hash) + siblings = append(siblings, &blockWithNumVotes{ + Hash: *hash, + NumVotes: numVotes, + }) + } + sort.Sort(sort.Reverse(byNumberOfVotes(siblings))) + return siblings +} + +// handleTrackSideChainsTimeout handles potentially reorganizing the chain to a +// side chain block with the most votes in the case the minimum number of +// votes needed to build a block template on the current tip have not been +// received within a certain timeout. +// +// It also doubles to reset the regen timer for the current template in the case +// no validate candidates are found since it is disabled when setting up this +// timeout to prevent creating new templates that would very likely be stale +// soon after. +// +// This function is only intended for use by the regen handler goroutine. +func (g *BgBlkTmplGenerator) handleTrackSideChainsTimeout(ctx context.Context, state *regenHandlerState) { + // Don't allow side chain variants to override the current tip when it + // already has the minimum required votes. + if state.awaitingMinVotesHash == nil { + return + } + + // Reorganize the chain to a valid sibling of the current tip that has at + // least the minimum number of required votes while preferring the most + // votes. + // + // Also, while looping, add each tip the map of side chain blocks to monitor + // for votes in the event there are not currently any eligible candidates + // since they may become eligible as votes arrive. + sortedSiblings := g.tipSiblingsSortedByVotes(state) + for _, sibling := range sortedSiblings { + if sibling.NumVotes >= g.minVotesRequired { + err := g.chain.ForceHeadReorganization(*state.awaitingMinVotesHash, + sibling.Hash) + if err != nil { + // Try the next block in the case of failure to reorg. + continue + } + + // Prevent votes on other tip candidates from causing reorg again + // since the new chain tip has enough votes. The reorg event clears + // the state, but, since there is a backing queue for the events, + // and the reorg itself might haven taken a bit of time, it could + // allow new side chain blocks or votes on existing ones in before + // the reorg events are processed. Thus, update the state to + // indicate the next template is to be built on the new tip to + // prevent any possible logic races. + state.awaitingMinVotesHash = nil + state.clearSideChainTracking() + state.stopRegenTimer() + state.failedGenRetryTimeout = nil + state.baseBlockHash = sibling.Hash + return + } + + state.awaitingSideChainMinVotes[sibling.Hash] = struct{}{} + } + + // Generate a new template building on the parent of the current tip when + // there is not already an existing template and the initial timeout has + // elapsed upon receiving the new tip without receiving votes for it. There + // will typically only not be an existing template when the generator is + // first instantiated and after a chain reorganization. + if state.baseBlockHash == zeroHash { + chainTip := g.chain.BestSnapshot() + state.failedGenRetryTimeout = nil + state.baseBlockHash = chainTip.PrevHash + state.baseBlockHeight = uint32(chainTip.Height - 1) + g.genTemplateAsync(ctx, TURNewParent) + return + } + + // At this point, no viable candidates to change the current template were + // found, so reset the regen timer for the current template. + state.resetRegenTimer(templateRegenSecs * time.Second) +} + +// regenHandler is the main workhorse for generating new templates in response +// to regen events and also handles generating a new template during initial +// startup. +// +// This must be run as a goroutine. +func (g *BgBlkTmplGenerator) regenHandler(ctx context.Context) { + // Treat the tip block as if it was just connected when starting up so the + // existing code paths are run. + tipBlock, err := g.chain.BlockByHash(&g.chain.BestSnapshot().Hash) + if err != nil { + g.setCurrentTemplate(nil, turUnknown, err) + } else { + select { + case <-ctx.Done(): + g.wg.Done() + return + case g.queueRegenEvent <- regenEvent{rtBlockConnected, tipBlock}: + } + } + + state := makeRegenHandlerState() + for { + select { + case event := <-g.regenEventMsgs: + g.handleRegenEvent(ctx, &state, event) + + // This timeout is selectively enabled once the minimum number of + // required votes has been received in order to give the remaining votes + // an opportunity to propagate. It is disabled if the remaining votes + // are received prior to the timeout. + case <-state.maxVotesTimeout: + state.maxVotesTimeout = nil + g.genTemplateAsync(ctx, TURNewParent) + + // This timeout is selectively enabled when a new block is connected in + // order to give the minimum number of required votes needed to build a + // block template on it an opportunity to propagate before attempting to + // find any other variants that extend the same parent as the current + // tip with enough votes to force a reorganization. This ensures the + // first block that is seen is chosen to build templates on so long as + // it receives the minimum required votes in order to prevent PoW miners + // from being able to gain an advantage through vote withholding. It is + // disabled if the minimum number of votes is received prior to the + // timeout. + case <-state.trackSideChainsTimeout: + state.trackSideChainsTimeout = nil + g.handleTrackSideChainsTimeout(ctx, &state) + + // This timeout is selectively enabled once a template has been + // generated in order to allow the template to be periodically + // regenerated with new transactions. Note that votes have special + // handling as described above. + case <-state.regenTimer.C: + // Mark the timer's channel as having been drained so the timer can + // safely be reset. + state.regenChanDrained = true + + // Generate a new template when there are new transactions + // available. + if g.tg.txSource.LastUpdated().Unix() > state.lastGeneratedTime { + state.failedGenRetryTimeout = nil + g.genTemplateAsync(ctx, TURNewTxns) + continue + } + + // There are no new transactions to include and the initial timeout + // has been triggered, so reset the timer to check again in one + // second. + state.resetRegenTimer(time.Second) + + // This timeout is selectively enabled in the rare case a template fails + // to generate and disabled prior to attempts at generating a new one. + case <-state.failedGenRetryTimeout: + state.failedGenRetryTimeout = nil + g.genTemplateAsync(ctx, TURNewParent) + + case <-ctx.Done(): + g.wg.Done() + return + } + } +} + +// ChainReorgStarted informs the background block template generator that a +// chain reorganization has started. It is caller's responsibility to ensure +// this is only invoked as described. +func (g *BgBlkTmplGenerator) ChainReorgStarted() { + g.sendQueueRegenEvent(regenEvent{rtReorgStarted, nil}) +} + +// ChainReorgDone informs the background block template generator that a chain +// reorganization has completed. It is caller's responsibility to ensure this +// is only invoked as described. +func (g *BgBlkTmplGenerator) ChainReorgDone() { + g.sendQueueRegenEvent(regenEvent{rtReorgDone, nil}) +} + +// BlockAccepted informs the background block template generator that a block +// has been accepted to the block chain. It is caller's responsibility to +// ensure this is only invoked as described. +// +// This function is safe for concurrent access. +func (g *BgBlkTmplGenerator) BlockAccepted(block *dcrutil.Block) { + g.sendQueueRegenEvent(regenEvent{rtBlockAccepted, block}) +} + +// BlockConnected informs the background block template generator that a block +// has been connected to the main chain. It is caller's responsibility to +// ensure this is only invoked as described. +// +// This function is safe for concurrent access. +func (g *BgBlkTmplGenerator) BlockConnected(block *dcrutil.Block) { + g.sendQueueRegenEvent(regenEvent{rtBlockConnected, block}) +} + +// BlockDisconnected informs the background block template generator that a +// block has been disconnected from the main chain. It is caller's +// responsibility to ensure this is only invoked as described. +// +// This function is safe for concurrent access. +func (g *BgBlkTmplGenerator) BlockDisconnected(block *dcrutil.Block) { + g.sendQueueRegenEvent(regenEvent{rtBlockDisconnected, block}) +} + +// VoteReceived informs the background block template generator that a new vote +// has been received. It is the caller's responsibility to ensure this is only +// invoked with valid votes. +// +// This function is safe for concurrent access. +func (g *BgBlkTmplGenerator) VoteReceived(tx *dcrutil.Tx) { + g.sendQueueRegenEvent(regenEvent{rtVote, tx}) +} + +// ForceRegen asks the background block template generator to generate a new +// template, independently of most of its internal timers. +// +// Note that there is no guarantee on whether a new template will actually be +// generated or when. This function does _not_ block until a new template is +// generated. +// +// This function is safe for concurrent access. +func (g *BgBlkTmplGenerator) ForceRegen() { + g.sendQueueRegenEvent(regenEvent{rtForceRegen, nil}) +} + +// Run starts the background block template generator and all other goroutines +// necessary for it to function properly and blocks until the provided context +// is cancelled. +func (g *BgBlkTmplGenerator) Run(ctx context.Context) { + g.wg.Add(4) + go g.regenQueueHandler(ctx) + go g.regenHandler(ctx) + go g.notifySubscribersHandler(ctx) + go func() { + <-ctx.Done() + close(g.quit) + g.wg.Done() + }() + g.wg.Wait() +} diff --git a/internal/mining/mining.go b/internal/mining/mining.go index 390d749fc3..7da3a843b2 100644 --- a/internal/mining/mining.go +++ b/internal/mining/mining.go @@ -7,13 +7,10 @@ package mining import ( "container/heap" - "context" "encoding/binary" "fmt" "math" - "math/rand" "sort" - "sync" "time" "github.com/decred/dcrd/blockchain/stake/v3" @@ -23,7 +20,6 @@ import ( "github.com/decred/dcrd/chaincfg/v3" "github.com/decred/dcrd/dcrutil/v3" "github.com/decred/dcrd/gcs/v2/blockcf2" - "github.com/decred/dcrd/lru" "github.com/decred/dcrd/txscript/v3" "github.com/decred/dcrd/wire" ) @@ -114,21 +110,6 @@ const ( // kilobyte is the size of a kilobyte. kilobyte = 1000 - - // minVotesTimeoutDuration is the duration that must elapse after a new tip - // block has been received before other variants that also extend the same - // parent and received later are considered for the base of new templates. - minVotesTimeoutDuration = time.Second * 3 - - // maxVoteTimeoutDuration is the duration elapsed after the minimum number - // of votes for a new tip block has been received that a new template with - // less than the maximum number of votes will be generated. - maxVoteTimeoutDuration = time.Millisecond * 2500 // 2.5 seconds - - // templateRegenSecs is the required number of seconds elapsed with - // incoming non vote transactions before template regeneration - // is required. - templateRegenSecs = 30 ) // containsTx is a helper function that checks to see if a list of transactions @@ -2064,1403 +2045,3 @@ func (g *BlkTmplGenerator) UpdateBlockTime(header *wire.BlockHeader) error { return nil } - -// regenEventType represents the type of a template regeneration event message. -type regenEventType int - -// Constants for the type of template regeneration event messages. -const ( - // rtReorgStarted indicates a chain reorganization has been started. - rtReorgStarted regenEventType = iota - - // rtReorgDone indicates a chain reorganization has completed. - rtReorgDone - - // rtBlockAccepted indicates a new block has been accepted to the block - // chain which does not necessarily mean it was added to the main chain. - // That case is rtBlockConnected. - rtBlockAccepted - - // rtBlockConnected indicates a new block has been connected to the main - // chain. - rtBlockConnected - - // rtBlockDisconnected indicates the current tip block of the best chain has - // been disconnected. - rtBlockDisconnected - - // rtVote indicates a new vote has been received. It applies to all votes - // and therefore may or may not be relevant. - rtVote - - // rtTemplateUpdated indicates the current template associated with the - // generator has been updated. - rtTemplateUpdated - - // rtForceRegen indicates the template should be regenerated even if - // it's not yet time for it to be regenerated. - rtForceRegen -) - -// TemplateUpdateReason represents the type of a reason why a template is -// being updated. -type TemplateUpdateReason int - -// Constants for the type of template update reasons. -const ( - // TURNewParent indicates the associated template has been updated because - // it builds on a new block as compared to the previous template. - TURNewParent TemplateUpdateReason = iota - - // TURNewVotes indicates the associated template has been updated because a - // new vote for the block it builds on has been received. - TURNewVotes - - // TURNewTxns indicates the associated template has been updated because new - // non-vote transactions are available and have potentially been included. - TURNewTxns - - // turUnknown indicates the associated template has either been updated due - // to an error or cleared for a chain reorg. It is only used internally to - // the background template generator. - turUnknown -) - -// TemplateNtfn represents a notification of a new template along with the -// reason it was generated. It is sent to subscribers on the channel obtained -// from the TemplateSubscription instance returned by Subscribe. -type TemplateNtfn struct { - Template *BlockTemplate - Reason TemplateUpdateReason -} - -// templateUpdate defines a type which is used to signal the regen event handler -// that a new template and relevant error have been associated with the -// generator. -type templateUpdate struct { - template *BlockTemplate - err error -} - -// regenEvent defines an event which will potentially result in regenerating a -// block template and consists of a regen event type as well as associated data -// that depends on the type as follows: -// - rtReorgStarted: nil -// - rtReorgDone: nil -// - rtBlockAccepted: *dcrutil.Block -// - rtBlockConnected: *dcrutil.Block -// - rtBlockDisconnected: *dcrutil.Block -// - rtVote: *dcrutil.Tx -// - rtTemplateUpdated: templateUpdate -type regenEvent struct { - reason regenEventType - value interface{} -} - -// BgBlkTmplGenerator provides facilities for asynchronously generating block -// templates in response to various relevant events and allowing clients to -// subscribe for updates when new templates are generated as well as access the -// most recently-generated template in a concurrency-safe manner. -// -// An example of some of the events that trigger a new block template to be -// generated are modifications to the current best chain, receiving relevant -// votes, and periodic timeouts to allow inclusion of new transactions. -// -// The templates are generated based on a given block template generator -// instance which itself is based on a given mining policy and transaction -// source. See the NewBlockTemplate method for a detailed description of how -// the block template is generated. -// -// The background generation makes use of three main goroutines -- a regen event -// queue to allow asynchronous non-blocking signalling, a regen event handler to -// process the aforementioned queue and react accordingly, and a subscriber -// notification controller. In addition, the templates themselves are generated -// in their own goroutines with a cancellable context. -// -// A high level overview of the semantics are as follows: -// - Ignore all vote handling when prior to stake validation height -// - Generate templates building on the current tip at startup with a fall back -// to generate a template on its parent if the current tip does not receive -// enough votes within a timeout -// - Continue monitoring for votes on any blocks that extend said parent to -// potentially switch to them and generate a template building on them when -// possible -// - Generate new templates building on new best chain tip blocks once they have -// received the minimum votes after a timeout to provide the additional votes -// an opportunity to propagate, except when it is an intermediate block in a -// chain reorganization -// - In the event the current tip fails to receive the minimum number of -// required votes, monitor side chain blocks which are siblings of it for -// votes in order to potentially switch to them and generate a template -// building on them when possible -// - Generate new templates on blocks disconnected from the best chain tip, -// except when it is an intermediate block in a chain reorganization -// - Generate new templates periodically when there are new regular transactions -// to include -// - Bias templates towards building on the first seen block when possible in -// order to prevent PoW miners from being able to gain an advantage through -// vote withholding -// - Schedule retries in the rare event template generation fails -// - Allow clients to subscribe for updates every time a new template is -// successfully generated along with a reason why it was generated -// - Provide direct access to the most-recently generated template -// - Block while generating new templates that will make the current template -// stale (e.g. new parent or new votes) -type BgBlkTmplGenerator struct { - wg sync.WaitGroup - quit chan struct{} - - // These fields are provided by the caller when the generator is created and - // are either independently safe for concurrent access or do not change after - // initialization. - // - // chain is the blockchain instance that is used to build the block and - // validate the block templates. - // - // tg is a block template generator instance that is used to actually create - // the block templates the background block template generator stores. - // - // allowUnsyncedMining indicates block templates should be created even when - // the chain is not fully synced. - // - // maxVotesPerBlock is the maximum number of votes per block and comes from - // the chain parameters. It is defined separately for convenience. - // - // minVotesRequired is the minimum number of votes required for a block to - // be built on. It is derived from the chain parameters and is defined - // separately for convenience. - chain *blockchain.BlockChain - tg *BlkTmplGenerator - allowUnsyncedMining bool - miningAddrs []dcrutil.Address - maxVotesPerBlock uint16 - minVotesRequired uint16 - - // These fields deal with providing a stream of template updates to - // subscribers. - // - // subscriptions tracks all template update subscriptions. It is protected - // for concurrent access by subscriptionMtx. - // - // notifySubscribers delivers template updates to the separate subscriber - // notification goroutine so it can in turn asynchronously deliver - // notifications to all subscribers. - subscriptionMtx sync.Mutex - subscriptions map[*TemplateSubscription]struct{} - notifySubscribers chan *TemplateNtfn - notifiedParents lru.Cache - - // These fields deal with the template regeneration event queue. This is - // implemented as a concurrent queue with immediate passthrough when - // possible to ensure the order of events is maintained and the related - // callbacks never block. - // - // queueRegenEvent either immediately forwards regen events to the - // regenEventMsgs channel when it would not block or adds the event to a - // queue that is processed asynchronously as soon as the receiver becomes - // available. - // - // regenEventMsgs delivers relevant regen events to which the generator - // reacts to the separate regen goroutine so it can in turn asynchronously - // process the events and regenerate templates as needed. - queueRegenEvent chan regenEvent - regenEventMsgs chan regenEvent - - // staleTemplateWg is used to allow template retrieval to block callers when - // a new template that will make the current template stale is being - // generated. Stale, in this context, means either the parent has changed - // or there are new votes available. - staleTemplateWg sync.WaitGroup - - // These fields track the current best template and are protected by the - // template mutex. The template will be nil when there is a template error - // set. - templateMtx sync.Mutex - template *BlockTemplate - templateReason TemplateUpdateReason - templateErr error - - // These fields are used to provide the ability to cancel a template that - // is in the process of being asynchronously generated in favor of - // generating a new one. - // - // cancelTemplate is a function which will cancel the current template that - // is in the process of being asynchronously generated. It will have no - // effect if no template generation is in progress. It is protected for - // concurrent access by cancelTemplateMtx. - cancelTemplateMtx sync.Mutex - cancelTemplate func() -} - -// NewBgBlkTmplGenerator initializes a background block template generator with -// the provided parameters. The returned instance must be started with the Run -// method to allowing processing. -func NewBgBlkTmplGenerator(tg *BlkTmplGenerator, addrs []dcrutil.Address, allowUnsynced bool) *BgBlkTmplGenerator { - return &BgBlkTmplGenerator{ - quit: make(chan struct{}), - chain: tg.chain, - tg: tg, - allowUnsyncedMining: allowUnsynced, - miningAddrs: addrs, - maxVotesPerBlock: tg.chainParams.TicketsPerBlock, - minVotesRequired: (tg.chainParams.TicketsPerBlock / 2) + 1, - subscriptions: make(map[*TemplateSubscription]struct{}), - notifySubscribers: make(chan *TemplateNtfn), - notifiedParents: lru.NewCache(3), - queueRegenEvent: make(chan regenEvent), - regenEventMsgs: make(chan regenEvent), - cancelTemplate: func() {}, - } -} - -// UpdateBlockTime updates the timestamp in the passed header to the current -// time while taking into account the median time of the last several blocks to -// ensure the new time is after that time per the chain 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 (g *BgBlkTmplGenerator) UpdateBlockTime(header *wire.BlockHeader) error { - return g.tg.UpdateBlockTime(header) -} - -// sendQueueRegenEvent sends the provided regen event on the internal queue -// regen event channel while respecting the quit channel. The allows orderly -// shutdown when the generator is shutdown. -func (g *BgBlkTmplGenerator) sendQueueRegenEvent(event regenEvent) { - select { - case g.queueRegenEvent <- event: - case <-g.quit: - } -} - -// setCurrentTemplate sets the current template and error associated with the -// background block template generator and notifies the regen event handler -// about the update. -// -// This function is safe for concurrent access. -func (g *BgBlkTmplGenerator) setCurrentTemplate(template *BlockTemplate, reason TemplateUpdateReason, err error) { - g.templateMtx.Lock() - g.template, g.templateReason, g.templateErr = template, reason, err - g.templateMtx.Unlock() - - tplUpdate := templateUpdate{template: template, err: err} - g.sendQueueRegenEvent(regenEvent{rtTemplateUpdated, tplUpdate}) -} - -// currentTemplate returns the current template associated with the background -// template generator along with the associated reason and error. -// -// NOTE: The returned template and block that it contains MUST be treated as -// immutable since they are shared by all callers. -// -// NOTE: The returned template might be nil even if there is no error. It is -// the responsibility of the caller to properly handle nil templates. -// -// This function differs from the exported version in that it also returns the -// reason associated with the template that is used in notifications. -// -// This function is safe for concurrent access. -func (g *BgBlkTmplGenerator) currentTemplate() (*BlockTemplate, TemplateUpdateReason, error) { - g.staleTemplateWg.Wait() - g.templateMtx.Lock() - template, reason, err := g.template, g.templateReason, g.templateErr - g.templateMtx.Unlock() - return template, reason, err -} - -// CurrentTemplate returns the current template associated with the background -// template generator along with any associated error. -// -// NOTE: The returned template and block that it contains MUST be treated as -// immutable since they are shared by all callers. -// -// NOTE: The returned template might be nil even if there is no error. It is -// the responsibility of the caller to properly handle nil templates. -// -// This function is safe for concurrent access. -func (g *BgBlkTmplGenerator) CurrentTemplate() (*BlockTemplate, error) { - template, _, err := g.currentTemplate() - return template, err -} - -// TemplateSubscription defines a subscription to receive block template updates -// from the background block template generator. The caller must call Stop on -// the subscription when it is no longer needed to free resources. -// -// NOTE: Notifications are dropped to make up for slow receivers to ensure -// notifications to other subscribers, as well as senders, are not blocked -// indefinitely. Since templates are typically only generated infrequently and -// receives must fall several templates behind before new ones are dropped, this -// should not affect callers in practice, however, if a caller wishes to -// guarantee that no templates are being dropped, they will need to ensure the -// channel is always processed quickly. -type TemplateSubscription struct { - g *BgBlkTmplGenerator - privC chan *TemplateNtfn -} - -// C returns a channel that produces a stream of block templates as each new -// template is generated. Successive calls to C return the same channel. -// -// NOTE: Notifications are dropped to make up for slow receivers. See the -// template subscription type documentation for more details. -func (s *TemplateSubscription) C() <-chan *TemplateNtfn { - return s.privC -} - -// Stop prevents any future template updates from being delivered and -// unsubscribes the associated subscription. -// -// NOTE: The channel is not closed to prevent a read from the channel succeeding -// incorrectly. -func (s *TemplateSubscription) Stop() { - s.g.subscriptionMtx.Lock() - delete(s.g.subscriptions, s) - s.g.subscriptionMtx.Unlock() -} - -// publishTemplateNtfn sends the provided template notification on the channel -// associated with the subscription. -func (s *TemplateSubscription) publishTemplateNtfn(templateNtfn *TemplateNtfn) { - // Make use of a non-blocking send along with the buffered channel to allow - // notifications to be dropped to make up for slow receivers. - select { - case s.privC <- templateNtfn: - default: - } -} - -// notifySubscribersHandler updates subscribers with newly created block -// templates. -// -// This must be run as a goroutine. -func (g *BgBlkTmplGenerator) notifySubscribersHandler(ctx context.Context) { - for { - select { - case templateNtfn := <-g.notifySubscribers: - g.tg.blockManager.NotifyWork(templateNtfn) - - g.subscriptionMtx.Lock() - for subscription := range g.subscriptions { - subscription.publishTemplateNtfn(templateNtfn) - } - g.subscriptionMtx.Unlock() - - case <-ctx.Done(): - g.wg.Done() - return - } - } -} - -// Subscribe subscribes a client for block template updates. The returned -// template subscription contains functions to retrieve a channel that produces -// the stream of block templates and to stop the stream when the caller no -// longer wishes to receive new templates. -// -// The current template associated with the background block template generator, -// if any, is immediately sent to the returned subscription stream. -func (g *BgBlkTmplGenerator) Subscribe() *TemplateSubscription { - // Create the subscription with a buffered channel that is large enough to - // handle twice the number of templates that can be induced due votes in - // order to provide a reasonable amount of buffering before dropping - // notifications due to a slow receiver. - maxVoteInducedRegens := g.maxVotesPerBlock - g.minVotesRequired + 1 - c := make(chan *TemplateNtfn, maxVoteInducedRegens*2) - subscription := &TemplateSubscription{ - g: g, - privC: c, - } - g.subscriptionMtx.Lock() - g.subscriptions[subscription] = struct{}{} - g.subscriptionMtx.Unlock() - - // Send existing valid template immediately. - template, reason, err := g.currentTemplate() - if err == nil && template != nil { - subscription.publishTemplateNtfn(&TemplateNtfn{template, reason}) - } - - return subscription -} - -// regenQueueHandler immediately forwards items from the regen event queue -// channel to the regen event messages channel when it would not block or adds -// the event to an internal queue to be processed as soon as the receiver -// becomes available. This ensures that queueing regen events never blocks -// despite how busy the regen handler might become during a burst of events. -// -// This must be run as a goroutine. -func (g *BgBlkTmplGenerator) regenQueueHandler(ctx context.Context) { - var q []regenEvent - var out, dequeue chan<- regenEvent = g.regenEventMsgs, nil - skipQueue := out - var next regenEvent - for { - select { - case n := <-g.queueRegenEvent: - // Either send to destination channel immediately when skipQueue is - // non-nil (queue is empty) and reader is ready, or append to the - // queue and send later. - select { - case skipQueue <- n: - default: - q = append(q, n) - dequeue = out - skipQueue = nil - next = q[0] - } - - case dequeue <- next: - copy(q, q[1:]) - q = q[:len(q)-1] - if len(q) == 0 { - dequeue = nil - skipQueue = out - } else { - next = q[0] - } - - case <-ctx.Done(): - g.wg.Done() - return - } - } -} - -// regenHandlerState houses the state used in the regen event handler goroutine. -// It is separated from the background template generator to ensure it is only -// available within the scope of the goroutine. -type regenHandlerState struct { - // isReorganizing indicates the chain is currently undergoing a - // reorganization and therefore the generator should not attempt to create - // new templates until the reorganization has completed. - isReorganizing bool - - // These fields are used to implement a periodic regeneration timeout that - // can be reset at any time without needing to create a new one and the - // associated extra garbage. - // - // regenTimer is an underlying timer that is used to implement the timeout. - // - // regenChanDrained indicates whether or not the channel for the regen timer - // has already been read and is used when resetting the timer to ensure the - // channel is drained when the timer is stopped as described in the timer - // documentation. - // - // lastGeneratedTime specifies the timestamp the current template was - // generated. - regenTimer *time.Timer - regenChanDrained bool - lastGeneratedTime int64 - - // These fields are used to control the various generation states when a new - // block that requires votes has been received. - // - // awaitingMinVotesHash is selectively set when a new tip block has been - // received that requires votes until the minimum number of required votes - // has been received. - // - // maxVotesTimeout is selectively enabled once the minimum number of - // required votes for the current tip block has been received and is - // disabled once the maximum number of votes has been received. This - // effectively sets a timeout to give the remaining votes an opportunity to - // propagate prior to forcing a template with less than the maximum number - // of votes. - awaitingMinVotesHash *chainhash.Hash - maxVotesTimeout <-chan time.Time - - // These fields are used to handle detection of side chain votes and - // potentially reorganizing the chain to a variant of the current tip when - // it is unable to obtain the minimum required votes. - // - // awaitingSideChainMinVotes houses the known blocks that build from the - // same parent as the current tip and will only be selectively populated - // when none of the current possible tips have the minimum number of - // required votes. - // - // trackSideChainsTimeout is selectively enabled when a new tip block has - // been received in order to give the minimum number of required votes - // needed to build a block template on it an opportunity to propagate before - // attempting to find any other variants that extend the same parent as the - // current tip with enough votes to force a reorganization. This ensures the - // first block that is seen is chosen to build templates on so long as it - // receives the minimum required votes in order to prevent PoW miners from - // being able to gain an advantage through vote withholding. It is disabled - // if the minimum number of votes is received prior to the timeout. - awaitingSideChainMinVotes map[chainhash.Hash]struct{} - trackSideChainsTimeout <-chan time.Time - - // failedGenRetryTimeout is selectively enabled in the rare case a template - // fails to generate so it can be regenerated again after a delay. A - // template should never fail to generate in practice, however, future code - // changes might break that assumption and thus it is important to handle - // the case properly. - failedGenRetryTimeout <-chan time.Time - - // These fields track the block and height that the next template to be - // generated will build on. This may not be the same as the current tip in - // the case it has not yet received the minimum number of required votes - // needed to build a template on it. - // - // baseBlockHash is the hash of the block the next template to be generated - // will build on. - // - // baseBlockHeight is the height of the block identified by the base block - // hash. - baseBlockHash chainhash.Hash - baseBlockHeight uint32 -} - -// makeRegenHandlerState returns a regen handler state that is ready to use. -func makeRegenHandlerState() regenHandlerState { - regenTimer := time.NewTimer(math.MaxInt64) - regenTimer.Stop() - return regenHandlerState{ - regenTimer: regenTimer, - regenChanDrained: true, - awaitingSideChainMinVotes: make(map[chainhash.Hash]struct{}), - } -} - -// stopRegenTimer stops the regen timer while ensuring to read from the timer's -// channel in the case the timer already expired which can happen due to the -// fact the stop happens in between channel reads. This behavior is well -// documented in the Timer docs. -// -// NOTE: This function must not be called concurrent with any other receives on -// the timer's channel. -func (state *regenHandlerState) stopRegenTimer() { - t := state.regenTimer - if !t.Stop() && !state.regenChanDrained { - <-t.C - } - state.regenChanDrained = true -} - -// resetRegenTimer resets the regen timer to the given duration while ensuring -// to read from the timer's channel in the case the timer already expired which -// can happen due to the fact the reset happens in between channel reads. This -// behavior is well documented in the Timer docs. -// -// NOTE: This function must not be called concurrent with any other receives on -// the timer's channel. -func (state *regenHandlerState) resetRegenTimer(d time.Duration) { - state.stopRegenTimer() - state.regenTimer.Reset(d) - state.regenChanDrained = false -} - -// clearSideChainTracking removes all tracking for minimum required votes on -// side chain blocks as well as clears the associated timeout that must -// transpire before said tracking is enabled. -func (state *regenHandlerState) clearSideChainTracking() { - for hash := range state.awaitingSideChainMinVotes { - delete(state.awaitingSideChainMinVotes, hash) - } - state.trackSideChainsTimeout = nil -} - -// genTemplateAsync cancels any asynchronous block template that is already -// currently being generated and launches a new goroutine to asynchronously -// generate a new one with the provided reason. It also handles updating the -// current template and error associated with the generator with the results in -// a concurrent safe fashion and, in the case a successful template is -// generated, notifies the subscription handler goroutine with the new template. -func (g *BgBlkTmplGenerator) genTemplateAsync(ctx context.Context, reason TemplateUpdateReason) { - // Cancel any other templates that might currently be in the process of - // being generated and create a new context that can be cancelled for the - // new template that is about to be generated. - g.cancelTemplateMtx.Lock() - g.cancelTemplate() - ctx, g.cancelTemplate = context.WithCancel(ctx) - g.cancelTemplateMtx.Unlock() - - // Ensure that attempts to retrieve the current template block until the - // new template is generated when it is because the parent has changed or - // new votes are available in order to avoid handing out a template that - // is guaranteed to be stale soon after. - blockRetrieval := reason == TURNewParent || reason == TURNewVotes - if blockRetrieval { - g.staleTemplateWg.Add(1) - } - go func(ctx context.Context, reason TemplateUpdateReason, blockRetrieval bool) { - if blockRetrieval { - defer g.staleTemplateWg.Done() - } - - // Pick a mining address at random and generate a block template that - // pays to it. - prng := rand.New(rand.NewSource(time.Now().Unix())) - payToAddr := g.miningAddrs[prng.Intn(len(g.miningAddrs))] - template, err := g.tg.NewBlockTemplate(payToAddr) - // NOTE: err is handled below. - if err != nil { - log.Tracef("NewBlockTemplate: %v", err) - } - - // Don't update the state or notify subscribers when the template - // generation was cancelled. - if ctx.Err() != nil { - return - } - - // Update the current template state with the results and notify - // subscribed clients of the new template so long as it's valid. - if err != nil { - reason = turUnknown - } - g.setCurrentTemplate(template, reason, err) - if err == nil && template != nil { - // It is possible for a new vote to show up while the template for - // a new parent is still being generated which causes that template - // to be canceled in favor of the the new one with the vote. So, - // ensure the first notification sent for a new parent has that - // reason. - header := &template.Block.Header - if reason == TURNewVotes { - if !g.notifiedParents.Contains(header.PrevBlock) { - reason = TURNewParent - } - } - if reason == TURNewParent { - g.notifiedParents.Add(header.PrevBlock) - } - - // Ensure the goroutine exits cleanly during shutdown. - select { - case <-ctx.Done(): - return - - case g.notifySubscribers <- &TemplateNtfn{template, reason}: - } - } - }(ctx, reason, blockRetrieval) -} - -// curTplHasNumVotes returns whether or not the current template is valid, -// builds on the provided hash, and contains the specified number of votes. -func (g *BgBlkTmplGenerator) curTplHasNumVotes(votedOnHash *chainhash.Hash, numVotes uint16) bool { - g.templateMtx.Lock() - template, err := g.template, g.templateErr - g.templateMtx.Unlock() - if template == nil || err != nil { - return false - } - if template.Block.Header.PrevBlock != *votedOnHash { - return false - } - return template.Block.Header.Voters == numVotes -} - -// numVotesForBlock returns the number of votes on the provided block hash that -// are known. -func (g *BgBlkTmplGenerator) numVotesForBlock(votedOnBlock *chainhash.Hash) uint16 { - return uint16(len(g.tg.txSource.VoteHashesForBlock(votedOnBlock))) -} - -// handleBlockConnected handles the rtBlockConnected event by either immediately -// generating a new template building on the block when it will still be prior -// to stake validation height or selectively setting up timeouts to give the -// votes a chance to propagate once the template will be at or after stake -// validation height. -// -// This function is only intended for use by the regen handler goroutine. -func (g *BgBlkTmplGenerator) handleBlockConnected(ctx context.Context, state *regenHandlerState, block *dcrutil.Block, chainTip *blockchain.BestState) { - // Clear all vote tracking when the current chain tip changes. - state.awaitingMinVotesHash = nil - state.clearSideChainTracking() - - // Nothing more to do if the connected block is not the current chain tip. - // This can happen in rare cases such as if more than one new block shows up - // while generating a template. Due to the requirement for votes later in - // the chain, it should almost never happen in practice once the chain has - // progressed that far, however, it is required for correctness. It is also - // worth noting that it happens more frequently earlier in the chain before - // voting starts, particularly in simulation networks with low difficulty. - blockHeight := block.MsgBlock().Header.Height - blockHash := block.Hash() - if int64(blockHeight) != chainTip.Height || *blockHash != chainTip.Hash { - return - } - - // Generate a new template immediately when it will be prior to stake - // validation height which means no votes are required. - newTemplateHeight := blockHeight + 1 - if newTemplateHeight < uint32(g.tg.chainParams.StakeValidationHeight) { - state.stopRegenTimer() - state.failedGenRetryTimeout = nil - state.baseBlockHash = *blockHash - state.baseBlockHeight = blockHeight - g.genTemplateAsync(ctx, TURNewParent) - return - } - - // At this point the template will be at or after stake validation height, - // and therefore requires the inclusion of votes on the previous block to be - // valid. - - // Generate a new template immediately when the maximum number of votes - // for the block are already known. - numVotes := g.numVotesForBlock(blockHash) - if numVotes >= g.maxVotesPerBlock { - state.stopRegenTimer() - state.failedGenRetryTimeout = nil - state.baseBlockHash = *blockHash - state.baseBlockHeight = blockHeight - g.genTemplateAsync(ctx, TURNewParent) - return - } - - // Update the state so the next template generated will build on the block - // and set a timeout to give the remaining votes an opportunity to propagate - // when the minimum number of required votes for the block are already - // known. This provides a balance between preferring to generate block - // templates with max votes and not waiting too long before starting work on - // the next block. - if numVotes >= g.minVotesRequired { - state.stopRegenTimer() - state.failedGenRetryTimeout = nil - state.baseBlockHash = *blockHash - state.baseBlockHeight = blockHeight - state.maxVotesTimeout = time.After(maxVoteTimeoutDuration) - return - } - - // Mark the state as waiting for the minimum number of required votes needed - // to build a template on the block to be received and set a timeout to give - // them an opportunity to propagate before attempting to find any other - // variants that extend the same parent with enough votes to force a - // reorganization. This ensures the first block that is seen is chosen to - // build templates on so long as it receives the minimum required votes in - // order to prevent PoW miners from being able to gain an advantage through - // vote withholding. - // - // Also, the regen timer for the current template is stopped since chances - // are high that the votes will be received and it is ideal to avoid - // regenerating a template that will likely be stale shortly. The regen - // timer is reset after the timeout if needed. - state.stopRegenTimer() - state.awaitingMinVotesHash = blockHash - state.trackSideChainsTimeout = time.After(minVotesTimeoutDuration) -} - -// handleBlockDisconnected handles the rtBlockDisconnected event by immediately -// generating a new template based on the new tip since votes for it are -// either necessarily already known due to being included in the block being -// disconnected or not required due to moving before stake validation height. -// -// This function is only intended for use by the regen handler goroutine. -func (g *BgBlkTmplGenerator) handleBlockDisconnected(ctx context.Context, state *regenHandlerState, block *dcrutil.Block, chainTip *blockchain.BestState) { - // Clear all vote tracking when the current chain tip changes. - state.awaitingMinVotesHash = nil - state.clearSideChainTracking() - - // Nothing more to do if the current chain tip is not the block prior to the - // block that was disconnected. This can happen in rare cases such as when - // forcing disconnects via block invalidation. In practice, disconnects - // happen as a result of chain reorganizations and thus this code will not - // be executed, however, it is required for correctness. - prevHeight := block.MsgBlock().Header.Height - 1 - prevHash := &block.MsgBlock().Header.PrevBlock - if int64(prevHeight) != chainTip.Height || *prevHash != chainTip.Hash { - return - } - - // NOTE: The block being disconnected necessarily has votes for the block - // that is becoming the new tip and they should ideally be extracted here to - // ensure they are available for use when building the template. However, - // the underlying template generator currently relies on pulling the votes - // out of the mempool and performs this task itself. In the future, the - // template generator should ideally accept the votes to include directly. - - // Generate a new template building on the new tip. - state.stopRegenTimer() - state.failedGenRetryTimeout = nil - state.baseBlockHash = *prevHash - state.baseBlockHeight = prevHeight - g.genTemplateAsync(ctx, TURNewParent) -} - -// handleBlockAccepted handles the rtBlockAccepted event by establishing vote -// tracking for the block when it is a variant that extends the same parent as -// the current tip, the current tip does not have the minimum number of required -// votes, and the initial timeout to provide them an opportunity to propagate -// has already expired. -// -// This function is only intended for use by the regen handler goroutine. -func (g *BgBlkTmplGenerator) handleBlockAccepted(_ context.Context, state *regenHandlerState, block *dcrutil.Block, chainTip *blockchain.BestState) { - // Ignore side chain blocks while still waiting for the side chain tracking - // timeout to expire. This provides a bias towards the first block that is - // seen in order to prevent PoW miners from being able to gain an advantage - // through vote withholding. - if state.trackSideChainsTimeout != nil { - return - } - - // Ignore side chain blocks when building on it would produce a block prior - // to stake validation height which means no votes are required and - // therefore no additional handling is necessary. - blockHeight := block.MsgBlock().Header.Height - newTemplateHeight := blockHeight + 1 - if newTemplateHeight < uint32(g.tg.chainParams.StakeValidationHeight) { - return - } - - // Ignore side chain blocks when the current tip already has enough votes - // for a template to be built on it. This ensures the first block that is - // seen is chosen to build templates on so long as it receives the minimum - // required votes in order to prevent PoW miners from being able to gain an - // advantage through vote withholding. - if state.awaitingMinVotesHash == nil { - return - } - - // Ignore blocks that are prior to the current tip. - if blockHeight < uint32(chainTip.Height) { - return - } - - // Ignore main chain tip block since it is handled by the connect path. - blockHash := block.Hash() - if *blockHash == chainTip.Hash { - return - } - - // Ignore side chain blocks when the current template is already building on - // the current tip or the accepted block is not a sibling of the current - // best chain tip. - alreadyBuildingOnCurTip := state.baseBlockHash == chainTip.Hash - acceptedPrevHash := &block.MsgBlock().Header.PrevBlock - if alreadyBuildingOnCurTip || *acceptedPrevHash != chainTip.PrevHash { - return - } - - // Setup tracking for votes on the block. - state.awaitingSideChainMinVotes[*blockHash] = struct{}{} -} - -// handleVote handles the rtVote event by determining if the vote is for a block -// the current state is monitoring and reacting accordingly. At a high level, -// this entails either establishing a timeout once the minimum number of -// required votes for the current tip have been received to provide the -// remaining votes an opportunity to propagate, regenerating the current -// template as a result of the vote, or potentially reorganizing the chain to a -// new tip that has enough votes in the case the current tip is unable to obtain -// the required votes. -// -// This function is only intended for use by the regen handler goroutine. -func (g *BgBlkTmplGenerator) handleVote(ctx context.Context, state *regenHandlerState, voteTx *dcrutil.Tx, chainTip *blockchain.BestState) { - votedOnHash, _ := stake.SSGenBlockVotedOn(voteTx.MsgTx()) - - // The awaiting min votes hash is selectively set once a block is connected - // such that a new template that builds on it will be at or after stake - // validation height until the minimum number of votes required to build a - // template are received. - // - // Update the state so the next template generated will build on the current - // tip once at least the minimum number of required votes for it has been - // received and either set a timeout to give the remaining votes an - // opportunity to propagate if the maximum number of votes is not already - // known or generate a new template immediately when they are. This - // provides a balance between preferring to generate block templates with - // max votes and not waiting too long before starting work on the next - // block. - minVotesHash := state.awaitingMinVotesHash - if minVotesHash != nil && votedOnHash == *minVotesHash { - numVotes := g.numVotesForBlock(minVotesHash) - log.Debugf("Received vote %s for tip block %s (%d total)", - voteTx.Hash(), minVotesHash, numVotes) - if numVotes >= g.minVotesRequired { - // Ensure the next template generated builds on the tip and clear - // all vote tracking to lock the current tip in now that it - // has the minimum required votes. - state.stopRegenTimer() - state.failedGenRetryTimeout = nil - state.baseBlockHash = *minVotesHash - state.baseBlockHeight = uint32(chainTip.Height) - state.awaitingMinVotesHash = nil - state.clearSideChainTracking() - - // Generate a new template immediately when the maximum number of - // votes for the block are already known. - if numVotes >= g.maxVotesPerBlock { - g.genTemplateAsync(ctx, TURNewParent) - return - } - - // Set a timeout to give the remaining votes an opportunity to - // propagate. - state.maxVotesTimeout = time.After(maxVoteTimeoutDuration) - } - return - } - - // Generate a template on new votes for the block the current state is - // configured to build the next block template on when either the maximum - // number of votes is received for it or once the minimum number of required - // votes has been received and the propagation delay timeout that is started - // upon receipt of said minimum votes has expired. - // - // Note that the base block hash is only updated to the current tip once it - // has received the minimum number of required votes, so this will continue - // to detect votes for the parent of the current tip prior to the point the - // new tip has received enough votes. - // - // This ensures new templates that include the new votes are generated - // immediately upon receiving the maximum number of votes as well as any - // additional votes that arrive after the initial timeout. - if votedOnHash == state.baseBlockHash { - // Avoid regenerating the current template if it is already building on - // the expected block and already has the maximum number of votes. - if g.curTplHasNumVotes(&votedOnHash, g.maxVotesPerBlock) { - state.maxVotesTimeout = nil - return - } - - numVotes := g.numVotesForBlock(&votedOnHash) - log.Debugf("Received vote %s for current template %s (%d total)", - voteTx.Hash(), votedOnHash, numVotes) - if numVotes >= g.maxVotesPerBlock || state.maxVotesTimeout == nil { - // The template needs to be updated due to a new parent the first - // time it is generated and due to new votes on subsequent votes. - // The max votes timeout is only non-nil before the first time it is - // generated. - tplUpdateReason := TURNewVotes - if state.maxVotesTimeout != nil { - tplUpdateReason = TURNewParent - } - - // Cancel the max votes timeout (if set). - state.maxVotesTimeout = nil - - state.stopRegenTimer() - state.failedGenRetryTimeout = nil - g.genTemplateAsync(ctx, tplUpdateReason) - return - } - } - - // Reorganize to an alternative chain tip when it receives at least the - // minimum required number of votes in the case the current chain tip does - // not receive the minimum number of required votes within an initial - // timeout period. - // - // Note that the potential side chain blocks to consider are only populated - // in the aforementioned case. - if _, ok := state.awaitingSideChainMinVotes[votedOnHash]; ok { - numVotes := g.numVotesForBlock(&votedOnHash) - log.Debugf("Received vote %s for side chain block %s (%d total)", - voteTx.Hash(), votedOnHash, numVotes) - if numVotes >= g.minVotesRequired { - err := g.chain.ForceHeadReorganization(chainTip.Hash, votedOnHash) - if err != nil { - return - } - - // Prevent votes on other tip candidates from causing reorg again - // since the new chain tip has enough votes. - state.clearSideChainTracking() - return - } - } -} - -// handleTemplateUpdate handles the rtTemplateUpdate event by updating the state -// accordingly. -// -// This function is only intended for use by the regen handler goroutine. -func (g *BgBlkTmplGenerator) handleTemplateUpdate(state *regenHandlerState, tplUpdate templateUpdate) { - // Schedule a template regen if it failed to generate for some reason. This - // should be exceedingly rare in practice. - if tplUpdate.err != nil && state.failedGenRetryTimeout == nil { - state.failedGenRetryTimeout = time.After(time.Second) - return - } - if tplUpdate.template == nil { - return - } - - // Ensure the base block details match the template. - state.baseBlockHash = tplUpdate.template.Block.Header.PrevBlock - state.baseBlockHeight = tplUpdate.template.Block.Header.Height - 1 - - // Update the state related to template regeneration due to new regular - // transactions. - state.lastGeneratedTime = time.Now().Unix() - state.resetRegenTimer(templateRegenSecs * time.Second) -} - -// handleForceRegen handles the rtForceRegen event by initiating the generation -// of a new template. -// -// This function is only intended for use by the regen handler goroutine. -func (g *BgBlkTmplGenerator) handleForceRegen(ctx context.Context, state *regenHandlerState) { - // Ignore requests to force regeneration if the minimum amount of votes - // has been received and it's just waiting for the last ones to arrive. - // The template will be regenerated shortly in that case. - if state.maxVotesTimeout != nil { - return - } - - state.stopRegenTimer() - state.failedGenRetryTimeout = nil - g.genTemplateAsync(ctx, turUnknown) -} - -// handleRegenEvent handles all regen events by determining the event reason and -// reacting accordingly. For example, it calls the appropriate associated event -// handler for the events that have one and prevents templates from being -// generating in the middle of reorgs. -// -// This function is only intended for use by the regen handler goroutine. -func (g *BgBlkTmplGenerator) handleRegenEvent(ctx context.Context, state *regenHandlerState, event regenEvent) { - // Handle chain reorg messages up front since all of the following logic - // only applies when not in the middle of reorganizing. - switch event.reason { - case rtReorgStarted: - // Ensure that attempts to retrieve the current template block until the - // new template after the reorg is generated. - g.staleTemplateWg.Add(1) - - // Mark the state as reorganizing. - state.isReorganizing = true - - // Stop all timeouts and clear all vote tracking. - state.stopRegenTimer() - state.failedGenRetryTimeout = nil - state.awaitingMinVotesHash = nil - state.maxVotesTimeout = nil - state.clearSideChainTracking() - - // Clear the current template and associated base block for the next - // generated template. - g.setCurrentTemplate(nil, turUnknown, nil) - state.baseBlockHash = zeroHash - state.baseBlockHeight = 0 - return - - case rtReorgDone: - state.isReorganizing = false - - // Treat the tip block as if it was just connected when a reorganize - // finishes so the existing code paths are run. - // - // An error should be impossible here since the request is for the block - // the chain believes is the current tip which means it must exist. - chainTip := g.chain.BestSnapshot() - tipBlock, err := g.chain.BlockByHash(&chainTip.Hash) - if err != nil { - g.setCurrentTemplate(nil, turUnknown, err) - } else { - g.handleBlockConnected(ctx, state, tipBlock, chainTip) - } - - g.staleTemplateWg.Done() - return - } - - // Do not generate block templates when the chain is in the middle of - // reorganizing. - if state.isReorganizing { - return - } - - // Do not generate block templates when the chain is not synced unless - // specifically requested to. - if !g.allowUnsyncedMining && !g.tg.blockManager.IsCurrent() { - return - } - - chainTip := g.chain.BestSnapshot() - switch event.reason { - case rtBlockConnected: - block := event.value.(*dcrutil.Block) - g.handleBlockConnected(ctx, state, block, chainTip) - - case rtBlockDisconnected: - block := event.value.(*dcrutil.Block) - g.handleBlockDisconnected(ctx, state, block, chainTip) - - case rtBlockAccepted: - block := event.value.(*dcrutil.Block) - g.handleBlockAccepted(ctx, state, block, chainTip) - - case rtVote: - voteTx := event.value.(*dcrutil.Tx) - g.handleVote(ctx, state, voteTx, chainTip) - - case rtTemplateUpdated: - tplUpdate := event.value.(templateUpdate) - g.handleTemplateUpdate(state, tplUpdate) - - case rtForceRegen: - g.handleForceRegen(ctx, state) - } -} - -// tipSiblingsSortedByVotes returns all blocks other than the current tip block -// that also extend its parent sorted by the number of votes each has in -// descending order. -func (g *BgBlkTmplGenerator) tipSiblingsSortedByVotes(state *regenHandlerState) []*blockWithNumVotes { - // Obtain all of the current blocks that extend the same parent as the - // current tip. The error is ignored here because it is deprecated. - generation, _ := g.chain.TipGeneration() - - // Nothing else to consider if there is only a single block which will be - // the current tip itself. - if len(generation) <= 1 { - return nil - } - - siblings := make([]*blockWithNumVotes, 0, len(generation)-1) - for i := range generation { - hash := &generation[i] - if *hash == *state.awaitingMinVotesHash { - continue - } - - numVotes := g.numVotesForBlock(hash) - siblings = append(siblings, &blockWithNumVotes{ - Hash: *hash, - NumVotes: numVotes, - }) - } - sort.Sort(sort.Reverse(byNumberOfVotes(siblings))) - return siblings -} - -// handleTrackSideChainsTimeout handles potentially reorganizing the chain to a -// side chain block with the most votes in the case the minimum number of -// votes needed to build a block template on the current tip have not been -// received within a certain timeout. -// -// It also doubles to reset the regen timer for the current template in the case -// no validate candidates are found since it is disabled when setting up this -// timeout to prevent creating new templates that would very likely be stale -// soon after. -// -// This function is only intended for use by the regen handler goroutine. -func (g *BgBlkTmplGenerator) handleTrackSideChainsTimeout(ctx context.Context, state *regenHandlerState) { - // Don't allow side chain variants to override the current tip when it - // already has the minimum required votes. - if state.awaitingMinVotesHash == nil { - return - } - - // Reorganize the chain to a valid sibling of the current tip that has at - // least the minimum number of required votes while preferring the most - // votes. - // - // Also, while looping, add each tip the map of side chain blocks to monitor - // for votes in the event there are not currently any eligible candidates - // since they may become eligible as votes arrive. - sortedSiblings := g.tipSiblingsSortedByVotes(state) - for _, sibling := range sortedSiblings { - if sibling.NumVotes >= g.minVotesRequired { - err := g.chain.ForceHeadReorganization(*state.awaitingMinVotesHash, - sibling.Hash) - if err != nil { - // Try the next block in the case of failure to reorg. - continue - } - - // Prevent votes on other tip candidates from causing reorg again - // since the new chain tip has enough votes. The reorg event clears - // the state, but, since there is a backing queue for the events, - // and the reorg itself might haven taken a bit of time, it could - // allow new side chain blocks or votes on existing ones in before - // the reorg events are processed. Thus, update the state to - // indicate the next template is to be built on the new tip to - // prevent any possible logic races. - state.awaitingMinVotesHash = nil - state.clearSideChainTracking() - state.stopRegenTimer() - state.failedGenRetryTimeout = nil - state.baseBlockHash = sibling.Hash - return - } - - state.awaitingSideChainMinVotes[sibling.Hash] = struct{}{} - } - - // Generate a new template building on the parent of the current tip when - // there is not already an existing template and the initial timeout has - // elapsed upon receiving the new tip without receiving votes for it. There - // will typically only not be an existing template when the generator is - // first instantiated and after a chain reorganization. - if state.baseBlockHash == zeroHash { - chainTip := g.chain.BestSnapshot() - state.failedGenRetryTimeout = nil - state.baseBlockHash = chainTip.PrevHash - state.baseBlockHeight = uint32(chainTip.Height - 1) - g.genTemplateAsync(ctx, TURNewParent) - return - } - - // At this point, no viable candidates to change the current template were - // found, so reset the regen timer for the current template. - state.resetRegenTimer(templateRegenSecs * time.Second) -} - -// regenHandler is the main workhorse for generating new templates in response -// to regen events and also handles generating a new template during initial -// startup. -// -// This must be run as a goroutine. -func (g *BgBlkTmplGenerator) regenHandler(ctx context.Context) { - // Treat the tip block as if it was just connected when starting up so the - // existing code paths are run. - tipBlock, err := g.chain.BlockByHash(&g.chain.BestSnapshot().Hash) - if err != nil { - g.setCurrentTemplate(nil, turUnknown, err) - } else { - select { - case <-ctx.Done(): - g.wg.Done() - return - case g.queueRegenEvent <- regenEvent{rtBlockConnected, tipBlock}: - } - } - - state := makeRegenHandlerState() - for { - select { - case event := <-g.regenEventMsgs: - g.handleRegenEvent(ctx, &state, event) - - // This timeout is selectively enabled once the minimum number of - // required votes has been received in order to give the remaining votes - // an opportunity to propagate. It is disabled if the remaining votes - // are received prior to the timeout. - case <-state.maxVotesTimeout: - state.maxVotesTimeout = nil - g.genTemplateAsync(ctx, TURNewParent) - - // This timeout is selectively enabled when a new block is connected in - // order to give the minimum number of required votes needed to build a - // block template on it an opportunity to propagate before attempting to - // find any other variants that extend the same parent as the current - // tip with enough votes to force a reorganization. This ensures the - // first block that is seen is chosen to build templates on so long as - // it receives the minimum required votes in order to prevent PoW miners - // from being able to gain an advantage through vote withholding. It is - // disabled if the minimum number of votes is received prior to the - // timeout. - case <-state.trackSideChainsTimeout: - state.trackSideChainsTimeout = nil - g.handleTrackSideChainsTimeout(ctx, &state) - - // This timeout is selectively enabled once a template has been - // generated in order to allow the template to be periodically - // regenerated with new transactions. Note that votes have special - // handling as described above. - case <-state.regenTimer.C: - // Mark the timer's channel as having been drained so the timer can - // safely be reset. - state.regenChanDrained = true - - // Generate a new template when there are new transactions - // available. - if g.tg.txSource.LastUpdated().Unix() > state.lastGeneratedTime { - state.failedGenRetryTimeout = nil - g.genTemplateAsync(ctx, TURNewTxns) - continue - } - - // There are no new transactions to include and the initial timeout - // has been triggered, so reset the timer to check again in one - // second. - state.resetRegenTimer(time.Second) - - // This timeout is selectively enabled in the rare case a template fails - // to generate and disabled prior to attempts at generating a new one. - case <-state.failedGenRetryTimeout: - state.failedGenRetryTimeout = nil - g.genTemplateAsync(ctx, TURNewParent) - - case <-ctx.Done(): - g.wg.Done() - return - } - } -} - -// ChainReorgStarted informs the background block template generator that a -// chain reorganization has started. It is caller's responsibility to ensure -// this is only invoked as described. -func (g *BgBlkTmplGenerator) ChainReorgStarted() { - g.sendQueueRegenEvent(regenEvent{rtReorgStarted, nil}) -} - -// ChainReorgDone informs the background block template generator that a chain -// reorganization has completed. It is caller's responsibility to ensure this -// is only invoked as described. -func (g *BgBlkTmplGenerator) ChainReorgDone() { - g.sendQueueRegenEvent(regenEvent{rtReorgDone, nil}) -} - -// BlockAccepted informs the background block template generator that a block -// has been accepted to the block chain. It is caller's responsibility to -// ensure this is only invoked as described. -// -// This function is safe for concurrent access. -func (g *BgBlkTmplGenerator) BlockAccepted(block *dcrutil.Block) { - g.sendQueueRegenEvent(regenEvent{rtBlockAccepted, block}) -} - -// BlockConnected informs the background block template generator that a block -// has been connected to the main chain. It is caller's responsibility to -// ensure this is only invoked as described. -// -// This function is safe for concurrent access. -func (g *BgBlkTmplGenerator) BlockConnected(block *dcrutil.Block) { - g.sendQueueRegenEvent(regenEvent{rtBlockConnected, block}) -} - -// BlockDisconnected informs the background block template generator that a -// block has been disconnected from the main chain. It is caller's -// responsibility to ensure this is only invoked as described. -// -// This function is safe for concurrent access. -func (g *BgBlkTmplGenerator) BlockDisconnected(block *dcrutil.Block) { - g.sendQueueRegenEvent(regenEvent{rtBlockDisconnected, block}) -} - -// VoteReceived informs the background block template generator that a new vote -// has been received. It is the caller's responsibility to ensure this is only -// invoked with valid votes. -// -// This function is safe for concurrent access. -func (g *BgBlkTmplGenerator) VoteReceived(tx *dcrutil.Tx) { - g.sendQueueRegenEvent(regenEvent{rtVote, tx}) -} - -// ForceRegen asks the background block template generator to generate a new -// template, independently of most of its internal timers. -// -// Note that there is no guarantee on whether a new template will actually be -// generated or when. This function does _not_ block until a new template is -// generated. -// -// This function is safe for concurrent access. -func (g *BgBlkTmplGenerator) ForceRegen() { - g.sendQueueRegenEvent(regenEvent{rtForceRegen, nil}) -} - -// Run starts the background block template generator and all other goroutines -// necessary for it to function properly and blocks until the provided context -// is cancelled. -func (g *BgBlkTmplGenerator) Run(ctx context.Context) { - g.wg.Add(4) - go g.regenQueueHandler(ctx) - go g.regenHandler(ctx) - go g.notifySubscribersHandler(ctx) - go func() { - <-ctx.Done() - close(g.quit) - g.wg.Done() - }() - g.wg.Wait() -}