Add a new findcheckpoint utility.
This utility is useful to programatically identify checkpoint candidates which a developer can then do a more in-depth analysis on to choose an appropriate checkpoint.
This commit is contained in:
parent
905715e830
commit
689c699bc5
2 changed files with 274 additions and 0 deletions
99
util/findcheckpoint/config.go
Normal file
99
util/findcheckpoint/config.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
// 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"
|
||||
"opensource.conformal.com/go/go-flags"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
minCandidates = 1
|
||||
maxCandidates = 20
|
||||
defaultNumCandidates = 5
|
||||
)
|
||||
|
||||
var (
|
||||
defaultDataDir = filepath.Join(btcdHomeDir(), "data")
|
||||
)
|
||||
|
||||
// config defines the configuration options for findcheckpoint.
|
||||
//
|
||||
// See loadConfig for details on the configuration load process.
|
||||
type config struct {
|
||||
DataDir string `short:"b" long:"datadir" description:"Location of the btcd data directory"`
|
||||
NumCandidates int `short:"n" long:"numcandidates" description:"Max num of checkpoint candidates to show {1-20}"`
|
||||
UseGoOutput bool `short:"g" long:"gooutput" description:"Display the candidates using Go syntax that is ready to insert into the btcchain checkpoint list"`
|
||||
}
|
||||
|
||||
// btcdHomeDir returns an OS appropriate home directory for btcd.
|
||||
func btcdHomeDir() string {
|
||||
// Search for Windows APPDATA first. This won't exist on POSIX OSes.
|
||||
appData := os.Getenv("APPDATA")
|
||||
if appData != "" {
|
||||
return filepath.Join(appData, "btcd")
|
||||
}
|
||||
|
||||
// Fall back to standard HOME directory that works for most POSIX OSes.
|
||||
home := os.Getenv("HOME")
|
||||
if home != "" {
|
||||
return filepath.Join(home, ".btcd")
|
||||
}
|
||||
|
||||
// In the worst case, use the current directory.
|
||||
return "."
|
||||
}
|
||||
|
||||
// loadConfig initializes and parses the config using command line options.
|
||||
//
|
||||
// The configuration proceeds as follows:
|
||||
// 1) Start with a default config with sane settings
|
||||
// 2) Pre-parse the command line to check for an alternative config file
|
||||
// 3) Load configuration file overwriting defaults with any specified options
|
||||
// 4) Parse CLI options and overwrite/add any specified options
|
||||
//
|
||||
// The above results in btcd functioning properly without any config settings
|
||||
// while still allowing the user to override settings with config files and
|
||||
// command line options. Command line options always take precedence.
|
||||
func loadConfig() (*config, []string, error) {
|
||||
// Default config.
|
||||
cfg := config{
|
||||
DataDir: defaultDataDir,
|
||||
NumCandidates: defaultNumCandidates,
|
||||
}
|
||||
|
||||
// Parse command line options.
|
||||
parser := flags.NewParser(&cfg, flags.Default)
|
||||
remainingArgs, err := parser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
parser.WriteHelp(os.Stderr)
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Validate the number of candidates.
|
||||
if cfg.NumCandidates < minCandidates || cfg.NumCandidates > maxCandidates {
|
||||
str := "%s: The specified number of candidates is out of " +
|
||||
"range -- parsed [%v]"
|
||||
err = fmt.Errorf(str, "loadConfig", cfg.NumCandidates)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
parser.WriteHelp(os.Stderr)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Append the network type to the data directory so it is "namespaced"
|
||||
// per network. In addition to the block database, there are other
|
||||
// pieces of data that are saved to disk such as address manager state.
|
||||
// All data is specific to a network, so namespacing the data directory
|
||||
// means each individual piece of serialized data does not have to
|
||||
// worry about changing names per network and such.
|
||||
//cfg.DataDir = cleanAndExpandPath(cfg.DataDir)
|
||||
cfg.DataDir = filepath.Join(cfg.DataDir, "mainnet")
|
||||
|
||||
return &cfg, remainingArgs, nil
|
||||
}
|
175
util/findcheckpoint/findcheckpoint.go
Normal file
175
util/findcheckpoint/findcheckpoint.go
Normal file
|
@ -0,0 +1,175 @@
|
|||
// 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"
|
||||
"github.com/conformal/btcchain"
|
||||
"github.com/conformal/btcdb"
|
||||
_ "github.com/conformal/btcdb/ldb"
|
||||
_ "github.com/conformal/btcdb/sqlite3"
|
||||
"github.com/conformal/btcwire"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
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 := "leveldb"
|
||||
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) ([]*btcchain.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, btcwire.MainNet, 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.
|
||||
var candidates []*btcchain.Checkpoint
|
||||
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 := btcchain.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 *btcchain.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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue