blockchain: Allow adding additional checkpoints
Introduces a `--checkpoint` flag that allows the user to specify additional checkpoints or override the default ones provided in the chain params.
This commit is contained in:
parent
8caa921ac3
commit
c2af640c95
10 changed files with 161 additions and 71 deletions
|
@ -158,6 +158,7 @@ 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[int32]*chaincfg.Checkpoint
|
||||
db database.DB
|
||||
chainParams *chaincfg.Params
|
||||
|
@ -188,8 +189,7 @@ type BlockChain struct {
|
|||
|
||||
// These fields are configuration parameters that can be toggled at
|
||||
// runtime. They are protected by the chain lock.
|
||||
noVerify bool
|
||||
noCheckpoints bool
|
||||
noVerify bool
|
||||
|
||||
// These fields are related to the memory block index. They are
|
||||
// protected by the chain lock.
|
||||
|
@ -1569,7 +1569,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
|
|||
func (b *BlockChain) isCurrent() bool {
|
||||
// Not current if the latest main (best) chain height is before the
|
||||
// latest known good checkpoint (when checkpoints are enabled).
|
||||
checkpoint := b.latestCheckpoint()
|
||||
checkpoint := b.LatestCheckpoint()
|
||||
if checkpoint != nil && b.bestNode.height < checkpoint.Height {
|
||||
return false
|
||||
}
|
||||
|
@ -1640,6 +1640,12 @@ type Config struct {
|
|||
// This field is required.
|
||||
ChainParams *chaincfg.Params
|
||||
|
||||
// Checkpoints hold caller-defined checkpoints that should be added to the
|
||||
// default checkpoints in ChainParams. Checkpoints must be sorted by height.
|
||||
//
|
||||
// This field can be nil if the caller did not specify any checkpoints.
|
||||
Checkpoints []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.
|
||||
//
|
||||
|
@ -1688,20 +1694,21 @@ func New(config *Config) (*BlockChain, error) {
|
|||
}
|
||||
|
||||
// Generate a checkpoint by height map from the provided checkpoints.
|
||||
params := config.ChainParams
|
||||
var checkpointsByHeight map[int32]*chaincfg.Checkpoint
|
||||
if len(params.Checkpoints) > 0 {
|
||||
if len(config.Checkpoints) > 0 {
|
||||
checkpointsByHeight = make(map[int32]*chaincfg.Checkpoint)
|
||||
for i := range params.Checkpoints {
|
||||
checkpoint := ¶ms.Checkpoints[i]
|
||||
for i := range config.Checkpoints {
|
||||
checkpoint := &config.Checkpoints[i]
|
||||
checkpointsByHeight[checkpoint.Height] = checkpoint
|
||||
}
|
||||
}
|
||||
|
||||
params := config.ChainParams
|
||||
targetTimespan := int64(params.TargetTimespan)
|
||||
targetTimePerBlock := int64(params.TargetTimePerBlock)
|
||||
adjustmentFactor := params.RetargetAdjustmentFactor
|
||||
b := BlockChain{
|
||||
checkpoints: config.Checkpoints,
|
||||
checkpointsByHeight: checkpointsByHeight,
|
||||
db: config.DB,
|
||||
chainParams: params,
|
||||
|
|
|
@ -43,9 +43,7 @@ func TestHaveBlock(t *testing.T) {
|
|||
}
|
||||
defer teardownFunc()
|
||||
|
||||
// Since we're not dealing with the real block chain, disable
|
||||
// checkpoints and set the coinbase maturity to 1.
|
||||
chain.DisableCheckpoints(true)
|
||||
// Since we're not dealing with the real block chain, set the coinbase maturity to 1.
|
||||
chain.TstSetCoinbaseMaturity(1)
|
||||
|
||||
for i := 1; i < len(blocks); i++ {
|
||||
|
@ -131,9 +129,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
|||
}
|
||||
defer teardownFunc()
|
||||
|
||||
// Since we're not dealing with the real block chain, disable
|
||||
// checkpoints and set the coinbase maturity to 1.
|
||||
chain.DisableCheckpoints(true)
|
||||
// Since we're not dealing with the real block chain, set the coinbase maturity to 1.
|
||||
chain.TstSetCoinbaseMaturity(1)
|
||||
|
||||
// Load all the blocks into our test chain.
|
||||
|
|
|
@ -27,66 +27,39 @@ func newHashFromStr(hexStr string) *chainhash.Hash {
|
|||
return hash
|
||||
}
|
||||
|
||||
// DisableCheckpoints provides a mechanism to disable validation against
|
||||
// checkpoints which you DO NOT want to do in production. It is provided only
|
||||
// for debug purposes.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) DisableCheckpoints(disable bool) {
|
||||
b.chainLock.Lock()
|
||||
b.noCheckpoints = disable
|
||||
b.chainLock.Unlock()
|
||||
}
|
||||
|
||||
// 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.
|
||||
// already known).
|
||||
// When there are no checkpoints for the chain, it will return nil.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) Checkpoints() []chaincfg.Checkpoint {
|
||||
b.chainLock.RLock()
|
||||
defer b.chainLock.RUnlock()
|
||||
|
||||
if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return b.chainParams.Checkpoints
|
||||
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.
|
||||
// HasCheckpoints returns whether this BlockChain has checkpoints defined.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for reads).
|
||||
func (b *BlockChain) latestCheckpoint() *chaincfg.Checkpoint {
|
||||
if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
checkpoints := b.chainParams.Checkpoints
|
||||
return &checkpoints[len(checkpoints)-1]
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) HasCheckpoints() bool {
|
||||
return len(b.checkpoints) > 0
|
||||
}
|
||||
|
||||
// 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.
|
||||
// LatestCheckpoint returns the most recent checkpoint (regardless of whether they
|
||||
// are already known). When there are no defined checkpoints for the active chain
|
||||
// instance, it will return nil.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) LatestCheckpoint() *chaincfg.Checkpoint {
|
||||
b.chainLock.RLock()
|
||||
checkpoint := b.latestCheckpoint()
|
||||
b.chainLock.RUnlock()
|
||||
return checkpoint
|
||||
if !b.HasCheckpoints() {
|
||||
return nil
|
||||
}
|
||||
return &b.checkpoints[len(b.checkpoints)-1]
|
||||
}
|
||||
|
||||
// verifyCheckpoint returns whether the passed block height and hash combination
|
||||
// match the hard-coded checkpoint data. It also returns true if there is no
|
||||
// checkpoint data for the passed block height.
|
||||
//
|
||||
// This function MUST be called with the chain lock held (for reads).
|
||||
// match the checkpoint data. It also returns true if there is no checkpoint
|
||||
// data for the passed block height.
|
||||
func (b *BlockChain) verifyCheckpoint(height int32, hash *chainhash.Hash) bool {
|
||||
if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 {
|
||||
if !b.HasCheckpoints() {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -112,14 +85,14 @@ func (b *BlockChain) verifyCheckpoint(height int32, hash *chainhash.Hash) bool {
|
|||
//
|
||||
// This function MUST be called with the chain lock held (for reads).
|
||||
func (b *BlockChain) findPreviousCheckpoint() (*btcutil.Block, error) {
|
||||
if b.noCheckpoints || len(b.chainParams.Checkpoints) == 0 {
|
||||
if !b.HasCheckpoints() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// No checkpoints.
|
||||
checkpoints := b.chainParams.Checkpoints
|
||||
checkpoints := b.checkpoints
|
||||
numCheckpoints := len(checkpoints)
|
||||
if numCheckpoints == 0 {
|
||||
// No checkpoints.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -261,11 +234,6 @@ func (b *BlockChain) IsCheckpointCandidate(block *btcutil.Block) (bool, error) {
|
|||
b.chainLock.RLock()
|
||||
defer b.chainLock.RUnlock()
|
||||
|
||||
// Checkpoints must be enabled.
|
||||
if b.noCheckpoints {
|
||||
return false, fmt.Errorf("checkpoints are disabled")
|
||||
}
|
||||
|
||||
var isCandidate bool
|
||||
err := b.db.View(func(dbTx database.Tx) error {
|
||||
// A checkpoint must be in the main chain.
|
||||
|
|
|
@ -116,6 +116,7 @@ func chainSetup(dbName string, params *chaincfg.Params) (*blockchain.BlockChain,
|
|||
chain, err := blockchain.New(&blockchain.Config{
|
||||
DB: db,
|
||||
ChainParams: ¶msCopy,
|
||||
Checkpoints: nil,
|
||||
TimeSource: blockchain.NewMedianTime(),
|
||||
SigCache: txscript.NewSigCache(1000),
|
||||
})
|
||||
|
|
|
@ -52,9 +52,7 @@ func TestReorganization(t *testing.T) {
|
|||
}
|
||||
defer teardownFunc()
|
||||
|
||||
// Since we're not dealing with the real block chain, disable
|
||||
// checkpoints and set the coinbase maturity to 1.
|
||||
chain.DisableCheckpoints(true)
|
||||
// Since we're not dealing with the real block chain set the coinbase maturity to 1.
|
||||
chain.TstSetCoinbaseMaturity(1)
|
||||
|
||||
expectedOrphans := map[int]struct{}{5: {}, 6: {}}
|
||||
|
|
|
@ -1107,7 +1107,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi
|
|||
// will therefore be detected by the next checkpoint). This is a huge
|
||||
// optimization because running the scripts is the most time consuming
|
||||
// portion of block handling.
|
||||
checkpoint := b.latestCheckpoint()
|
||||
checkpoint := b.LatestCheckpoint()
|
||||
runScripts := !b.noVerify
|
||||
if checkpoint != nil && node.height <= checkpoint.Height {
|
||||
runScripts = false
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
@ -182,7 +183,7 @@ func (b *blockManager) findNextHeaderCheckpoint(height int32) *chaincfg.Checkpoi
|
|||
if cfg.DisableCheckpoints {
|
||||
return nil
|
||||
}
|
||||
checkpoints := b.server.chainParams.Checkpoints
|
||||
checkpoints := b.chain.Checkpoints()
|
||||
if len(checkpoints) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -1324,6 +1325,57 @@ func (b *blockManager) Pause() chan<- struct{} {
|
|||
return c
|
||||
}
|
||||
|
||||
// checkpointSorter implements sort.Interface to allow a slice of checkpoints to
|
||||
// be sorted.
|
||||
type checkpointSorter []chaincfg.Checkpoint
|
||||
|
||||
// Len returns the number of checkpoints in the slice. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s checkpointSorter) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Swap swaps the checkpoints at the passed indices. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s checkpointSorter) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less returns whether the checkpoint with index i should sort before the
|
||||
// checkpoint with index j. It is part of the sort.Interface implementation.
|
||||
func (s checkpointSorter) Less(i, j int) bool {
|
||||
return s[i].Height < s[j].Height
|
||||
}
|
||||
|
||||
// mergeCheckpoints returns two slices of checkpoints merged into one slice
|
||||
// such that the checkpoints are sorted by height. In the case the additional
|
||||
// checkpoints contain a checkpoint with the same height as a checkpoint in the
|
||||
// default checkpoints, the additional checkpoint will take precedence and
|
||||
// overwrite the default one.
|
||||
func mergeCheckpoints(defaultCheckpoints, additional []chaincfg.Checkpoint) []chaincfg.Checkpoint {
|
||||
// Create a map of the additional checkpoint heights to detect
|
||||
// duplicates.
|
||||
additionalHeights := make(map[int32]struct{})
|
||||
for _, checkpoint := range additional {
|
||||
additionalHeights[checkpoint.Height] = struct{}{}
|
||||
}
|
||||
|
||||
// Add all default checkpoints that do not have an override in the
|
||||
// additional checkpoints.
|
||||
numDefault := len(defaultCheckpoints)
|
||||
checkpoints := make([]chaincfg.Checkpoint, 0, numDefault+len(additional))
|
||||
for _, checkpoint := range defaultCheckpoints {
|
||||
if _, exists := additionalHeights[checkpoint.Height]; !exists {
|
||||
checkpoints = append(checkpoints, checkpoint)
|
||||
}
|
||||
}
|
||||
|
||||
// Append the additional checkpoints and return the sorted results.
|
||||
checkpoints = append(checkpoints, additional...)
|
||||
sort.Sort(checkpointSorter(checkpoints))
|
||||
return checkpoints
|
||||
}
|
||||
|
||||
// newBlockManager returns a new bitcoin block manager.
|
||||
// Use Start to begin processing asynchronous block and inv updates.
|
||||
func newBlockManager(s *server, indexManager blockchain.IndexManager) (*blockManager, error) {
|
||||
|
@ -1338,11 +1390,18 @@ func newBlockManager(s *server, indexManager blockchain.IndexManager) (*blockMan
|
|||
quit: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Merge given checkpoints with the default ones unless they are disabled.
|
||||
var checkpoints []chaincfg.Checkpoint
|
||||
if !cfg.DisableCheckpoints {
|
||||
checkpoints = mergeCheckpoints(s.chainParams.Checkpoints, cfg.addCheckpoints)
|
||||
}
|
||||
|
||||
// Create a new block chain instance with the appropriate configuration.
|
||||
var err error
|
||||
bm.chain, err = blockchain.New(&blockchain.Config{
|
||||
DB: s.db,
|
||||
ChainParams: s.chainParams,
|
||||
Checkpoints: checkpoints,
|
||||
TimeSource: s.timeSource,
|
||||
Notifications: bm.handleNotifyMsg,
|
||||
SigCache: s.sigCache,
|
||||
|
@ -1352,7 +1411,6 @@ func newBlockManager(s *server, indexManager blockchain.IndexManager) (*blockMan
|
|||
return nil, err
|
||||
}
|
||||
best := bm.chain.BestSnapshot()
|
||||
bm.chain.DisableCheckpoints(cfg.DisableCheckpoints)
|
||||
if !cfg.DisableCheckpoints {
|
||||
// Initialize the next checkpoint based on the current height.
|
||||
bm.nextCheckpoint = bm.findNextHeaderCheckpoint(best.Height)
|
||||
|
|
57
config.go
57
config.go
|
@ -20,6 +20,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/connmgr"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
_ "github.com/btcsuite/btcd/database/ffldb"
|
||||
|
@ -123,6 +125,7 @@ type config struct {
|
|||
TestNet3 bool `long:"testnet" description:"Use the test network"`
|
||||
RegressionTest bool `long:"regtest" description:"Use the regression test network"`
|
||||
SimNet bool `long:"simnet" description:"Use the simulation test network"`
|
||||
AddCheckpoints []string `long:"addcheckpoint" description:"Add a custom checkpoint. Format: '<height>:<hash>'"`
|
||||
DisableCheckpoints bool `long:"nocheckpoints" description:"Disable built-in checkpoints. Don't do this unless you know what you're doing."`
|
||||
DbType string `long:"dbtype" description:"Database backend to use for the Block Chain"`
|
||||
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
|
||||
|
@ -150,6 +153,7 @@ type config struct {
|
|||
lookup func(string) ([]net.IP, error)
|
||||
oniondial func(string, string, time.Duration) (net.Conn, error)
|
||||
dial func(string, string, time.Duration) (net.Conn, error)
|
||||
addCheckpoints []chaincfg.Checkpoint
|
||||
miningAddrs []btcutil.Address
|
||||
minRelayTxFee btcutil.Amount
|
||||
}
|
||||
|
@ -303,6 +307,49 @@ func normalizeAddresses(addrs []string, defaultPort string) []string {
|
|||
return removeDuplicateAddresses(addrs)
|
||||
}
|
||||
|
||||
// newCheckpointFromStr parses checkpoints in the '<height>:<hash>' format.
|
||||
func newCheckpointFromStr(checkpoint string) (chaincfg.Checkpoint, error) {
|
||||
parts := strings.Split(checkpoint, ":")
|
||||
if len(parts) != 2 {
|
||||
return chaincfg.Checkpoint{}, errors.New("checkpoints must use the " +
|
||||
"syntax '<height>:<hash>'")
|
||||
}
|
||||
|
||||
height, err := strconv.ParseInt(parts[0], 10, 32)
|
||||
if err != nil {
|
||||
return chaincfg.Checkpoint{}, fmt.Errorf("unable to parse checkpoint "+
|
||||
"due to malformed height: %s", parts[0])
|
||||
}
|
||||
|
||||
hash, err := chainhash.NewHashFromStr(parts[1])
|
||||
if err != nil {
|
||||
return chaincfg.Checkpoint{}, fmt.Errorf("unable to parse checkpoint "+
|
||||
"due to malformed hash: %s", parts[1])
|
||||
}
|
||||
|
||||
return chaincfg.Checkpoint{
|
||||
Height: int32(height),
|
||||
Hash: hash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseCheckpoints checks the checkpoint strings for valid syntax
|
||||
// ('<height>:<hash>') and parses them to chaincfg.Checkpoint instances.
|
||||
func parseCheckpoints(checkpointStrings []string) ([]chaincfg.Checkpoint, error) {
|
||||
if len(checkpointStrings) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
checkpoints := make([]chaincfg.Checkpoint, len(checkpointStrings))
|
||||
for i, cpString := range checkpointStrings {
|
||||
checkpoint, err := newCheckpointFromStr(cpString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
checkpoints[i] = checkpoint
|
||||
}
|
||||
return checkpoints, nil
|
||||
}
|
||||
|
||||
// filesExists reports whether the named file or directory exists.
|
||||
func fileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
|
@ -804,6 +851,16 @@ func loadConfig() (*config, []string, error) {
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Check the checkpoints for syntax errors.
|
||||
cfg.addCheckpoints, err = parseCheckpoints(cfg.AddCheckpoints)
|
||||
if err != nil {
|
||||
str := "%s: Error parsing checkpoints: %v"
|
||||
err := fmt.Errorf(str, funcName, err)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
fmt.Fprintln(os.Stderr, usageMessage)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Tor stream isolation requires either proxy or onion proxy to be set.
|
||||
if cfg.TorIsolation && cfg.Proxy == "" && cfg.OnionProxy == "" {
|
||||
str := "%s: Tor stream isolation requires either proxy or " +
|
||||
|
|
2
doc.go
2
doc.go
|
@ -74,6 +74,8 @@ Application Options:
|
|||
--testnet Use the test network
|
||||
--regtest Use the regression test network
|
||||
--simnet Use the simulation test network
|
||||
--addcheckpoint= Add ad additional checkpoint.
|
||||
Format: '<height>:<hash>'
|
||||
--nocheckpoints Disable built-in checkpoints. Don't do this unless
|
||||
you know what you're doing.
|
||||
--dbtype= Database backend to use for the Block Chain (ffldb)
|
||||
|
|
|
@ -153,6 +153,9 @@
|
|||
; Disable peer bloom filtering. See BIP0111.
|
||||
; nopeerbloomfilters=1
|
||||
|
||||
; Add additional checkpoints. Format: '<height>:<hash>'
|
||||
; addcheckpoint=<height>:<hash>
|
||||
|
||||
|
||||
; ------------------------------------------------------------------------------
|
||||
; RPC server options - The following options control the built-in RPC server
|
||||
|
|
Loading…
Reference in a new issue