2015-08-26 06:03:18 +02:00
|
|
|
// Copyright (c) 2013-2016 The btcsuite developers
|
2013-10-04 17:12:24 +02:00
|
|
|
// Use of this source code is governed by an ISC
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2014-07-02 15:50:08 +02:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
|
2015-01-30 23:25:42 +01:00
|
|
|
"github.com/btcsuite/btcd/blockchain"
|
2015-02-06 06:18:27 +01:00
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
2016-08-08 21:04:33 +02:00
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
2015-08-26 11:54:55 +02:00
|
|
|
"github.com/btcsuite/btcd/database"
|
2013-10-04 17:12:24 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const blockDbNamePrefix = "blocks"
|
|
|
|
|
|
|
|
var (
|
|
|
|
cfg *config
|
|
|
|
)
|
|
|
|
|
|
|
|
// loadBlockDB opens the block database and returns a handle to it.
|
2015-08-26 06:03:18 +02:00
|
|
|
func loadBlockDB() (database.DB, error) {
|
2013-10-04 17:12:24 +02:00
|
|
|
// The database name is based on the database type.
|
2015-08-26 06:03:18 +02:00
|
|
|
dbName := blockDbNamePrefix + "_" + cfg.DbType
|
2013-10-04 17:12:24 +02:00
|
|
|
dbPath := filepath.Join(cfg.DataDir, dbName)
|
|
|
|
fmt.Printf("Loading block database from '%s'\n", dbPath)
|
2015-08-26 06:03:18 +02:00
|
|
|
db, err := database.Open(cfg.DbType, dbPath, activeNetParams.Net)
|
2013-10-04 17:12:24 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return db, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// findCandidates searches the chain backwards for checkpoint candidates and
|
|
|
|
// returns a slice of found candidates, if any. It also stops searching for
|
|
|
|
// candidates at the last checkpoint that is already hard coded into btcchain
|
|
|
|
// since there is no point in finding candidates before already existing
|
|
|
|
// checkpoints.
|
2016-08-08 21:04:33 +02:00
|
|
|
func findCandidates(chain *blockchain.BlockChain, latestHash *chainhash.Hash) ([]*chaincfg.Checkpoint, error) {
|
2013-10-04 17:12:24 +02:00
|
|
|
// Start with the latest block of the main chain.
|
2015-08-26 06:03:18 +02:00
|
|
|
block, err := chain.BlockByHash(latestHash)
|
2013-10-04 17:12:24 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-08-26 06:03:18 +02:00
|
|
|
// Get the latest known checkpoint.
|
2013-10-04 17:12:24 +02:00
|
|
|
latestCheckpoint := chain.LatestCheckpoint()
|
|
|
|
if latestCheckpoint == nil {
|
2016-02-19 22:52:55 +01:00
|
|
|
// Set the latest checkpoint to the genesis block if there isn't
|
|
|
|
// already one.
|
|
|
|
latestCheckpoint = &chaincfg.Checkpoint{
|
|
|
|
Hash: activeNetParams.GenesisHash,
|
|
|
|
Height: 0,
|
|
|
|
}
|
2013-10-04 17:12:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// The latest known block must be at least the last known checkpoint
|
|
|
|
// plus required checkpoint confirmations.
|
2015-08-08 04:20:49 +02:00
|
|
|
checkpointConfirmations := int32(blockchain.CheckpointConfirmations)
|
2013-10-04 17:12:24 +02:00
|
|
|
requiredHeight := latestCheckpoint.Height + checkpointConfirmations
|
|
|
|
if block.Height() < requiredHeight {
|
|
|
|
return nil, fmt.Errorf("the block database is only at height "+
|
|
|
|
"%d which is less than the latest checkpoint height "+
|
|
|
|
"of %d plus required confirmations of %d",
|
|
|
|
block.Height(), latestCheckpoint.Height,
|
|
|
|
checkpointConfirmations)
|
|
|
|
}
|
|
|
|
|
2016-02-19 22:52:55 +01:00
|
|
|
// For the first checkpoint, the required height is any block after the
|
|
|
|
// genesis block, so long as the chain has at least the required number
|
|
|
|
// of confirmations (which is enforced above).
|
|
|
|
if len(activeNetParams.Checkpoints) == 0 {
|
|
|
|
requiredHeight = 1
|
|
|
|
}
|
|
|
|
|
2013-10-04 17:12:24 +02:00
|
|
|
// Indeterminate progress setup.
|
|
|
|
numBlocksToTest := block.Height() - requiredHeight
|
|
|
|
progressInterval := (numBlocksToTest / 100) + 1 // min 1
|
|
|
|
fmt.Print("Searching for candidates")
|
|
|
|
defer fmt.Println()
|
|
|
|
|
|
|
|
// Loop backwards through the chain to find checkpoint candidates.
|
2015-02-06 06:18:27 +01:00
|
|
|
candidates := make([]*chaincfg.Checkpoint, 0, cfg.NumCandidates)
|
2015-08-08 04:20:49 +02:00
|
|
|
numTested := int32(0)
|
2013-10-04 17:12:24 +02:00
|
|
|
for len(candidates) < cfg.NumCandidates && block.Height() > requiredHeight {
|
|
|
|
// Display progress.
|
|
|
|
if numTested%progressInterval == 0 {
|
|
|
|
fmt.Print(".")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine if this block is a checkpoint candidate.
|
|
|
|
isCandidate, err := chain.IsCheckpointCandidate(block)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// All checks passed, so this node seems like a reasonable
|
|
|
|
// checkpoint candidate.
|
|
|
|
if isCandidate {
|
2015-02-06 06:18:27 +01:00
|
|
|
checkpoint := chaincfg.Checkpoint{
|
2013-10-04 17:12:24 +02:00
|
|
|
Height: block.Height(),
|
2016-08-08 21:04:33 +02:00
|
|
|
Hash: block.Hash(),
|
2013-10-04 17:12:24 +02:00
|
|
|
}
|
|
|
|
candidates = append(candidates, &checkpoint)
|
|
|
|
}
|
|
|
|
|
|
|
|
prevHash := &block.MsgBlock().Header.PrevBlock
|
2015-08-26 06:03:18 +02:00
|
|
|
block, err = chain.BlockByHash(prevHash)
|
2013-10-04 17:12:24 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
numTested++
|
|
|
|
}
|
|
|
|
return candidates, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// showCandidate display a checkpoint candidate using and output format
|
|
|
|
// determined by the configuration parameters. The Go syntax output
|
|
|
|
// uses the format the btcchain code expects for checkpoints added to the list.
|
2015-02-06 06:18:27 +01:00
|
|
|
func showCandidate(candidateNum int, checkpoint *chaincfg.Checkpoint) {
|
2013-10-04 17:12:24 +02:00
|
|
|
if cfg.UseGoOutput {
|
|
|
|
fmt.Printf("Candidate %d -- {%d, newShaHashFromStr(\"%v\")},\n",
|
|
|
|
candidateNum, checkpoint.Height, checkpoint.Hash)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("Candidate %d -- Height: %d, Hash: %v\n", candidateNum,
|
|
|
|
checkpoint.Height, checkpoint.Hash)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
// Load configuration and parse command line.
|
|
|
|
tcfg, _, err := loadConfig()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
cfg = tcfg
|
|
|
|
|
|
|
|
// Load the block database.
|
|
|
|
db, err := loadBlockDB()
|
|
|
|
if err != nil {
|
2016-02-19 22:52:55 +01:00
|
|
|
fmt.Fprintln(os.Stderr, "failed to load database:", err)
|
2013-10-04 17:12:24 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
2015-08-26 06:03:18 +02:00
|
|
|
// Setup chain. Ignore notifications since they aren't needed for this
|
|
|
|
// util.
|
|
|
|
chain, err := blockchain.New(&blockchain.Config{
|
|
|
|
DB: db,
|
|
|
|
ChainParams: activeNetParams,
|
2017-01-12 17:19:51 +01:00
|
|
|
TimeSource: blockchain.NewMedianTime(),
|
2015-08-26 06:03:18 +02:00
|
|
|
})
|
2013-10-04 17:12:24 +02:00
|
|
|
if err != nil {
|
2015-08-26 06:03:18 +02:00
|
|
|
fmt.Fprintf(os.Stderr, "failed to initialize chain: %v\n", err)
|
2013-10-04 17:12:24 +02:00
|
|
|
return
|
|
|
|
}
|
2015-08-26 06:03:18 +02:00
|
|
|
|
|
|
|
// Get the latest block hash and height from the database and report
|
|
|
|
// status.
|
|
|
|
best := chain.BestSnapshot()
|
|
|
|
fmt.Printf("Block database loaded with block height %d\n", best.Height)
|
2013-10-04 17:12:24 +02:00
|
|
|
|
|
|
|
// Find checkpoint candidates.
|
2017-02-01 22:25:25 +01:00
|
|
|
candidates, err := findCandidates(chain, &best.Hash)
|
2013-10-04 17:12:24 +02:00
|
|
|
if err != nil {
|
2016-02-19 22:52:55 +01:00
|
|
|
fmt.Fprintln(os.Stderr, "Unable to identify candidates:", err)
|
2013-10-04 17:12:24 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// No candidates.
|
|
|
|
if len(candidates) == 0 {
|
|
|
|
fmt.Println("No candidates found.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Show the candidates.
|
|
|
|
for i, checkpoint := range candidates {
|
|
|
|
showCandidate(i+1, checkpoint)
|
|
|
|
}
|
|
|
|
}
|