Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi: Rework sync model to use hdr annoucements. #2555

Merged
merged 10 commits into from
Jan 22, 2021
40 changes: 36 additions & 4 deletions blockchain/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,19 @@ func (b *BlockChain) DisableVerify(disable bool) {
b.chainLock.Unlock()
}

// HaveHeader returns whether or not the chain instance has the block header
// represented by the passed hash. Note that this will return true for both the
// main chain and any side chains.
//
// This function is safe for concurrent access.
func (b *BlockChain) HaveHeader(hash *chainhash.Hash) bool {
b.index.RLock()
node := b.index.index[*hash]
headerKnown := node != nil
b.index.RUnlock()
return headerKnown
}

// HaveBlock returns whether or not the chain instance has the block represented
// by the passed hash. This includes checking the various places a block can
// be like part of the main chain or on a side chain.
Expand Down Expand Up @@ -1436,7 +1449,7 @@ func (b *BlockChain) isOldTimestamp(node *blockNode) bool {
}

// maybeUpdateIsCurrent potentially updates whether or not the chain believes it
// is current.
// is current using the provided best chain tip.
//
// It makes use of a latching approach such that once the chain becomes current
// it will only switch back to false in the case no new blocks have been seen
Expand Down Expand Up @@ -1474,7 +1487,20 @@ func (b *BlockChain) maybeUpdateIsCurrent(curBest *blockNode) {
log.Debugf("Chain latched to current at block %s (height %d)",
curBest.hash, curBest.height)
}
}

// MaybeUpdateIsCurrent potentially updates whether or not the chain believes it
// is current.
//
// It makes use of a latching approach such that once the chain becomes current
// it will only switch back to false in the case no new blocks have been seen
// for an extended period of time.
//
// This function is safe for concurrent access.
func (b *BlockChain) MaybeUpdateIsCurrent() {
b.chainLock.Lock()
b.maybeUpdateIsCurrent(b.bestChain.Tip())
b.chainLock.Unlock()
}

// isCurrent returns whether or not the chain believes it is current based on
Expand Down Expand Up @@ -2188,10 +2214,16 @@ func New(ctx context.Context, config *Config) (*BlockChain, error) {
"%d, block index: %d", b.dbInfo.version, b.dbInfo.compVer,
b.dbInfo.bidxVer)

b.index.RLock()
bestHdr := b.index.bestHeader
b.index.RUnlock()
log.Infof("Best known header: height %d, hash %v", bestHdr.height,
bestHdr.hash)

tip := b.bestChain.Tip()
log.Infof("Chain state: height %d, hash %v, total transactions %d, "+
"work %v, stake version %v", tip.height, tip.hash,
b.stateSnapshot.TotalTxns, tip.workSum, 0)
log.Infof("Chain state: height %d, hash %v, total transactions %d, work "+
"%v, progress %0.2f%%", tip.height, tip.hash,
b.stateSnapshot.TotalTxns, tip.workSum, b.VerifyProgress())

return &b, nil
}
98 changes: 97 additions & 1 deletion blockchain/chainquery.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Copyright (c) 2018-2020 The Decred developers
// Copyright (c) 2018-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package blockchain

import (
"bytes"
"math"
"sort"

"github.com/decred/dcrd/chaincfg/chainhash"
Expand Down Expand Up @@ -154,3 +155,98 @@ func (b *BlockChain) BestInvalidHeader() chainhash.Hash {
b.index.RUnlock()
return hash
}

// NextNeededBlocks returns hashes for the next blocks after the current best
// chain tip that are needed to make progress towards the current best known
// header skipping any blocks that are already known or in the provided map of
// blocks to exclude. Typically the caller would want to exclude all blocks
// that have outstanding requests.
//
// The maximum number of results is limited to the provided value or the maximum
// allowed by the internal lookahead buffer in the case the requested number of
// max results exceeds that value.
//
// This function is safe for concurrent access.
func (b *BlockChain) NextNeededBlocks(maxResults uint8, exclude map[chainhash.Hash]struct{}) []*chainhash.Hash {
// Nothing to do when no results are requested.
if maxResults == 0 {
return nil
}

// Determine the common ancestor between the current best chain tip and the
// current best known header. In practice this should never be nil because
// orphan headers are not allowed into the block index, but be paranoid and
// check anyway in case things change in the future.
b.index.RLock()
bestHeader := b.index.bestHeader
fork := b.bestChain.FindFork(bestHeader)
if fork == nil {
b.index.RUnlock()
return nil
}

// Determine the final block to consider for determining the next needed
// blocks by determining the descendants of the current best chain tip on
// the branch that leads to the best known header while clamping the number
// of descendants to consider to a lookahead buffer.
const lookaheadBuffer = 512
numBlocksToConsider := bestHeader.height - fork.height
if numBlocksToConsider == 0 {
b.index.RUnlock()
return nil
}
if numBlocksToConsider > lookaheadBuffer {
bestHeader = bestHeader.Ancestor(fork.height + lookaheadBuffer)
numBlocksToConsider = lookaheadBuffer
}

// Walk backwards from the final block to consider to the current best chain
// tip excluding any blocks that already have their data available or that
// the caller asked to be excluded (likely because they've already been
// requested).
neededBlocks := make([]*chainhash.Hash, 0, numBlocksToConsider)
for node := bestHeader; node != nil && node != fork; node = node.parent {
_, isExcluded := exclude[node.hash]
if isExcluded || node.status.HaveData() {
continue
}

neededBlocks = append(neededBlocks, &node.hash)
}
b.index.RUnlock()

// Reverse the needed blocks so they are in forwards order.
reverse := func(s []*chainhash.Hash) {
slen := len(s)
for i := 0; i < slen/2; i++ {
s[i], s[slen-1-i] = s[slen-1-i], s[i]
}
}
reverse(neededBlocks)

// Clamp the number of results to the lower of the requested max or number
// available.
if int64(maxResults) > numBlocksToConsider {
maxResults = uint8(numBlocksToConsider)
}
if uint16(len(neededBlocks)) > uint16(maxResults) {
neededBlocks = neededBlocks[:maxResults]
}
return neededBlocks
}

// VerifyProgress returns a percentage that is a guess of the progress of the
// chain verification process.
//
// This function is safe for concurrent access.
func (b *BlockChain) VerifyProgress() float64 {
b.index.RLock()
bestHdr := b.index.bestHeader
b.index.RUnlock()
if bestHdr.height == 0 {
return 0.0
}

tip := b.bestChain.Tip()
return math.Min(float64(tip.height)/float64(bestHdr.height), 1.0) * 100
}
4 changes: 2 additions & 2 deletions blockchain/checkpoints.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2020 The Decred 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.

Expand Down Expand Up @@ -65,7 +65,7 @@ func (b *BlockChain) verifyCheckpoint(height int64, hash *chainhash.Hash) bool {
return false
}

log.Infof("Verified checkpoint at height %d/block %s", checkpoint.Height,
log.Debugf("Verified checkpoint at height %d/block %s", checkpoint.Height,
checkpoint.Hash)
return true
}
Expand Down
8 changes: 6 additions & 2 deletions blockdb.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2020 The Decred 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.

Expand Down Expand Up @@ -238,7 +238,11 @@ func dumpBlockChain(params *chaincfg.Params, b *blockchain.BlockChain) error {
return err
}

progressLogger.LogBlockHeight(bl.MsgBlock(), tipHeight)
msgBlock := bl.MsgBlock()
forceLog := int64(msgBlock.Header.Height) >= tipHeight
progressLogger.LogProgress(msgBlock, forceLog, func() float64 {
return float64(msgBlock.Header.Height) / float64(tipHeight) * 100
})
}

srvrLog.Infof("Successfully dumped the blockchain (%v blocks) to %v.",
Expand Down
17 changes: 9 additions & 8 deletions internal/mining/cpuminer/cpuminer.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2014-2016 The btcsuite developers
// Copyright (c) 2015-2020 The Decred 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.

Expand Down Expand Up @@ -96,7 +96,7 @@ type Config struct {
// ProcessBlock defines the function to call with any solved blocks.
// It typically must run the provided block through the same set of
// rules and handling as any other block coming from the network.
ProcessBlock func(*dcrutil.Block, blockchain.BehaviorFlags) (bool, error)
ProcessBlock func(*dcrutil.Block, blockchain.BehaviorFlags) error

// ConnectedCount defines the function to use to obtain how many other
// peers the server is connected to. This is used by the automatic
Expand Down Expand Up @@ -200,8 +200,14 @@ func (m *CPUMiner) submitBlock(block *dcrutil.Block) bool {

// Process this block using the same rules as blocks coming from other
// nodes. This will in turn relay it to the network like normal.
isOrphan, err := m.cfg.ProcessBlock(block, blockchain.BFNone)
err := m.cfg.ProcessBlock(block, blockchain.BFNone)
if err != nil {
if errors.Is(err, blockchain.ErrMissingParent) {
log.Errorf("Block submitted via CPU miner is an orphan building "+
"on parent %v", block.MsgBlock().Header.PrevBlock)
return false
}

// Anything other than a rule violation is an unexpected error,
// so log that error as an internal error.
var rErr blockchain.RuleError
Expand All @@ -228,11 +234,6 @@ func (m *CPUMiner) submitBlock(block *dcrutil.Block) bool {
log.Errorf("Block submitted via CPU miner rejected: %v", err)
return false
}
if isOrphan {
log.Errorf("Block submitted via CPU miner is an orphan building on "+
"parent %v", block.MsgBlock().Header.PrevBlock)
return false
}

// The block was accepted.
coinbaseTxOuts := block.MsgBlock().Transactions[0].TxOut
Expand Down
11 changes: 10 additions & 1 deletion internal/netsync/log.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020 The Decred developers
// Copyright (c) 2020-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand All @@ -20,3 +20,12 @@ var log = slog.Disabled
func UseLogger(logger slog.Logger) {
log = logger
}

// pickNoun returns the singular or plural form of a noun depending on the
// provided count.
func pickNoun(n uint64, singular, plural string) string {
if n == 1 {
return singular
}
return plural
}
Loading