1// Copyright (c) 2013-2016 The btcsuite developers
2// Use of this source code is governed by an ISC
3// license that can be found in the LICENSE file.
4
5package main
6
7import (
8	"fmt"
9	"os"
10	"path/filepath"
11
12	"github.com/btcsuite/btcd/blockchain"
13	"github.com/btcsuite/btcd/chaincfg"
14	"github.com/btcsuite/btcd/chaincfg/chainhash"
15	"github.com/btcsuite/btcd/database"
16)
17
18const blockDbNamePrefix = "blocks"
19
20var (
21	cfg *config
22)
23
24// loadBlockDB opens the block database and returns a handle to it.
25func loadBlockDB() (database.DB, error) {
26	// The database name is based on the database type.
27	dbName := blockDbNamePrefix + "_" + cfg.DbType
28	dbPath := filepath.Join(cfg.DataDir, dbName)
29	fmt.Printf("Loading block database from '%s'\n", dbPath)
30	db, err := database.Open(cfg.DbType, dbPath, activeNetParams.Net)
31	if err != nil {
32		return nil, err
33	}
34	return db, nil
35}
36
37// findCandidates searches the chain backwards for checkpoint candidates and
38// returns a slice of found candidates, if any.  It also stops searching for
39// candidates at the last checkpoint that is already hard coded into btcchain
40// since there is no point in finding candidates before already existing
41// checkpoints.
42func findCandidates(chain *blockchain.BlockChain, latestHash *chainhash.Hash) ([]*chaincfg.Checkpoint, error) {
43	// Start with the latest block of the main chain.
44	block, err := chain.BlockByHash(latestHash)
45	if err != nil {
46		return nil, err
47	}
48
49	// Get the latest known checkpoint.
50	latestCheckpoint := chain.LatestCheckpoint()
51	if latestCheckpoint == nil {
52		// Set the latest checkpoint to the genesis block if there isn't
53		// already one.
54		latestCheckpoint = &chaincfg.Checkpoint{
55			Hash:   activeNetParams.GenesisHash,
56			Height: 0,
57		}
58	}
59
60	// The latest known block must be at least the last known checkpoint
61	// plus required checkpoint confirmations.
62	checkpointConfirmations := int32(blockchain.CheckpointConfirmations)
63	requiredHeight := latestCheckpoint.Height + checkpointConfirmations
64	if block.Height() < requiredHeight {
65		return nil, fmt.Errorf("the block database is only at height "+
66			"%d which is less than the latest checkpoint height "+
67			"of %d plus required confirmations of %d",
68			block.Height(), latestCheckpoint.Height,
69			checkpointConfirmations)
70	}
71
72	// For the first checkpoint, the required height is any block after the
73	// genesis block, so long as the chain has at least the required number
74	// of confirmations (which is enforced above).
75	if len(activeNetParams.Checkpoints) == 0 {
76		requiredHeight = 1
77	}
78
79	// Indeterminate progress setup.
80	numBlocksToTest := block.Height() - requiredHeight
81	progressInterval := (numBlocksToTest / 100) + 1 // min 1
82	fmt.Print("Searching for candidates")
83	defer fmt.Println()
84
85	// Loop backwards through the chain to find checkpoint candidates.
86	candidates := make([]*chaincfg.Checkpoint, 0, cfg.NumCandidates)
87	numTested := int32(0)
88	for len(candidates) < cfg.NumCandidates && block.Height() > requiredHeight {
89		// Display progress.
90		if numTested%progressInterval == 0 {
91			fmt.Print(".")
92		}
93
94		// Determine if this block is a checkpoint candidate.
95		isCandidate, err := chain.IsCheckpointCandidate(block)
96		if err != nil {
97			return nil, err
98		}
99
100		// All checks passed, so this node seems like a reasonable
101		// checkpoint candidate.
102		if isCandidate {
103			checkpoint := chaincfg.Checkpoint{
104				Height: block.Height(),
105				Hash:   block.Hash(),
106			}
107			candidates = append(candidates, &checkpoint)
108		}
109
110		prevHash := &block.MsgBlock().Header.PrevBlock
111		block, err = chain.BlockByHash(prevHash)
112		if err != nil {
113			return nil, err
114		}
115		numTested++
116	}
117	return candidates, nil
118}
119
120// showCandidate display a checkpoint candidate using and output format
121// determined by the configuration parameters.  The Go syntax output
122// uses the format the btcchain code expects for checkpoints added to the list.
123func showCandidate(candidateNum int, checkpoint *chaincfg.Checkpoint) {
124	if cfg.UseGoOutput {
125		fmt.Printf("Candidate %d -- {%d, newShaHashFromStr(\"%v\")},\n",
126			candidateNum, checkpoint.Height, checkpoint.Hash)
127		return
128	}
129
130	fmt.Printf("Candidate %d -- Height: %d, Hash: %v\n", candidateNum,
131		checkpoint.Height, checkpoint.Hash)
132
133}
134
135func main() {
136	// Load configuration and parse command line.
137	tcfg, _, err := loadConfig()
138	if err != nil {
139		return
140	}
141	cfg = tcfg
142
143	// Load the block database.
144	db, err := loadBlockDB()
145	if err != nil {
146		fmt.Fprintln(os.Stderr, "failed to load database:", err)
147		return
148	}
149	defer db.Close()
150
151	// Setup chain.  Ignore notifications since they aren't needed for this
152	// util.
153	chain, err := blockchain.New(&blockchain.Config{
154		DB:          db,
155		ChainParams: activeNetParams,
156		TimeSource:  blockchain.NewMedianTime(),
157	})
158	if err != nil {
159		fmt.Fprintf(os.Stderr, "failed to initialize chain: %v\n", err)
160		return
161	}
162
163	// Get the latest block hash and height from the database and report
164	// status.
165	best := chain.BestSnapshot()
166	fmt.Printf("Block database loaded with block height %d\n", best.Height)
167
168	// Find checkpoint candidates.
169	candidates, err := findCandidates(chain, &best.Hash)
170	if err != nil {
171		fmt.Fprintln(os.Stderr, "Unable to identify candidates:", err)
172		return
173	}
174
175	// No candidates.
176	if len(candidates) == 0 {
177		fmt.Println("No candidates found.")
178		return
179	}
180
181	// Show the candidates.
182	for i, checkpoint := range candidates {
183		showCandidate(i+1, checkpoint)
184	}
185}
186