Skip to content

Commit

Permalink
multi: Use single latest checkpoint.
Browse files Browse the repository at this point in the history
This updates the blockchain checkpoints logic to use a single latest
checkpoint rather than multiple checkpoints.  The intermediate
checkpoints are no longer necessary because headers first syncing was
introduced, which discovers the most recent checkpoint before block
syncing even starts.
  • Loading branch information
rstaudt2 committed Oct 14, 2021
1 parent c74d1fe commit a65d6ec
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 90 deletions.
59 changes: 19 additions & 40 deletions blockchain/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,17 @@ type BlockChain struct {
// The following fields are set when the instance is created and can't
// be changed afterwards, so there is no need to protect them with a
// separate mutex.
checkpoints []chaincfg.Checkpoint
checkpointsByHeight map[int64]*chaincfg.Checkpoint
deploymentVers map[string]uint32
db database.DB
dbInfo *databaseInfo
chainParams *chaincfg.Params
timeSource MedianTimeSource
notifications NotificationCallback
sigCache *txscript.SigCache
indexSubscriber *indexers.IndexSubscriber
interrupt <-chan struct{}
utxoCache UtxoCacher
latestCheckpoint *chaincfg.Checkpoint
deploymentVers map[string]uint32
db database.DB
dbInfo *databaseInfo
chainParams *chaincfg.Params
timeSource MedianTimeSource
notifications NotificationCallback
sigCache *txscript.SigCache
indexSubscriber *indexers.IndexSubscriber
interrupt <-chan struct{}
utxoCache UtxoCacher

// subsidyCache is the cache that provides quick lookup of subsidy
// values.
Expand Down Expand Up @@ -820,8 +819,8 @@ func (b *BlockChain) connectBlock(node *blockNode, block, parent *dcrutil.Block,
// Optimization: Before checkpoints, immediately dump the parent's stake
// node because we no longer need it.
var latestCheckpointHeight int64
if len(b.checkpoints) > 0 {
latestCheckpointHeight = b.checkpoints[len(b.checkpoints)-1].Height
if b.latestCheckpoint != nil {
latestCheckpointHeight = b.latestCheckpoint.Height
}
if node.height < latestCheckpointHeight {
parent := b.bestChain.Tip().parent
Expand Down Expand Up @@ -2296,13 +2295,11 @@ type Config struct {
// This field is required.
ChainParams *chaincfg.Params

// Checkpoints specifies caller-defined checkpoints that are typically the
// default checkpoints in ChainParams or additional checkpoints added to
// them. Checkpoints must be sorted by height.
// LatestCheckpoint specifies the most recent known checkpoint that is
// typically the default checkpoint in ChainParams.
//
// This field can be nil if the caller does not wish to specify any
// checkpoints.
Checkpoints []chaincfg.Checkpoint
// This field can be nil if the caller does not wish to specify a checkpoint.
LatestCheckpoint *chaincfg.Checkpoint

// TimeSource defines the median time source to use for things such as
// block processing and determining whether or not the chain is current.
Expand Down Expand Up @@ -2362,25 +2359,8 @@ func New(ctx context.Context, config *Config) (*BlockChain, error) {
return nil, AssertError("blockchain.New chain parameters nil")
}

// Generate a checkpoint by height map from the provided checkpoints.
params := config.ChainParams
var checkpointsByHeight map[int64]*chaincfg.Checkpoint
var prevCheckpointHeight int64
if len(config.Checkpoints) > 0 {
checkpointsByHeight = make(map[int64]*chaincfg.Checkpoint)
for i := range config.Checkpoints {
checkpoint := &config.Checkpoints[i]
if checkpoint.Height <= prevCheckpointHeight {
return nil, AssertError("blockchain.New checkpoints are not " +
"sorted by height")
}

checkpointsByHeight[checkpoint.Height] = checkpoint
prevCheckpointHeight = checkpoint.Height
}
}

// Generate a deployment ID to version map from the provided params.
params := config.ChainParams
deploymentVers, err := extractDeploymentIDVersions(params)
if err != nil {
return nil, err
Expand All @@ -2394,8 +2374,7 @@ func New(ctx context.Context, config *Config) (*BlockChain, error) {
}

b := BlockChain{
checkpoints: config.Checkpoints,
checkpointsByHeight: checkpointsByHeight,
latestCheckpoint: config.LatestCheckpoint,
deploymentVers: deploymentVers,
db: config.DB,
chainParams: params,
Expand Down
5 changes: 2 additions & 3 deletions blockchain/chainio.go
Original file line number Diff line number Diff line change
Expand Up @@ -1471,13 +1471,12 @@ func (b *BlockChain) initChainState(ctx context.Context,
}

// Find the most recent checkpoint.
for i := len(b.checkpoints) - 1; i >= 0; i-- {
node := b.index.lookupNode(b.checkpoints[i].Hash)
if b.latestCheckpoint != nil {
node := b.index.lookupNode(b.latestCheckpoint.Hash)
if node != nil {
log.Debugf("Most recent checkpoint is %s (height %d)",
node.hash, node.height)
b.checkpointNode = node
break
}
}

Expand Down
41 changes: 13 additions & 28 deletions blockchain/checkpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,13 @@ import (
// best block chain that a good checkpoint candidate must be.
const CheckpointConfirmations = 4096

// Checkpoints returns a slice of checkpoints (regardless of whether they are
// already known). When checkpoints are disabled or there are no checkpoints
// for the active network, it will return nil.
//
// This function is safe for concurrent access.
func (b *BlockChain) Checkpoints() []chaincfg.Checkpoint {
if len(b.checkpoints) == 0 {
return nil
}

return b.checkpoints
}

// LatestCheckpoint returns the most recent checkpoint (regardless of whether it
// is already known). When checkpoints are disabled or there are no checkpoints
// for the active network, it will return nil.
//
// This function is safe for concurrent access.
func (b *BlockChain) LatestCheckpoint() *chaincfg.Checkpoint {
if len(b.checkpoints) == 0 {
return nil
}

return &b.checkpoints[len(b.checkpoints)-1]
return b.latestCheckpoint
}

// verifyCheckpoint returns whether the passed block height and hash combination
Expand All @@ -51,22 +34,21 @@ func (b *BlockChain) LatestCheckpoint() *chaincfg.Checkpoint {
//
// This function MUST be called with the chain lock held (for reads).
func (b *BlockChain) verifyCheckpoint(height int64, hash *chainhash.Hash) bool {
if len(b.checkpoints) == 0 {
if b.latestCheckpoint == nil {
return true
}

// Nothing to check if there is no checkpoint data for the block height.
checkpoint, exists := b.checkpointsByHeight[height]
if !exists {
if b.latestCheckpoint.Height != height {
return true
}

if *checkpoint.Hash != *hash {
if *b.latestCheckpoint.Hash != *hash {
return false
}

log.Debugf("Verified checkpoint at height %d/block %s", checkpoint.Height,
checkpoint.Hash)
log.Debugf("Verified checkpoint at height %d/block %s",
b.latestCheckpoint.Height, b.latestCheckpoint.Hash)
return true
}

Expand All @@ -75,20 +57,23 @@ func (b *BlockChain) verifyCheckpoint(height int64, hash *chainhash.Hash) bool {
//
// This function MUST be called with the chain lock held (for writes).
func (b *BlockChain) maybeUpdateMostRecentCheckpoint(node *blockNode) {
if len(b.checkpoints) == 0 {
if b.latestCheckpoint == nil {
return
}

// Nothing to update if there is no checkpoint data for the block height or
// the checkpoint hash does not match.
checkpoint, exists := b.checkpointsByHeight[node.height]
if !exists || node.hash != *checkpoint.Hash {
if node.height != b.latestCheckpoint.Height ||
node.hash != *b.latestCheckpoint.Hash {

return
}

// Update the previous checkpoint to current block so long as it is more
// recent than the existing previous checkpoint.
if b.checkpointNode == nil || b.checkpointNode.height < checkpoint.Height {
if b.checkpointNode == nil ||
b.checkpointNode.height < b.latestCheckpoint.Height {

log.Debugf("Most recent checkpoint updated to %s (height %d)",
node.hash, node.height)
b.checkpointNode = node
Expand Down
6 changes: 5 additions & 1 deletion chaincfg/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@ type Params struct {
BlockTaxProportion uint16

// Checkpoints ordered from oldest to newest.
//
// Note: Only the most recent checkpoint is used and all others are ignored.
// This is left as a slice to avoid a major version bump for the module and
// will likely be changed to only allow a single checkpoint in the future.
Checkpoints []Checkpoint

// MinKnownChainWork is the minimum amount of known total work for the chain
Expand All @@ -339,7 +343,7 @@ type Params struct {
// These fields are related to voting on consensus rule changes as
// defined by BIP0009.
//
// RuleChangeActivationQurom is the number of votes required for a vote
// RuleChangeActivationQuorum is the number of votes required for a vote
// to take effect.
//
// RuleChangeActivationInterval is the number of blocks in each threshold
Expand Down
19 changes: 14 additions & 5 deletions cmd/addblock/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/decred/dcrd/blockchain/v4"
"github.com/decred/dcrd/blockchain/v4/indexers"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/dcrd/database/v3"
"github.com/decred/dcrd/dcrutil/v4"
"github.com/decred/dcrd/wire"
Expand Down Expand Up @@ -311,13 +312,21 @@ func newBlockImporter(ctx context.Context, db database.DB, r io.ReadSeeker, canc
subber := indexers.NewIndexSubscriber(ctx)
subber.Run(ctx)

var latestCheckpoint *chaincfg.Checkpoint
numCheckpoints := len(activeNetParams.Checkpoints)
if numCheckpoints != 0 {
// Only use the most recent checkpoint, which is the last entry in the
// slice.
latestCheckpoint = &activeNetParams.Checkpoints[numCheckpoints-1]
}

chain, err := blockchain.New(context.Background(),
&blockchain.Config{
DB: db,
ChainParams: activeNetParams,
Checkpoints: activeNetParams.Checkpoints,
TimeSource: blockchain.NewMedianTime(),
IndexSubscriber: subber,
DB: db,
ChainParams: activeNetParams,
LatestCheckpoint: latestCheckpoint,
TimeSource: blockchain.NewMedianTime(),
IndexSubscriber: subber,
})
if err != nil {
return nil, err
Expand Down
29 changes: 16 additions & 13 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3465,9 +3465,12 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB,
s.feeEstimator = fe

// Only configure checkpoints when enabled.
var checkpoints []chaincfg.Checkpoint
if !cfg.DisableCheckpoints {
checkpoints = s.chainParams.Checkpoints
var latestCheckpoint *chaincfg.Checkpoint
numCheckpoints := len(s.chainParams.Checkpoints)
if !cfg.DisableCheckpoints && numCheckpoints != 0 {
// Only use the most recent checkpoint, which is the last entry in the
// slice.
latestCheckpoint = &s.chainParams.Checkpoints[numCheckpoints-1]
}

// Create a new block chain instance with the appropriate configuration.
Expand All @@ -3479,16 +3482,16 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB,
})
s.chain, err = blockchain.New(ctx,
&blockchain.Config{
DB: s.db,
UtxoBackend: utxoBackend,
ChainParams: s.chainParams,
Checkpoints: checkpoints,
TimeSource: s.timeSource,
Notifications: s.handleBlockchainNotification,
SigCache: s.sigCache,
SubsidyCache: s.subsidyCache,
IndexSubscriber: s.indexSubscriber,
UtxoCache: utxoCache,
DB: s.db,
UtxoBackend: utxoBackend,
ChainParams: s.chainParams,
LatestCheckpoint: latestCheckpoint,
TimeSource: s.timeSource,
Notifications: s.handleBlockchainNotification,
SigCache: s.sigCache,
SubsidyCache: s.subsidyCache,
IndexSubscriber: s.indexSubscriber,
UtxoCache: utxoCache,
})
if err != nil {
return nil, err
Expand Down

0 comments on commit a65d6ec

Please sign in to comment.