github.com/btcsuite/btcd@v0.24.0/cmd/findcheckpoint/findcheckpoint.go (about)

     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  
     5  package main
     6  
     7  import (
     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  
    18  const blockDbNamePrefix = "blocks"
    19  
    20  var (
    21  	cfg *config
    22  )
    23  
    24  // loadBlockDB opens the block database and returns a handle to it.
    25  func 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.
    42  func 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.
   123  func 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  
   135  func 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  }