lbcd/util/findcheckpoint/findcheckpoint.go
2015-01-16 15:13:21 -06:00

176 lines
4.9 KiB
Go

// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/btcsuite/btcwire"
"github.com/conformal/btcchain"
"github.com/conformal/btcdb"
_ "github.com/conformal/btcdb/ldb"
"github.com/conformal/btcnet"
)
const blockDbNamePrefix = "blocks"
var (
cfg *config
)
// loadBlockDB opens the block database and returns a handle to it.
func loadBlockDB() (btcdb.Db, error) {
// The database name is based on the database type.
dbType := cfg.DbType
dbName := blockDbNamePrefix + "_" + dbType
if dbType == "sqlite" {
dbName = dbName + ".db"
}
dbPath := filepath.Join(cfg.DataDir, dbName)
fmt.Printf("Loading block database from '%s'\n", dbPath)
db, err := btcdb.OpenDB(dbType, dbPath)
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.
func findCandidates(db btcdb.Db, latestHash *btcwire.ShaHash) ([]*btcnet.Checkpoint, error) {
// Start with the latest block of the main chain.
block, err := db.FetchBlockBySha(latestHash)
if err != nil {
return nil, err
}
// Setup chain and get the latest checkpoint. Ignore notifications
// since they aren't needed for this util.
chain := btcchain.New(db, activeNetParams, nil)
latestCheckpoint := chain.LatestCheckpoint()
if latestCheckpoint == nil {
return nil, fmt.Errorf("unable to retrieve latest checkpoint")
}
// The latest known block must be at least the last known checkpoint
// plus required checkpoint confirmations.
checkpointConfirmations := int64(btcchain.CheckpointConfirmations)
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)
}
// 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.
candidates := make([]*btcnet.Checkpoint, 0, cfg.NumCandidates)
numTested := int64(0)
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 {
candidateHash, err := block.Sha()
if err != nil {
return nil, err
}
checkpoint := btcnet.Checkpoint{
Height: block.Height(),
Hash: candidateHash,
}
candidates = append(candidates, &checkpoint)
}
prevHash := &block.MsgBlock().Header.PrevBlock
block, err = db.FetchBlockBySha(prevHash)
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.
func showCandidate(candidateNum int, checkpoint *btcnet.Checkpoint) {
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 {
fmt.Fprintf(os.Stderr, "failed to load database: %v\n", err)
return
}
defer db.Close()
// Get the latest block hash and height from the database and report
// status.
latestHash, height, err := db.NewestSha()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Printf("Block database loaded with block height %d\n", height)
// Find checkpoint candidates.
candidates, err := findCandidates(db, latestHash)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to identify candidates: %v", err)
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)
}
}