// Copyright (c) 2013-2016 The btcsuite developers // Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package netsync import ( "context" "errors" "fmt" "math" "runtime/debug" "sync" "time" "github.com/decred/dcrd/blockchain/stake/v4" "github.com/decred/dcrd/blockchain/v4" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/chaincfg/v3" "github.com/decred/dcrd/container/apbf" "github.com/decred/dcrd/database/v3" "github.com/decred/dcrd/dcrutil/v4" "github.com/decred/dcrd/internal/mempool" "github.com/decred/dcrd/internal/progresslog" peerpkg "github.com/decred/dcrd/peer/v3" "github.com/decred/dcrd/wire" ) const ( // minInFlightBlocks is the minimum number of blocks that should be // in the request queue before requesting more. minInFlightBlocks = 10 // maxInFlightBlocks is the maximum number of blocks to allow in the sync // peer request queue. maxInFlightBlocks = 16 // maxRejectedTxns specifies the maximum number of recently rejected // transactions to track. This is primarily used to avoid wasting a bunch // of bandwidth from requesting transactions that are already known to be // invalid again from multiple peers, however, it also doubles as DoS // protection against malicious peers. // // Recall that there are 125 connection slots by default. Assuming the // default setting of 8 outbound connections, which attackers cannot // control, that leaves 117 max inbound connections which could potentially // be malicious. maxRejectedTxns is set to target tracking the maximum // number of rejected transactions that would result from 120 connections // with malicious peers. 120 is used since it is strictly greater than the // aforementioned 117 max inbound connections while still providing for the // possibility of a few happenstance malicious outbound connections as well. // // It's also worth noting that even if attackers were to manage to exceed // the configured value, the result is not catastrophic as it would only // result in increased bandwidth usage versus not exceeding it. // // rejectedTxnsFPRate is the false positive rate to use for the APBF used to // track recently rejected transactions. It is set to a rate of 1 per 10 // million to make it incredibly unlikely that any transactions that haven't // actually been rejected are incorrectly treated as if they had. // // These values result in about 568 KiB memory usage including overhead. maxRejectedTxns = 62500 rejectedTxnsFPRate = 0.0000001 // maxRequestedBlocks is the maximum number of requested block // hashes to store in memory. maxRequestedBlocks = wire.MaxInvPerMsg // maxRequestedTxns is the maximum number of requested transactions // hashes to store in memory. maxRequestedTxns = wire.MaxInvPerMsg // maxExpectedHeaderAnnouncementsPerMsg is the maximum number of headers in // a single message that is expected when determining when the message // appears to be announcing new blocks. maxExpectedHeaderAnnouncementsPerMsg = 12 // maxConsecutiveOrphanHeaders is the maximum number of consecutive header // messages that contain headers which do not connect a peer can send before // it is deemed to have diverged so far it is no longer useful. maxConsecutiveOrphanHeaders = 10 // headerSyncStallTimeoutSecs is the number of seconds to wait for progress // during the header sync process before stalling the sync and disconnecting // the peer. headerSyncStallTimeoutSecs = (3 + wire.MaxBlockHeadersPerMsg/1000) * 2 ) // zeroHash is the zero value hash (all zeros). It is defined as a convenience. var zeroHash chainhash.Hash // newPeerMsg signifies a newly connected peer to the event handler. type newPeerMsg struct { peer *peerpkg.Peer } // blockMsg packages a Decred block message and the peer it came from together // so the event handler has access to that information. type blockMsg struct { block *dcrutil.Block peer *peerpkg.Peer reply chan struct{} } // invMsg packages a Decred inv message and the peer it came from together // so the event handler has access to that information. type invMsg struct { inv *wire.MsgInv peer *peerpkg.Peer } // headersMsg packages a Decred headers message and the peer it came from // together so the event handler has access to that information. type headersMsg struct { headers *wire.MsgHeaders peer *peerpkg.Peer } // notFoundMsg packages a Decred notfound message and the peer it came from // together so the event handler has access to that information. type notFoundMsg struct { notFound *wire.MsgNotFound peer *peerpkg.Peer } // donePeerMsg signifies a newly disconnected peer to the event handler. type donePeerMsg struct { peer *peerpkg.Peer } // txMsg packages a Decred tx message and the peer it came from together // so the event handler has access to that information. type txMsg struct { tx *dcrutil.Tx peer *peerpkg.Peer reply chan struct{} } // getSyncPeerMsg is a message type to be sent across the message channel for // retrieving the current sync peer. type getSyncPeerMsg struct { reply chan int32 } // requestFromPeerMsg is a message type to be sent across the message channel // for requesting either blocks or transactions from a given peer. It routes // this through the sync manager so the sync manager doesn't ban the peer // when it sends this information back. type requestFromPeerMsg struct { peer *peerpkg.Peer blocks []chainhash.Hash voteHashes []chainhash.Hash tSpendHashes []chainhash.Hash reply chan requestFromPeerResponse } // requestFromPeerResponse is a response sent to the reply channel of a // requestFromPeerMsg query. type requestFromPeerResponse struct { err error } // processBlockResponse is a response sent to the reply channel of a // processBlockMsg. type processBlockResponse struct { forkLen int64 err error } // processBlockMsg is a message type to be sent across the message channel // for requested a block is processed. Note this call differs from blockMsg // above in that blockMsg is intended for blocks that came from peers and have // extra handling whereas this message essentially is just a concurrent safe // way to call ProcessBlock on the internal block chain instance. type processBlockMsg struct { block *dcrutil.Block reply chan processBlockResponse } // syncMgrPeer extends a peer to maintain additional state maintained by the // sync manager. type syncMgrPeer struct { *peerpkg.Peer syncCandidate bool requestedTxns map[chainhash.Hash]struct{} requestedBlocks map[chainhash.Hash]struct{} // initialStateRequested tracks whether or not the initial state data has // been requested from the peer. initialStateRequested bool // numConsecutiveOrphanHeaders tracks the number of consecutive header // messages sent by the peer that contain headers which do not connect. It // is used to detect peers that have either diverged so far they are no // longer useful or are otherwise being malicious. numConsecutiveOrphanHeaders int32 lastAnnouncedBlock *chainhash.Hash } // headerSyncState houses the state used to track the header sync progress and // related stall handling. type headerSyncState struct { // headersSynced tracks whether or not the headers are synced to a point // that is recent enough to start downloading blocks. headersSynced bool // These fields are used to implement a progress stall timeout that can be // reset at any time without needing to create a new one and the associated // extra garbage. // // stallTimer is an underlying timer that is used to implement the timeout. // // stallChanDrained indicates whether or not the channel for the stall 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. stallTimer *time.Timer stallChanDrained bool } // makeHeaderSyncState returns a header sync state that is ready to use. func makeHeaderSyncState() headerSyncState { stallTimer := time.NewTimer(math.MaxInt64) stallTimer.Stop() return headerSyncState{ stallTimer: stallTimer, stallChanDrained: true, } } // stopStallTimeout stops the progress stall 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 *headerSyncState) stopStallTimeout() { t := state.stallTimer if !t.Stop() && !state.stallChanDrained { <-t.C } state.stallChanDrained = true } // resetStallTimeout resets the progress stall 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 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 *headerSyncState) resetStallTimeout() { state.stopStallTimeout() state.stallTimer.Reset(headerSyncStallTimeoutSecs * time.Second) state.stallChanDrained = false } // SyncManager provides a concurrency safe sync manager for handling all // incoming blocks. type SyncManager struct { // The following fields are used for lifecycle management of the sync // manager. wg sync.WaitGroup quit chan struct{} // cfg specifies the configuration of the sync manager and is set at // creation time and treated as immutable after that. cfg Config rejectedTxns *apbf.Filter requestedTxns map[chainhash.Hash]struct{} requestedBlocks map[chainhash.Hash]struct{} progressLogger *progresslog.Logger syncPeer *syncMgrPeer msgChan chan interface{} peers map[*peerpkg.Peer]*syncMgrPeer // hdrSyncState houses the state used to track the initial header sync // process and related stall handling. hdrSyncState headerSyncState // The following fields are used to track the height being synced to from // peers. syncHeightMtx sync.Mutex syncHeight int64 // The following fields are used to track whether or not the manager // believes it is fully synced to the network. isCurrentMtx sync.Mutex isCurrent bool // The following fields are used to track the list of the next blocks to // download in the branch leading up to the best known header. // // nextBlocksHeader is the hash of the best known header when the list was // last updated. // // nextBlocksBuf houses an overall list of blocks needed (up to the size of // the array) regardless of whether or not they have been requested and // provides what is effectively a reusable lookahead buffer. Note that // since it is a fixed size and acts as a backing array, not all entries // will necessarily refer to valid data, especially once the chain is // synced. nextNeededBlocks slices into the valid part of the array. // // nextNeededBlocks subslices into nextBlocksBuf such that it provides an // upper bound on the entries of the backing array that are valid and also // acts as a list of needed blocks that are not already known to be in // flight. nextBlocksHeader chainhash.Hash nextBlocksBuf [512]chainhash.Hash nextNeededBlocks []chainhash.Hash } // lookupPeer returns the sync manager peer that maintains additional state for // a given base peer. In the event the mapping does not exist, a warning is // logged and nil is returned. func lookupPeer(peer *peerpkg.Peer, peers map[*peerpkg.Peer]*syncMgrPeer) *syncMgrPeer { sp, ok := peers[peer] if !ok { log.Warnf("Attempt to lookup unknown peer %s\nStack: %v", peer, string(debug.Stack())) return nil } return sp } // SyncHeight returns latest known block being synced to. func (m *SyncManager) SyncHeight() int64 { m.syncHeightMtx.Lock() syncHeight := m.syncHeight m.syncHeightMtx.Unlock() return syncHeight } // chainBlockLocatorToHashes converts a block locator from chain to a slice // of hashes. func chainBlockLocatorToHashes(locator blockchain.BlockLocator) []chainhash.Hash { if len(locator) == 0 { return nil } result := make([]chainhash.Hash, 0, len(locator)) for _, hash := range locator { result = append(result, *hash) } return result } // maybeUpdateNextNeededBlocks potentially updates the list of the next blocks // to download in the branch leading up to the best known header. // // This function is NOT safe for concurrent access. It must be called from the // event handler goroutine. func (m *SyncManager) maybeUpdateNextNeededBlocks() { // Update the list if the best known header changed since the last time it // was updated or it is not empty, is getting short, and does not already // end at the best known header. chain := m.cfg.Chain bestHeader, _ := chain.BestHeader() numNeeded := len(m.nextNeededBlocks) needsUpdate := m.nextBlocksHeader != bestHeader || (numNeeded > 0 && numNeeded < minInFlightBlocks && m.nextNeededBlocks[numNeeded-1] != bestHeader) if needsUpdate { m.nextNeededBlocks = chain.PutNextNeededBlocks(m.nextBlocksBuf[:]) m.nextBlocksHeader = bestHeader } } // fetchNextBlocks creates and sends a request to the provided peer for the next // blocks to be downloaded based on the current headers. func (m *SyncManager) fetchNextBlocks(peer *syncMgrPeer) { // Nothing to do if the target maximum number of blocks to request from the // peer at the same time are already in flight. numInFlight := len(peer.requestedBlocks) if numInFlight >= maxInFlightBlocks { return } // Potentially update the list of the next blocks to download in the branch // leading up to the best known header. m.maybeUpdateNextNeededBlocks() // Build and send a getdata request for the needed blocks. numNeeded := len(m.nextNeededBlocks) if numNeeded == 0 { return } maxNeeded := maxInFlightBlocks - numInFlight if numNeeded > maxNeeded { numNeeded = maxNeeded } gdmsg := wire.NewMsgGetDataSizeHint(uint(numNeeded)) for i := 0; i < numNeeded && len(gdmsg.InvList) < wire.MaxInvPerMsg; i++ { // The block is either going to be skipped because it has already been // requested or it will be requested, but in either case, the block is // no longer needed for future iterations. hash := &m.nextNeededBlocks[0] m.nextNeededBlocks = m.nextNeededBlocks[1:] // Skip blocks that have already been requested. The needed blocks // might have been updated above thereby potentially repopulating some // blocks that are still in flight. if _, ok := m.requestedBlocks[*hash]; ok { continue } iv := wire.NewInvVect(wire.InvTypeBlock, hash) m.requestedBlocks[*hash] = struct{}{} peer.requestedBlocks[*hash] = struct{}{} gdmsg.AddInvVect(iv) } if len(gdmsg.InvList) > 0 { peer.QueueMessage(gdmsg, nil) } } // startSync will choose the best peer among the available candidate peers to // download/sync the blockchain from. When syncing is already running, it // simply returns. It also examines the candidates for any which are no longer // candidates and removes them as needed. func (m *SyncManager) startSync() { // Nothing more to do when already syncing. if m.syncPeer != nil { return } chain := m.cfg.Chain best := chain.BestSnapshot() var bestPeer *syncMgrPeer for _, peer := range m.peers { if !peer.syncCandidate { continue } // Remove sync candidate peers that are no longer candidates due // to passing their latest known block. NOTE: The < is // intentional as opposed to <=. While technically the peer // doesn't have a later block when it's equal, it will likely // have one soon so it is a reasonable choice. It also allows // the case where both are at 0 such as during regression test. if peer.LastBlock() < best.Height { peer.syncCandidate = false continue } // The best sync candidate is the most updated peer. if bestPeer == nil { bestPeer = peer } if bestPeer.LastBlock() < peer.LastBlock() { bestPeer = peer } } // Update the state of whether or not the manager believes the chain is // fully synced to whatever the chain believes when there is no candidate // for a sync peer. // // Also, return now when there isn't a sync peer candidate as there is // nothing more to do without one. if bestPeer == nil { m.isCurrentMtx.Lock() m.isCurrent = chain.IsCurrent() m.isCurrentMtx.Unlock() log.Warnf("No sync peer candidates available") return } // Start syncing from the best peer. // Clear the requestedBlocks if the sync peer changes, otherwise // we may ignore blocks we need that the last sync peer failed // to send. m.requestedBlocks = make(map[chainhash.Hash]struct{}) syncHeight := bestPeer.LastBlock() headersSynced := m.hdrSyncState.headersSynced if !headersSynced { log.Infof("Syncing headers to block height %d from peer %v", syncHeight, bestPeer) } // The chain is not synced whenever the current best height is less than the // height to sync to. if best.Height < syncHeight { m.isCurrentMtx.Lock() m.isCurrent = false m.isCurrentMtx.Unlock() } // Request headers to discover any blocks that are not already known // starting from the parent of the best known header for the local chain. // The parent is used as a means to accurately discover the best known block // of the remote peer in the case both tips are the same where it would // otherwise result in an empty response. bestHeaderHash, _ := chain.BestHeader() parentHash := bestHeaderHash header, err := chain.HeaderByHash(&bestHeaderHash) if err == nil { parentHash = header.PrevBlock } blkLocator := chain.BlockLocatorFromHash(&parentHash) locator := chainBlockLocatorToHashes(blkLocator) bestPeer.PushGetHeadersMsg(locator, &zeroHash) // Track the sync peer and update the sync height when it is higher than the // currently best known value. m.syncPeer = bestPeer m.syncHeightMtx.Lock() if syncHeight > m.syncHeight { m.syncHeight = syncHeight } m.syncHeightMtx.Unlock() // Start the header sync progress stall timeout when the initial headers // sync is not already done. if !headersSynced { m.hdrSyncState.resetStallTimeout() } // Download any blocks needed to catch the local chain up to the best // known header (if any) when the initial headers sync is already done. // // This is done in addition to the header request above to avoid waiting // for the round trip when there are still blocks that are needed // regardless of the headers response. if headersSynced { m.fetchNextBlocks(m.syncPeer) } } // maybeRequestInitialState potentially requests initial state information from // the provided peer by sending it an appropriate initial state sync message // dependending on the protocol version. // // The request will not be sent more than once or when the peer is in the // process of being removed. func maybeRequestInitialState(peer *syncMgrPeer) { // Don't request the initial state more than once or when the peer is in the // process of being removed. if peer.initialStateRequested || !peer.Connected() { return } // Choose which initial state sync p2p messages to use based on the protocol // version. Protocol versions prior to the init state version use // getminingstate and miningstate while those after use getinitstate and // initstate. var msg wire.Message if peer.ProtocolVersion() < wire.InitStateVersion { msg = wire.NewMsgGetMiningState() } else { m := wire.NewMsgGetInitState() err := m.AddTypes(wire.InitStateHeadBlocks, wire.InitStateHeadBlockVotes, wire.InitStateTSpends) if err != nil { log.Errorf("Unexpected error building getinitstate msg: %v", err) return } msg = m } peer.QueueMessage(msg, nil) peer.initialStateRequested = true } // onInitialChainSyncDone is invoked when the initial chain sync process // completes. func (m *SyncManager) onInitialChainSyncDone() { best := m.cfg.Chain.BestSnapshot() log.Infof("Initial chain sync complete (hash %s, height %d)", best.Hash, best.Height) // Request initial state from all peers that are marked as needing it now // that the initial chain sync is done when enabled. if !m.cfg.NoMiningStateSync { for _, peer := range m.peers { maybeRequestInitialState(peer) } } } // isSyncCandidate returns whether or not the peer is a candidate to consider // syncing from. func (m *SyncManager) isSyncCandidate(peer *peerpkg.Peer) bool { // The peer is not a candidate for sync if it's not a full node. return peer.Services()&wire.SFNodeNetwork == wire.SFNodeNetwork } // handleNewPeerMsg deals with new peers that have signalled they may // be considered as a sync peer (they have already successfully negotiated). It // also starts syncing if needed. It is invoked from the syncHandler goroutine. func (m *SyncManager) handleNewPeerMsg(ctx context.Context, peer *peerpkg.Peer) { select { case <-ctx.Done(): default: } log.Infof("New valid peer %s (%s)", peer, peer.UserAgent()) // Initialize the peer state isSyncCandidate := m.isSyncCandidate(peer) m.peers[peer] = &syncMgrPeer{ Peer: peer, syncCandidate: isSyncCandidate, requestedTxns: make(map[chainhash.Hash]struct{}), requestedBlocks: make(map[chainhash.Hash]struct{}), } // Start syncing by choosing the best candidate if needed. if isSyncCandidate && m.syncPeer == nil { m.startSync() } // Request the initial state from this peer now when enabled and the manager // believes the chain is fully synced. Otherwise, it will be requested when // the initial chain sync process is complete. if !m.cfg.NoMiningStateSync && m.IsCurrent() { maybeRequestInitialState(m.peers[peer]) } } // handleDonePeerMsg deals with peers that have signalled they are done. It // removes the peer as a candidate for syncing and in the case where it was // the current sync peer, attempts to select a new best peer to sync from. It // is invoked from the syncHandler goroutine. func (m *SyncManager) handleDonePeerMsg(p *peerpkg.Peer) { peer := lookupPeer(p, m.peers) if peer == nil { return } // Remove the peer from the list of candidate peers. delete(m.peers, p) // Remove requested transactions from the global map so that they will // be fetched from elsewhere. for txHash := range peer.requestedTxns { delete(m.requestedTxns, txHash) } // Remove requested blocks from the global map so that they will be // fetched from elsewhere. // TODO(oga) we could possibly here check which peers have these blocks // and request them now to speed things up a little. for blockHash := range peer.requestedBlocks { delete(m.requestedBlocks, blockHash) } // Attempt to find a new peer to sync from and reset the final requested // block when the quitting peer is the sync peer. if m.syncPeer == peer { m.syncPeer = nil m.startSync() } } // handleTxMsg handles transaction messages from all peers. func (m *SyncManager) handleTxMsg(tmsg *txMsg) { peer := lookupPeer(tmsg.peer, m.peers) if peer == nil { return } // NOTE: BitcoinJ, and possibly other wallets, don't follow the spec of // sending an inventory message and allowing the remote peer to decide // whether or not they want to request the transaction via a getdata // message. Unfortunately, the reference implementation permits // unrequested data, so it has allowed wallets that don't follow the // spec to proliferate. While this is not ideal, there is no check here // to disconnect peers for sending unsolicited transactions to provide // interoperability. txHash := tmsg.tx.Hash() // Ignore transactions that have already been rejected. The transaction was // unsolicited if it was already previously rejected. if m.rejectedTxns.Contains(txHash[:]) { log.Debugf("Ignoring unsolicited previously rejected transaction %v "+ "from %s", txHash, peer) return } // Process the transaction to include validation, insertion in the // memory pool, orphan handling, etc. allowOrphans := m.cfg.MaxOrphanTxs > 0 acceptedTxs, err := m.cfg.TxMemPool.ProcessTransaction(tmsg.tx, allowOrphans, true, true, mempool.Tag(peer.ID())) // Remove transaction from request maps. Either the mempool/chain // already knows about it and as such we shouldn't have any more // instances of trying to fetch it, or we failed to insert and thus // we'll retry next time we get an inv. delete(peer.requestedTxns, *txHash) delete(m.requestedTxns, *txHash) if err != nil { // Do not request this transaction again until a new block has been // processed. m.rejectedTxns.Add(txHash[:]) // When the error is a rule error, it means the transaction was // simply rejected as opposed to something actually going wrong, // so log it as such. Otherwise, something really did go wrong, // so log it as an actual error. var rErr mempool.RuleError if errors.As(err, &rErr) { log.Debugf("Rejected transaction %v from %s: %v", txHash, peer, err) } else { log.Errorf("Failed to process transaction %v: %v", txHash, err) } return } m.cfg.PeerNotifier.AnnounceNewTransactions(acceptedTxs) } // maybeUpdateIsCurrent potentially updates the manager to signal it believes // the chain is considered synced. // // This function MUST be called with the is current mutex held (for writes). func (m *SyncManager) maybeUpdateIsCurrent() { // Nothing to do when already considered synced. if m.isCurrent { return } // The chain is considered synced once both the blockchain believes it is // current and the sync height is reached or exceeded. best := m.cfg.Chain.BestSnapshot() syncHeight := m.SyncHeight() if best.Height >= syncHeight && m.cfg.Chain.IsCurrent() { m.isCurrent = true } } // processBlock processes the provided block using the internal chain instance. // // When no errors occurred during processing, the first return value indicates // the length of the fork the block extended. In the case it either extended // the best chain or is now the tip of the best chain due to causing a // reorganize, the fork length will be 0. Orphans are rejected and can be // detected by checking if the error is blockchain.ErrMissingParent. func (m *SyncManager) processBlock(block *dcrutil.Block) (int64, error) { // Process the block to include validation, best chain selection, etc. forkLen, err := m.cfg.Chain.ProcessBlock(block) if err != nil { return 0, err } m.isCurrentMtx.Lock() m.maybeUpdateIsCurrent() m.isCurrentMtx.Unlock() return forkLen, nil } // handleBlockMsg handles block messages from all peers. func (m *SyncManager) handleBlockMsg(bmsg *blockMsg) { peer := lookupPeer(bmsg.peer, m.peers) if peer == nil { return } // The remote peer is misbehaving when the block was not requested. blockHash := bmsg.block.Hash() if _, exists := peer.requestedBlocks[*blockHash]; !exists { log.Warnf("Got unrequested block %v from %s -- disconnecting", blockHash, peer) peer.Disconnect() return } // Save whether or not the chain believes it is current prior to processing // the block for use below in determining logging behavior. chain := m.cfg.Chain wasChainCurrent := chain.IsCurrent() // Process the block to include validation, best chain selection, etc. // // Also, remove the block from the request maps once it has been processed. // This ensures chain is aware of the block before it is removed from the // maps in order to help prevent duplicate requests. forkLen, err := m.processBlock(bmsg.block) delete(peer.requestedBlocks, *blockHash) delete(m.requestedBlocks, *blockHash) if err != nil { // Ideally there should never be any requests for duplicate blocks, but // ignore any that manage to make it through. if errors.Is(err, blockchain.ErrDuplicateBlock) { return } // When the error is a rule error, it means the block was simply // rejected as opposed to something actually going wrong, so log it as // such. Otherwise, something really did go wrong, so log it as an // actual error. // // Note that orphan blocks are never requested so there is no need to // test for that rule error separately. var rErr blockchain.RuleError if errors.As(err, &rErr) { log.Infof("Rejected block %v from %s: %v", blockHash, peer, err) } else { log.Errorf("Failed to process block %v: %v", blockHash, err) } if errors.Is(err, database.ErrCorruption) || errors.Is(err, blockchain.ErrUtxoBackendCorruption) { log.Errorf("Critical failure: %v", err) } return } // Log information about the block. Use the progress logger when the chain // was not already current prior to processing the block to provide nicer // periodic logging with a progress percentage. Otherwise, log the block // individually along with some stats. msgBlock := bmsg.block.MsgBlock() header := &msgBlock.Header if !wasChainCurrent { forceLog := int64(header.Height) >= m.SyncHeight() m.progressLogger.LogProgress(msgBlock, forceLog, chain.VerifyProgress) if chain.IsCurrent() { m.onInitialChainSyncDone() } } else { var interval string prevBlockHeader, err := chain.HeaderByHash(&header.PrevBlock) if err == nil { diff := header.Timestamp.Sub(prevBlockHeader.Timestamp) interval = ", interval " + diff.Round(time.Second).String() } numTxns := uint64(len(msgBlock.Transactions)) numTickets := uint64(header.FreshStake) numVotes := uint64(header.Voters) numRevokes := uint64(header.Revocations) log.Infof("New block %s (%d %s, %d %s, %d %s, %d %s, height %d%s)", blockHash, numTxns, pickNoun(numTxns, "transaction", "transactions"), numTickets, pickNoun(numTickets, "ticket", "tickets"), numVotes, pickNoun(numVotes, "vote", "votes"), numRevokes, pickNoun(numRevokes, "revocation", "revocations"), header.Height, interval) } // Perform some additional processing when the block extended the main // chain. onMainChain := forkLen == 0 if onMainChain { // Prune invalidated transactions. best := chain.BestSnapshot() m.cfg.TxMemPool.PruneStakeTx(best.NextStakeDiff, best.Height) m.cfg.TxMemPool.PruneExpiredTx() // Clear the rejected transactions. m.rejectedTxns.Reset() } // Update the latest block height for the peer to avoid stale heights when // looking for future potential sync node candidacy. // // Also, when the chain is considered current and the block was accepted to // the main chain, update the heights of other peers whose invs may have // been ignored when actively syncing while the chain was not yet current or // lost the lock announcement race. blockHeight := int64(header.Height) peer.UpdateLastBlockHeight(blockHeight) if onMainChain && m.IsCurrent() { for _, p := range m.peers { // The height for the sending peer is already updated. if p == peer { continue } lastAnnBlock := p.lastAnnouncedBlock if lastAnnBlock != nil && *lastAnnBlock == *blockHash { p.UpdateLastBlockHeight(blockHeight) p.lastAnnouncedBlock = nil } } } // Request more blocks using the headers when the request queue is getting // short. if peer == m.syncPeer && len(peer.requestedBlocks) < minInFlightBlocks { m.fetchNextBlocks(peer) } } // guessHeaderSyncProgress returns a percentage that is a guess of the progress // of the header sync progress for the given currently best known header based // on an algorithm that considers the total number of expected headers based on // the target time per block of the network. It should only be used for the // main and test networks because it relies on relatively consistent mining // which is not the case for other network such as the simulation test network. // // This function is safe for concurrent access. func (m *SyncManager) guessHeaderSyncProgress(header *wire.BlockHeader) float64 { // Calculate the expected total number of blocks to reach the current time // by considering the number there already are plus the expected number of // remaining ones there should be in the time interval since the provided // best known header and the current time given the target block time. // // This approach is used as opposed to calculating the total expected since // the genesis block since it gets more accurate as more headers are // processed and thus provide more information. It is also more robust // against networks with dynamic difficulty readjustment such as the test // network. curTimestamp := m.cfg.TimeSource.AdjustedTime().Unix() targetSecsPerBlock := int64(m.cfg.ChainParams.TargetTimePerBlock.Seconds()) remaining := (curTimestamp - header.Timestamp.Unix()) / targetSecsPerBlock expectedTotal := int64(header.Height) + remaining // Finally the progress guess is simply the ratio of the current number of // known headers to the total expected number of headers. return math.Min(float64(header.Height)/float64(expectedTotal), 1.0) * 100 } // headerSyncProgress returns a percentage that is a guess of the progress of // of the header sync process. // // This function is safe for concurrent access. func (m *SyncManager) headerSyncProgress() float64 { hash, _ := m.cfg.Chain.BestHeader() header, err := m.cfg.Chain.HeaderByHash(&hash) if err != nil { return 0.0 } // Use an algorithm that considers the total number of expected headers // based on the target time per block of the network for the main and test // networks. This is the preferred approach because, unlike the sync height // reported by remote peers, it is difficult to game since it is based on // the target proof of work, but it assumes consistent mining, which is not // the case on all networks, so limit it to the two where that applies. net := m.cfg.ChainParams.Net if net == wire.MainNet || net == wire.TestNet3 { return m.guessHeaderSyncProgress(&header) } // Fall back to using the sync height reported by the remote peer otherwise. syncHeight := m.SyncHeight() if syncHeight == 0 { return 0.0 } return math.Min(float64(header.Height)/float64(syncHeight), 1.0) * 100 } // handleHeadersMsg handles headers messages from all peers. func (m *SyncManager) handleHeadersMsg(hmsg *headersMsg) { peer := lookupPeer(hmsg.peer, m.peers) if peer == nil { return } // Nothing to do for an empty headers message as it means the sending peer // does not have any additional headers for the requested block locator. headers := hmsg.headers.Headers numHeaders := len(headers) if numHeaders == 0 { return } // Handle the case where the first header does not connect to any known // headers specially. chain := m.cfg.Chain firstHeader := headers[0] firstHeaderHash := firstHeader.BlockHash() firstHeaderConnects := chain.HaveHeader(&firstHeader.PrevBlock) headersSynced := m.hdrSyncState.headersSynced if !firstHeaderConnects { // Ignore headers that do not connect to any known headers when the // initial headers sync is taking place. It is expected that headers // will be announced that are not yet known. if !headersSynced { return } // Attempt to detect block announcements which do not connect to any // known headers and request any headers starting from the best header // the local chain knows in order to (hopefully) discover the missing // headers. // // Meanwhile, also keep track of how many times the peer has // consecutively sent a headers message that does not connect and // disconnect it once the max allowed threshold has been reached. if numHeaders < maxExpectedHeaderAnnouncementsPerMsg { peer.numConsecutiveOrphanHeaders++ if peer.numConsecutiveOrphanHeaders >= maxConsecutiveOrphanHeaders { log.Debugf("Received %d consecutive headers messages that do "+ "not connect from peer %s -- disconnecting", peer.numConsecutiveOrphanHeaders, peer) peer.Disconnect() } log.Debugf("Requesting missing parents for header %s (height %d) "+ "received from peer %s", firstHeaderHash, firstHeader.Height, peer) bestHeaderHash, _ := chain.BestHeader() blkLocator := chain.BlockLocatorFromHash(&bestHeaderHash) locator := chainBlockLocatorToHashes(blkLocator) peer.PushGetHeadersMsg(locator, &zeroHash) return } // The initial headers sync process is done and this does not appear to // be a block announcement, so disconnect the peer. log.Debugf("Received orphan header from peer %s -- disconnecting", peer) peer.Disconnect() return } // Ensure all of the received headers connect the previous one before // attempting to perform any further processing on any of them. headerHashes := make([]chainhash.Hash, 0, len(headers)) headerHashes = append(headerHashes, firstHeaderHash) for prevIdx, header := range headers[1:] { prevHash := &headerHashes[prevIdx] prevHeight := headers[prevIdx].Height if header.PrevBlock != *prevHash || header.Height != prevHeight+1 { log.Debugf("Received block header that does not properly connect "+ "to previous one from peer %s -- disconnecting", peer) peer.Disconnect() return } headerHashes = append(headerHashes, header.BlockHash()) } // Save the current best known header height prior to processing the headers // so the code later is able to determine if any new useful headers were // provided. _, prevBestHeaderHeight := chain.BestHeader() // Process all of the received headers. for _, header := range headers { err := chain.ProcessBlockHeader(header) if err != nil { // Note that there is no need to check for an orphan header here // because they were already verified to connect above. log.Debugf("Failed to process block header %s from peer %s: %v -- "+ "disconnecting", header.BlockHash(), peer, err) peer.Disconnect() return } } // All of the headers were either accepted or already known valid at this // point. // Reset the header sync progress stall timeout when the headers are not // already synced and progress was made. newBestHeaderHash, newBestHeaderHeight := chain.BestHeader() if peer == m.syncPeer && !headersSynced { if newBestHeaderHeight > prevBestHeaderHeight { m.hdrSyncState.resetStallTimeout() } } // Reset the count of consecutive headers messages that contained headers // which do not connect. Note that this is intentionally only done when all // of the provided headers are successfully processed above. peer.numConsecutiveOrphanHeaders = 0 // Update the last announced block to the final one in the announced headers // above and update the height for the peer too. finalHeader := headers[len(headers)-1] finalReceivedHash := &headerHashes[len(headerHashes)-1] peer.lastAnnouncedBlock = finalReceivedHash peer.UpdateLastBlockHeight(int64(finalHeader.Height)) // Update the sync height if the new best known header height exceeds it. syncHeight := m.SyncHeight() if newBestHeaderHeight > syncHeight { syncHeight = newBestHeaderHeight m.syncHeightMtx.Lock() m.syncHeight = syncHeight m.syncHeightMtx.Unlock() } // Disconnect outbound peers that have less cumulative work than the minimum // value already known to have been achieved on the network a priori while // the initial sync is still underway. This is determined by noting that a // peer only sends fewer than the maximum number of headers per message when // it has reached its best known header. isChainCurrent := chain.IsCurrent() receivedMaxHeaders := len(headers) == wire.MaxBlockHeadersPerMsg if !isChainCurrent && !peer.Inbound() && !receivedMaxHeaders { minKnownWork := m.cfg.ChainParams.MinKnownChainWork if minKnownWork != nil { workSum, err := chain.ChainWork(finalReceivedHash) if err == nil && workSum.Cmp(minKnownWork) < 0 { log.Debugf("Best known chain for peer %s has too little "+ "cumulative work -- disconnecting", peer) peer.Disconnect() return } } } // Request more headers when the peer announced the maximum number of // headers that can be sent in a single message since it probably has more. if receivedMaxHeaders { blkLocator := chain.BlockLocatorFromHash(finalReceivedHash) locator := chainBlockLocatorToHashes(blkLocator) peer.PushGetHeadersMsg(locator, &zeroHash) m.progressLogger.LogHeaderProgress(uint64(len(headers)), headersSynced, m.headerSyncProgress) } // Consider the headers synced once the sync peer sends a message with a // final header that is within a few blocks of the sync height. if !headersSynced && peer == m.syncPeer { const syncHeightFetchOffset = 6 if int64(finalHeader.Height)+syncHeightFetchOffset > syncHeight { headersSynced = true m.hdrSyncState.headersSynced = headersSynced m.hdrSyncState.stopStallTimeout() m.progressLogger.LogHeaderProgress(uint64(len(headers)), headersSynced, m.headerSyncProgress) log.Infof("Initial headers sync complete (best header hash %s, "+ "height %d)", newBestHeaderHash, newBestHeaderHeight) log.Info("Syncing chain") m.progressLogger.SetLastLogTime(time.Now()) // Potentially update whether the chain believes it is current now // that the headers are synced. chain.MaybeUpdateIsCurrent() isChainCurrent = chain.IsCurrent() if isChainCurrent { m.onInitialChainSyncDone() } } } // Immediately download blocks associated with the announced headers once // the chain is current. This allows downloading from whichever peer // announces it first and also ensures any side chain blocks are downloaded // for vote consideration. // // Ultimately, it would likely be better for this functionality to be moved // to the code which determines the next blocks to request based on the // available headers once that code supports downloading from multiple peers // and associated infrastructure to efficiently determine which peers have // the associated block(s). if isChainCurrent { gdmsg := wire.NewMsgGetDataSizeHint(uint(len(headers))) for i := range headerHashes { // Skip the block when it has already been requested or is otherwise // already known. hash := &headerHashes[i] _, isRequestedBlock := m.requestedBlocks[*hash] if isRequestedBlock || chain.HaveBlock(hash) { continue } iv := wire.NewInvVect(wire.InvTypeBlock, hash) limitAdd(m.requestedBlocks, *hash, maxRequestedBlocks) limitAdd(peer.requestedBlocks, *hash, maxRequestedBlocks) gdmsg.AddInvVect(iv) } if len(gdmsg.InvList) > 0 { peer.QueueMessage(gdmsg, nil) } } // Download any blocks needed to catch the local chain up to the best known // header (if any) once the initial headers sync is done. if headersSynced && m.syncPeer != nil { m.fetchNextBlocks(m.syncPeer) } } // handleNotFoundMsg handles notfound messages from all peers. func (m *SyncManager) handleNotFoundMsg(nfmsg *notFoundMsg) { peer := lookupPeer(nfmsg.peer, m.peers) if peer == nil { return } for _, inv := range nfmsg.notFound.InvList { // verify the hash was actually announced by the peer // before deleting from the global requested maps. switch inv.Type { case wire.InvTypeBlock: if _, exists := peer.requestedBlocks[inv.Hash]; exists { delete(peer.requestedBlocks, inv.Hash) delete(m.requestedBlocks, inv.Hash) } case wire.InvTypeTx: if _, exists := peer.requestedTxns[inv.Hash]; exists { delete(peer.requestedTxns, inv.Hash) delete(m.requestedTxns, inv.Hash) } } } } // needTx returns whether or not the transaction needs to be downloaded. For // example, it does not need to be downloaded when it is already known. func (m *SyncManager) needTx(hash *chainhash.Hash) bool { // No need for transactions that have already been rejected. if m.rejectedTxns.Contains(hash[:]) { return false } // No need for transactions that are already available in the transaction // memory pool (main pool or orphan). if m.cfg.TxMemPool.HaveTransaction(hash) { return false } // No need for transactions that were recently confirmed. if m.cfg.RecentlyConfirmedTxns.Contains(hash[:]) { return false } return true } // handleInvMsg handles inv messages from all peers. This entails examining the // inventory advertised by the remote peer for block and transaction // announcements and acting accordingly. func (m *SyncManager) handleInvMsg(imsg *invMsg) { peer := lookupPeer(imsg.peer, m.peers) if peer == nil { return } isCurrent := m.IsCurrent() // Update state information regarding per-peer known inventory and determine // what inventory to request based on factors such as the current sync state // and whether or not the data is already available. // // Also, keep track of the final announced block (when there is one) so the // peer can be updated with that information accordingly. var lastBlock *wire.InvVect var requestQueue []*wire.InvVect for _, iv := range imsg.inv.InvList { switch iv.Type { case wire.InvTypeBlock: // NOTE: All block announcements are now made via headers and the // decisions regarding which blocks to download are based on those // headers. Therefore, there is no need to request anything here. // // Also, there realistically should not typically be any inv // messages with a type of block for the same reason. However, it // doesn't hurt to update the state accordingly just in case. // Add the block to the cache of known inventory for the peer. This // helps avoid sending blocks to the peer that it is already known // to have. peer.AddKnownInventory(iv) // Update the last block in the announced inventory. lastBlock = iv case wire.InvTypeTx: // Add the tx to the cache of known inventory for the peer. This // helps avoid sending transactions to the peer that it is already // known to have. peer.AddKnownInventory(iv) // Ignore transaction announcements before the chain is current or // are otherwise not needed, such as when they were recently // rejected or are already known. // // Transaction announcements are based on the state of the fully // synced ledger, so they are likely to be invalid before the chain // is current. if !isCurrent || !m.needTx(&iv.Hash) { continue } // Request the transaction if there is not one already pending. if _, exists := m.requestedTxns[iv.Hash]; !exists { limitAdd(m.requestedTxns, iv.Hash, maxRequestedTxns) limitAdd(peer.requestedTxns, iv.Hash, maxRequestedTxns) requestQueue = append(requestQueue, iv) } } } if lastBlock != nil { // Update the last announced block to the final one in the announced // inventory above (if any). In the case the header for that block is // already known, use that information to update the height for the peer // too. peer.lastAnnouncedBlock = &lastBlock.Hash if isCurrent { header, err := m.cfg.Chain.HeaderByHash(&lastBlock.Hash) if err == nil { peer.UpdateLastBlockHeight(int64(header.Height)) } } } // Request as much as possible at once. var numRequested int32 gdmsg := wire.NewMsgGetData() for _, iv := range requestQueue { gdmsg.AddInvVect(iv) numRequested++ if numRequested == wire.MaxInvPerMsg { // Send full getdata message and reset. // // NOTE: There should never be more than wire.MaxInvPerMsg in the // inv request, so this could return after the QueueMessage, but // this is safer. peer.QueueMessage(gdmsg, nil) gdmsg = wire.NewMsgGetData() numRequested = 0 } } if len(gdmsg.InvList) > 0 { peer.QueueMessage(gdmsg, nil) } } // limitAdd is a helper function for maps that require a maximum limit by // evicting a random value if adding the new value would cause it to // overflow the maximum allowed. func limitAdd(m map[chainhash.Hash]struct{}, hash chainhash.Hash, limit int) { if len(m)+1 > limit { // Remove a random entry from the map. For most compilers, Go's // range statement iterates starting at a random item although // that is not 100% guaranteed by the spec. The iteration order // is not important here because an adversary would have to be // able to pull off preimage attacks on the hashing function in // order to target eviction of specific entries anyways. for txHash := range m { delete(m, txHash) break } } m[hash] = struct{}{} } // eventHandler is the main handler for the sync manager. It must be run as a // goroutine. It processes block and inv messages in a separate goroutine from // the peer handlers so the block (MsgBlock) messages are handled by a single // thread without needing to lock memory data structures. This is important // because the sync manager controls which blocks are needed and how the // fetching should proceed. func (m *SyncManager) eventHandler(ctx context.Context) { out: for { select { case data := <-m.msgChan: switch msg := data.(type) { case *newPeerMsg: m.handleNewPeerMsg(ctx, msg.peer) case *txMsg: m.handleTxMsg(msg) select { case msg.reply <- struct{}{}: case <-ctx.Done(): } case *blockMsg: m.handleBlockMsg(msg) select { case msg.reply <- struct{}{}: case <-ctx.Done(): } case *invMsg: m.handleInvMsg(msg) case *headersMsg: m.handleHeadersMsg(msg) case *notFoundMsg: m.handleNotFoundMsg(msg) case *donePeerMsg: m.handleDonePeerMsg(msg.peer) case getSyncPeerMsg: var peerID int32 if m.syncPeer != nil { peerID = m.syncPeer.ID() } msg.reply <- peerID case requestFromPeerMsg: err := m.requestFromPeer(msg.peer, msg.blocks, msg.voteHashes, msg.tSpendHashes) msg.reply <- requestFromPeerResponse{ err: err, } case processBlockMsg: forkLen, err := m.processBlock(msg.block) if err != nil { msg.reply <- processBlockResponse{ forkLen: forkLen, err: err, } continue } onMainChain := forkLen == 0 if onMainChain { // Prune invalidated transactions. best := m.cfg.Chain.BestSnapshot() m.cfg.TxMemPool.PruneStakeTx(best.NextStakeDiff, best.Height) m.cfg.TxMemPool.PruneExpiredTx() } msg.reply <- processBlockResponse{ err: nil, } default: log.Warnf("Invalid message type in event handler: %T", msg) } case <-m.hdrSyncState.stallTimer.C: // Mark the timer's channel as having been drained so the timer can // safely be reset. m.hdrSyncState.stallChanDrained = true // Disconnect the sync peer due to stalling the header sync process. if m.syncPeer != nil { log.Debugf("Header sync progress stalled from peer %s -- "+ "disconnecting", m.syncPeer) m.syncPeer.Disconnect() } case <-ctx.Done(): break out } } m.wg.Done() log.Trace("Sync manager event handler done") } // NewPeer informs the sync manager of a newly active peer. func (m *SyncManager) NewPeer(peer *peerpkg.Peer) { select { case m.msgChan <- &newPeerMsg{peer: peer}: case <-m.quit: } } // QueueTx adds the passed transaction message and peer to the event handling // queue. func (m *SyncManager) QueueTx(tx *dcrutil.Tx, peer *peerpkg.Peer, done chan struct{}) { select { case m.msgChan <- &txMsg{tx: tx, peer: peer, reply: done}: case <-m.quit: done <- struct{}{} } } // QueueBlock adds the passed block message and peer to the event handling // queue. func (m *SyncManager) QueueBlock(block *dcrutil.Block, peer *peerpkg.Peer, done chan struct{}) { select { case m.msgChan <- &blockMsg{block: block, peer: peer, reply: done}: case <-m.quit: done <- struct{}{} } } // QueueInv adds the passed inv message and peer to the event handling queue. func (m *SyncManager) QueueInv(inv *wire.MsgInv, peer *peerpkg.Peer) { select { case m.msgChan <- &invMsg{inv: inv, peer: peer}: case <-m.quit: } } // QueueHeaders adds the passed headers message and peer to the event handling // queue. func (m *SyncManager) QueueHeaders(headers *wire.MsgHeaders, peer *peerpkg.Peer) { select { case m.msgChan <- &headersMsg{headers: headers, peer: peer}: case <-m.quit: } } // QueueNotFound adds the passed notfound message and peer to the event handling // queue. func (m *SyncManager) QueueNotFound(notFound *wire.MsgNotFound, peer *peerpkg.Peer) { select { case m.msgChan <- ¬FoundMsg{notFound: notFound, peer: peer}: case <-m.quit: } } // DonePeer informs the sync manager that a peer has disconnected. func (m *SyncManager) DonePeer(peer *peerpkg.Peer) { select { case m.msgChan <- &donePeerMsg{peer: peer}: case <-m.quit: } } // SyncPeerID returns the ID of the current sync peer, or 0 if there is none. func (m *SyncManager) SyncPeerID() int32 { reply := make(chan int32, 1) select { case m.msgChan <- getSyncPeerMsg{reply: reply}: case <-m.quit: } select { case peerID := <-reply: return peerID case <-m.quit: return 0 } } // RequestFromPeer allows an outside caller to request blocks or transactions // from a peer. The requests are logged in the internal map of requests so the // peer is not later banned for sending the respective data. func (m *SyncManager) RequestFromPeer(p *peerpkg.Peer, blocks, voteHashes, tSpendHashes []chainhash.Hash) error { reply := make(chan requestFromPeerResponse, 1) request := requestFromPeerMsg{ peer: p, blocks: blocks, voteHashes: voteHashes, tSpendHashes: tSpendHashes, reply: reply, } select { case m.msgChan <- request: case <-m.quit: } select { case response := <-reply: return response.err case <-m.quit: return fmt.Errorf("sync manager stopped") } } func (m *SyncManager) requestFromPeer(p *peerpkg.Peer, blocks, voteHashes, tSpendHashes []chainhash.Hash) error { peer := lookupPeer(p, m.peers) if peer == nil { return fmt.Errorf("unknown peer %s", p) } // Add the blocks to the request. msgResp := wire.NewMsgGetData() for i := range blocks { // If we've already requested this block, skip it. bh := &blocks[i] _, alreadyReqP := peer.requestedBlocks[*bh] _, alreadyReqB := m.requestedBlocks[*bh] if alreadyReqP || alreadyReqB { continue } // Skip the block when it is already known. if m.cfg.Chain.HaveBlock(bh) { continue } err := msgResp.AddInvVect(wire.NewInvVect(wire.InvTypeBlock, bh)) if err != nil { return fmt.Errorf("unexpected error encountered building request "+ "for mining state block %v: %v", bh, err.Error()) } peer.requestedBlocks[*bh] = struct{}{} m.requestedBlocks[*bh] = struct{}{} } addTxsToRequest := func(txs []chainhash.Hash, txType stake.TxType) error { // Return immediately if txs is nil. if txs == nil { return nil } for i := range txs { // If we've already requested this transaction, skip it. tx := &txs[i] _, alreadyReqP := peer.requestedTxns[*tx] _, alreadyReqB := m.requestedTxns[*tx] if alreadyReqP || alreadyReqB { continue } // Ask the transaction memory pool if the transaction is known // to it in any form (main pool or orphan). if m.cfg.TxMemPool.HaveTransaction(tx) { continue } // Check if the transaction exists from the point of view of the main // chain tip. Note that this is only a best effort since it is expensive // to check existence of every output and the only purpose of this check // is to avoid requesting already known transactions. // // Check for a specific outpoint based on the tx type. outpoint := wire.OutPoint{Hash: *tx} switch txType { case stake.TxTypeSSGen: // The first two outputs of vote transactions are OP_RETURN , and // therefore never exist as an unspent txo. Use the third output, as // the third output (and subsequent outputs) are OP_SSGEN outputs. outpoint.Index = 2 outpoint.Tree = wire.TxTreeStake case stake.TxTypeTSpend: // The first output of a tSpend transaction is OP_RETURN , and // therefore never exists as an unspent txo. Use the second output, as // the second output (and subsequent outputs) are OP_TGEN outputs. outpoint.Index = 1 outpoint.Tree = wire.TxTreeStake } entry, err := m.cfg.Chain.FetchUtxoEntry(outpoint) if err != nil { return err } if entry != nil { continue } err = msgResp.AddInvVect(wire.NewInvVect(wire.InvTypeTx, tx)) if err != nil { return fmt.Errorf("unexpected error encountered building request "+ "for mining state vote %v: %v", tx, err.Error()) } peer.requestedTxns[*tx] = struct{}{} m.requestedTxns[*tx] = struct{}{} } return nil } // Add the vote transactions to the request. err := addTxsToRequest(voteHashes, stake.TxTypeSSGen) if err != nil { return err } // Add the tspend transactions to the request. err = addTxsToRequest(tSpendHashes, stake.TxTypeTSpend) if err != nil { return err } if len(msgResp.InvList) > 0 { p.QueueMessage(msgResp, nil) } return nil } // ProcessBlock makes use of ProcessBlock on an internal instance of a block // chain. It is funneled through the sync manager since blockchain is not safe // for concurrent access. func (m *SyncManager) ProcessBlock(block *dcrutil.Block) error { reply := make(chan processBlockResponse, 1) select { case m.msgChan <- processBlockMsg{block: block, reply: reply}: case <-m.quit: } select { case response := <-reply: return response.err case <-m.quit: return fmt.Errorf("sync manager stopped") } } // IsCurrent returns whether or not the sync manager believes it is synced with // the connected peers. // // This function is safe for concurrent access. func (m *SyncManager) IsCurrent() bool { m.isCurrentMtx.Lock() m.maybeUpdateIsCurrent() isCurrent := m.isCurrent m.isCurrentMtx.Unlock() return isCurrent } // Run starts the sync manager and all other goroutines necessary for it to // function properly and blocks until the provided context is cancelled. func (m *SyncManager) Run(ctx context.Context) { log.Trace("Starting sync manager") // Start the event handler goroutine. m.wg.Add(1) go m.eventHandler(ctx) // Shutdown the sync manager when the context is cancelled. m.wg.Add(1) go func(ctx context.Context) { <-ctx.Done() close(m.quit) m.wg.Done() }(ctx) m.wg.Wait() log.Trace("Sync manager stopped") } // Config holds the configuration options related to the network chain // synchronization manager. type Config struct { // PeerNotifier specifies an implementation to use for notifying peers of // status changes related to blocks and transactions. PeerNotifier PeerNotifier // ChainParams identifies which chain parameters the manager is associated // with. ChainParams *chaincfg.Params // Chain specifies the chain instance to use for processing blocks and // transactions. Chain *blockchain.BlockChain // TimeSource defines the median time source which is used to retrieve the // current time adjusted by the median time offset. TimeSource blockchain.MedianTimeSource // TxMemPool specifies the mempool to use for processing transactions. TxMemPool *mempool.TxPool // NoMiningStateSync indicates whether or not the sync manager should // perform an initial mining state synchronization with peers once they are // believed to be fully synced. NoMiningStateSync bool // MaxPeers specifies the maximum number of peers the server is expected to // be connected with. It is primarily used as a hint for more efficient // synchronization. MaxPeers int // MaxOrphanTxs specifies the maximum number of orphan transactions the // transaction pool associated with the server supports. MaxOrphanTxs int // RecentlyConfirmedTxns specifies a size limited set to use for tracking // and querying the most recently confirmed transactions. It is useful for // preventing duplicate requests. RecentlyConfirmedTxns *apbf.Filter } // New returns a new network chain synchronization manager. Use Run to begin // processing asynchronous events. func New(config *Config) *SyncManager { return &SyncManager{ cfg: *config, rejectedTxns: apbf.NewFilter(maxRejectedTxns, rejectedTxnsFPRate), requestedTxns: make(map[chainhash.Hash]struct{}), requestedBlocks: make(map[chainhash.Hash]struct{}), peers: make(map[*peerpkg.Peer]*syncMgrPeer), hdrSyncState: makeHeaderSyncState(), progressLogger: progresslog.New("Processed", log), msgChan: make(chan interface{}, config.MaxPeers*3), quit: make(chan struct{}), syncHeight: config.Chain.BestSnapshot().Height, isCurrent: config.Chain.IsCurrent(), } }