Add vendor dir (#16) (#4)

* Add vendor dir so builds dont require dep

* Pin specific version go-eth version
This commit is contained in:
Matt K
2018-01-29 13:44:18 -06:00
committed by GitHub
parent 82119b3c4b
commit 293dd2e848
4319 changed files with 1448696 additions and 26 deletions
+132
View File
@@ -0,0 +1,132 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"os"
"path/filepath"
"runtime"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/blockchain/indexers"
"github.com/btcsuite/btcd/database"
"github.com/btcsuite/btcd/limits"
"github.com/btcsuite/btclog"
)
const (
// blockDbNamePrefix is the prefix for the btcd block database.
blockDbNamePrefix = "blocks"
)
var (
cfg *config
log btclog.Logger
)
// loadBlockDB opens the block database and returns a handle to it.
func loadBlockDB() (database.DB, error) {
// The database name is based on the database type.
dbName := blockDbNamePrefix + "_" + cfg.DbType
dbPath := filepath.Join(cfg.DataDir, dbName)
log.Infof("Loading block database from '%s'", dbPath)
db, err := database.Open(cfg.DbType, dbPath, activeNetParams.Net)
if err != nil {
// Return the error if it's not because the database doesn't
// exist.
if dbErr, ok := err.(database.Error); !ok || dbErr.ErrorCode !=
database.ErrDbDoesNotExist {
return nil, err
}
// Create the db if it does not exist.
err = os.MkdirAll(cfg.DataDir, 0700)
if err != nil {
return nil, err
}
db, err = database.Create(cfg.DbType, dbPath, activeNetParams.Net)
if err != nil {
return nil, err
}
}
log.Info("Block database loaded")
return db, nil
}
// realMain is the real main function for the utility. It is necessary to work
// around the fact that deferred functions do not run when os.Exit() is called.
func realMain() error {
// Load configuration and parse command line.
tcfg, _, err := loadConfig()
if err != nil {
return err
}
cfg = tcfg
// Setup logging.
backendLogger := btclog.NewBackend(os.Stdout)
defer os.Stdout.Sync()
log = backendLogger.Logger("MAIN")
database.UseLogger(backendLogger.Logger("BCDB"))
blockchain.UseLogger(backendLogger.Logger("CHAN"))
indexers.UseLogger(backendLogger.Logger("INDX"))
// Load the block database.
db, err := loadBlockDB()
if err != nil {
log.Errorf("Failed to load database: %v", err)
return err
}
defer db.Close()
fi, err := os.Open(cfg.InFile)
if err != nil {
log.Errorf("Failed to open file %v: %v", cfg.InFile, err)
return err
}
defer fi.Close()
// Create a block importer for the database and input file and start it.
// The done channel returned from start will contain an error if
// anything went wrong.
importer, err := newBlockImporter(db, fi)
if err != nil {
log.Errorf("Failed create block importer: %v", err)
return err
}
// Perform the import asynchronously. This allows blocks to be
// processed and read in parallel. The results channel returned from
// Import contains the statistics about the import including an error
// if something went wrong.
log.Info("Starting import")
resultsChan := importer.Import()
results := <-resultsChan
if results.err != nil {
log.Errorf("%v", results.err)
return results.err
}
log.Infof("Processed a total of %d blocks (%d imported, %d already "+
"known)", results.blocksProcessed, results.blocksImported,
results.blocksProcessed-results.blocksImported)
return nil
}
func main() {
// Use all processor cores and up some limits.
runtime.GOMAXPROCS(runtime.NumCPU())
if err := limits.SetLimits(); err != nil {
os.Exit(1)
}
// Work around defer not working after os.Exit()
if err := realMain(); err != nil {
os.Exit(1)
}
}
+161
View File
@@ -0,0 +1,161 @@
// Copyright (c) 2013-2016 The btcsuite developers
// 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/btcd/chaincfg"
"github.com/btcsuite/btcd/database"
_ "github.com/btcsuite/btcd/database/ffldb"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
flags "github.com/jessevdk/go-flags"
)
const (
defaultDbType = "ffldb"
defaultDataFile = "bootstrap.dat"
defaultProgress = 10
)
var (
btcdHomeDir = btcutil.AppDataDir("btcd", false)
defaultDataDir = filepath.Join(btcdHomeDir, "data")
knownDbTypes = database.SupportedDrivers()
activeNetParams = &chaincfg.MainNetParams
)
// 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"`
DbType string `long:"dbtype" description:"Database backend to use for the Block Chain"`
TestNet3 bool `long:"testnet" description:"Use the test network"`
RegressionTest bool `long:"regtest" description:"Use the regression test network"`
SimNet bool `long:"simnet" description:"Use the simulation test network"`
InFile string `short:"i" long:"infile" description:"File containing the block(s)"`
TxIndex bool `long:"txindex" description:"Build a full hash-based transaction index which makes all transactions available via the getrawtransaction RPC"`
AddrIndex bool `long:"addrindex" description:"Build a full address-based transaction index which makes the searchrawtransactions RPC available"`
Progress int `short:"p" long:"progress" description:"Show a progress message each time this number of seconds have passed -- Use 0 to disable progress announcements"`
}
// filesExists reports whether the named file or directory exists.
func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// validDbType returns whether or not dbType is a supported database type.
func validDbType(dbType string) bool {
for _, knownType := range knownDbTypes {
if dbType == knownType {
return true
}
}
return false
}
// netName returns the name used when referring to a bitcoin network. At the
// time of writing, btcd currently places blocks for testnet version 3 in the
// data and log directory "testnet", which does not match the Name field of the
// chaincfg parameters. This function can be used to override this directory name
// as "testnet" when the passed active network matches wire.TestNet3.
//
// A proper upgrade to move the data and log directories for this network to
// "testnet3" is planned for the future, at which point this function can be
// removed and the network parameter's name used instead.
func netName(chainParams *chaincfg.Params) string {
switch chainParams.Net {
case wire.TestNet3:
return "testnet"
default:
return chainParams.Name
}
}
// loadConfig initializes and parses the config using command line options.
func loadConfig() (*config, []string, error) {
// Default config.
cfg := config{
DataDir: defaultDataDir,
DbType: defaultDbType,
InFile: defaultDataFile,
Progress: defaultProgress,
}
// 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
}
// Multiple networks can't be selected simultaneously.
funcName := "loadConfig"
numNets := 0
// Count number of network flags passed; assign active network params
// while we're at it
if cfg.TestNet3 {
numNets++
activeNetParams = &chaincfg.TestNet3Params
}
if cfg.RegressionTest {
numNets++
activeNetParams = &chaincfg.RegressionNetParams
}
if cfg.SimNet {
numNets++
activeNetParams = &chaincfg.SimNetParams
}
if numNets > 1 {
str := "%s: The testnet, regtest, and simnet params can't be " +
"used together -- choose one of the three"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
parser.WriteHelp(os.Stderr)
return nil, nil, err
}
// Validate database type.
if !validDbType(cfg.DbType) {
str := "%s: The specified database type [%v] is invalid -- " +
"supported types %v"
err := fmt.Errorf(str, "loadConfig", cfg.DbType, knownDbTypes)
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 = filepath.Join(cfg.DataDir, netName(activeNetParams))
// Ensure the specified block file exists.
if !fileExists(cfg.InFile) {
str := "%s: The specified block file [%v] does not exist"
err := fmt.Errorf(str, "loadConfig", cfg.InFile)
fmt.Fprintln(os.Stderr, err)
parser.WriteHelp(os.Stderr)
return nil, nil, err
}
return &cfg, remainingArgs, nil
}
+353
View File
@@ -0,0 +1,353 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"encoding/binary"
"fmt"
"io"
"sync"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/blockchain/indexers"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/database"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
var zeroHash = chainhash.Hash{}
// importResults houses the stats and result as an import operation.
type importResults struct {
blocksProcessed int64
blocksImported int64
err error
}
// blockImporter houses information about an ongoing import from a block data
// file to the block database.
type blockImporter struct {
db database.DB
chain *blockchain.BlockChain
r io.ReadSeeker
processQueue chan []byte
doneChan chan bool
errChan chan error
quit chan struct{}
wg sync.WaitGroup
blocksProcessed int64
blocksImported int64
receivedLogBlocks int64
receivedLogTx int64
lastHeight int64
lastBlockTime time.Time
lastLogTime time.Time
}
// readBlock reads the next block from the input file.
func (bi *blockImporter) readBlock() ([]byte, error) {
// The block file format is:
// <network> <block length> <serialized block>
var net uint32
err := binary.Read(bi.r, binary.LittleEndian, &net)
if err != nil {
if err != io.EOF {
return nil, err
}
// No block and no error means there are no more blocks to read.
return nil, nil
}
if net != uint32(activeNetParams.Net) {
return nil, fmt.Errorf("network mismatch -- got %x, want %x",
net, uint32(activeNetParams.Net))
}
// Read the block length and ensure it is sane.
var blockLen uint32
if err := binary.Read(bi.r, binary.LittleEndian, &blockLen); err != nil {
return nil, err
}
if blockLen > wire.MaxBlockPayload {
return nil, fmt.Errorf("block payload of %d bytes is larger "+
"than the max allowed %d bytes", blockLen,
wire.MaxBlockPayload)
}
serializedBlock := make([]byte, blockLen)
if _, err := io.ReadFull(bi.r, serializedBlock); err != nil {
return nil, err
}
return serializedBlock, nil
}
// processBlock potentially imports the block into the database. It first
// deserializes the raw block while checking for errors. Already known blocks
// are skipped and orphan blocks are considered errors. Finally, it runs the
// block through the chain rules to ensure it follows all rules and matches
// up to the known checkpoint. Returns whether the block was imported along
// with any potential errors.
func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) {
// Deserialize the block which includes checks for malformed blocks.
block, err := btcutil.NewBlockFromBytes(serializedBlock)
if err != nil {
return false, err
}
// update progress statistics
bi.lastBlockTime = block.MsgBlock().Header.Timestamp
bi.receivedLogTx += int64(len(block.MsgBlock().Transactions))
// Skip blocks that already exist.
blockHash := block.Hash()
exists, err := bi.chain.HaveBlock(blockHash)
if err != nil {
return false, err
}
if exists {
return false, nil
}
// Don't bother trying to process orphans.
prevHash := &block.MsgBlock().Header.PrevBlock
if !prevHash.IsEqual(&zeroHash) {
exists, err := bi.chain.HaveBlock(prevHash)
if err != nil {
return false, err
}
if !exists {
return false, fmt.Errorf("import file contains block "+
"%v which does not link to the available "+
"block chain", prevHash)
}
}
// Ensure the blocks follows all of the chain rules and match up to the
// known checkpoints.
isMainChain, isOrphan, err := bi.chain.ProcessBlock(block,
blockchain.BFFastAdd)
if err != nil {
return false, err
}
if !isMainChain {
return false, fmt.Errorf("import file contains an block that "+
"does not extend the main chain: %v", blockHash)
}
if isOrphan {
return false, fmt.Errorf("import file contains an orphan "+
"block: %v", blockHash)
}
return true, nil
}
// readHandler is the main handler for reading blocks from the import file.
// This allows block processing to take place in parallel with block reads.
// It must be run as a goroutine.
func (bi *blockImporter) readHandler() {
out:
for {
// Read the next block from the file and if anything goes wrong
// notify the status handler with the error and bail.
serializedBlock, err := bi.readBlock()
if err != nil {
bi.errChan <- fmt.Errorf("Error reading from input "+
"file: %v", err.Error())
break out
}
// A nil block with no error means we're done.
if serializedBlock == nil {
break out
}
// Send the block or quit if we've been signalled to exit by
// the status handler due to an error elsewhere.
select {
case bi.processQueue <- serializedBlock:
case <-bi.quit:
break out
}
}
// Close the processing channel to signal no more blocks are coming.
close(bi.processQueue)
bi.wg.Done()
}
// logProgress logs block progress as an information message. In order to
// prevent spam, it limits logging to one message every cfg.Progress seconds
// with duration and totals included.
func (bi *blockImporter) logProgress() {
bi.receivedLogBlocks++
now := time.Now()
duration := now.Sub(bi.lastLogTime)
if duration < time.Second*time.Duration(cfg.Progress) {
return
}
// Truncate the duration to 10s of milliseconds.
durationMillis := int64(duration / time.Millisecond)
tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10)
// Log information about new block height.
blockStr := "blocks"
if bi.receivedLogBlocks == 1 {
blockStr = "block"
}
txStr := "transactions"
if bi.receivedLogTx == 1 {
txStr = "transaction"
}
log.Infof("Processed %d %s in the last %s (%d %s, height %d, %s)",
bi.receivedLogBlocks, blockStr, tDuration, bi.receivedLogTx,
txStr, bi.lastHeight, bi.lastBlockTime)
bi.receivedLogBlocks = 0
bi.receivedLogTx = 0
bi.lastLogTime = now
}
// processHandler is the main handler for processing blocks. This allows block
// processing to take place in parallel with block reads from the import file.
// It must be run as a goroutine.
func (bi *blockImporter) processHandler() {
out:
for {
select {
case serializedBlock, ok := <-bi.processQueue:
// We're done when the channel is closed.
if !ok {
break out
}
bi.blocksProcessed++
bi.lastHeight++
imported, err := bi.processBlock(serializedBlock)
if err != nil {
bi.errChan <- err
break out
}
if imported {
bi.blocksImported++
}
bi.logProgress()
case <-bi.quit:
break out
}
}
bi.wg.Done()
}
// statusHandler waits for updates from the import operation and notifies
// the passed doneChan with the results of the import. It also causes all
// goroutines to exit if an error is reported from any of them.
func (bi *blockImporter) statusHandler(resultsChan chan *importResults) {
select {
// An error from either of the goroutines means we're done so signal
// caller with the error and signal all goroutines to quit.
case err := <-bi.errChan:
resultsChan <- &importResults{
blocksProcessed: bi.blocksProcessed,
blocksImported: bi.blocksImported,
err: err,
}
close(bi.quit)
// The import finished normally.
case <-bi.doneChan:
resultsChan <- &importResults{
blocksProcessed: bi.blocksProcessed,
blocksImported: bi.blocksImported,
err: nil,
}
}
}
// Import is the core function which handles importing the blocks from the file
// associated with the block importer to the database. It returns a channel
// on which the results will be returned when the operation has completed.
func (bi *blockImporter) Import() chan *importResults {
// Start up the read and process handling goroutines. This setup allows
// blocks to be read from disk in parallel while being processed.
bi.wg.Add(2)
go bi.readHandler()
go bi.processHandler()
// Wait for the import to finish in a separate goroutine and signal
// the status handler when done.
go func() {
bi.wg.Wait()
bi.doneChan <- true
}()
// Start the status handler and return the result channel that it will
// send the results on when the import is done.
resultChan := make(chan *importResults)
go bi.statusHandler(resultChan)
return resultChan
}
// newBlockImporter returns a new importer for the provided file reader seeker
// and database.
func newBlockImporter(db database.DB, r io.ReadSeeker) (*blockImporter, error) {
// Create the transaction and address indexes if needed.
//
// CAUTION: the txindex needs to be first in the indexes array because
// the addrindex uses data from the txindex during catchup. If the
// addrindex is run first, it may not have the transactions from the
// current block indexed.
var indexes []indexers.Indexer
if cfg.TxIndex || cfg.AddrIndex {
// Enable transaction index if address index is enabled since it
// requires it.
if !cfg.TxIndex {
log.Infof("Transaction index enabled because it is " +
"required by the address index")
cfg.TxIndex = true
} else {
log.Info("Transaction index is enabled")
}
indexes = append(indexes, indexers.NewTxIndex(db))
}
if cfg.AddrIndex {
log.Info("Address index is enabled")
indexes = append(indexes, indexers.NewAddrIndex(db, activeNetParams))
}
// Create an index manager if any of the optional indexes are enabled.
var indexManager blockchain.IndexManager
if len(indexes) > 0 {
indexManager = indexers.NewManager(db, indexes)
}
chain, err := blockchain.New(&blockchain.Config{
DB: db,
ChainParams: activeNetParams,
TimeSource: blockchain.NewMedianTime(),
IndexManager: indexManager,
})
if err != nil {
return nil, err
}
return &blockImporter{
db: db,
r: r,
processQueue: make(chan []byte, 2),
doneChan: make(chan bool),
errChan: make(chan error),
quit: make(chan struct{}),
chain: chain,
lastLogTime: time.Now(),
}, nil
}
+167
View File
@@ -0,0 +1,167 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/btcsuite/btcd/btcjson"
)
const (
showHelpMessage = "Specify -h to show available options"
listCmdMessage = "Specify -l to list available commands"
)
// commandUsage display the usage for a specific command.
func commandUsage(method string) {
usage, err := btcjson.MethodUsageText(method)
if err != nil {
// This should never happen since the method was already checked
// before calling this function, but be safe.
fmt.Fprintln(os.Stderr, "Failed to obtain command usage:", err)
return
}
fmt.Fprintln(os.Stderr, "Usage:")
fmt.Fprintf(os.Stderr, " %s\n", usage)
}
// usage displays the general usage when the help flag is not displayed and
// and an invalid command was specified. The commandUsage function is used
// instead when a valid command was specified.
func usage(errorMessage string) {
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
fmt.Fprintln(os.Stderr, errorMessage)
fmt.Fprintln(os.Stderr, "Usage:")
fmt.Fprintf(os.Stderr, " %s [OPTIONS] <command> <args...>\n\n",
appName)
fmt.Fprintln(os.Stderr, showHelpMessage)
fmt.Fprintln(os.Stderr, listCmdMessage)
}
func main() {
cfg, args, err := loadConfig()
if err != nil {
os.Exit(1)
}
if len(args) < 1 {
usage("No command specified")
os.Exit(1)
}
// Ensure the specified method identifies a valid registered command and
// is one of the usable types.
method := args[0]
usageFlags, err := btcjson.MethodUsageFlags(method)
if err != nil {
fmt.Fprintf(os.Stderr, "Unrecognized command '%s'\n", method)
fmt.Fprintln(os.Stderr, listCmdMessage)
os.Exit(1)
}
if usageFlags&unusableFlags != 0 {
fmt.Fprintf(os.Stderr, "The '%s' command can only be used via "+
"websockets\n", method)
fmt.Fprintln(os.Stderr, listCmdMessage)
os.Exit(1)
}
// Convert remaining command line args to a slice of interface values
// to be passed along as parameters to new command creation function.
//
// Since some commands, such as submitblock, can involve data which is
// too large for the Operating System to allow as a normal command line
// parameter, support using '-' as an argument to allow the argument
// to be read from a stdin pipe.
bio := bufio.NewReader(os.Stdin)
params := make([]interface{}, 0, len(args[1:]))
for _, arg := range args[1:] {
if arg == "-" {
param, err := bio.ReadString('\n')
if err != nil && err != io.EOF {
fmt.Fprintf(os.Stderr, "Failed to read data "+
"from stdin: %v\n", err)
os.Exit(1)
}
if err == io.EOF && len(param) == 0 {
fmt.Fprintln(os.Stderr, "Not enough lines "+
"provided on stdin")
os.Exit(1)
}
param = strings.TrimRight(param, "\r\n")
params = append(params, param)
continue
}
params = append(params, arg)
}
// Attempt to create the appropriate command using the arguments
// provided by the user.
cmd, err := btcjson.NewCmd(method, params...)
if err != nil {
// Show the error along with its error code when it's a
// btcjson.Error as it reallistcally will always be since the
// NewCmd function is only supposed to return errors of that
// type.
if jerr, ok := err.(btcjson.Error); ok {
fmt.Fprintf(os.Stderr, "%s command: %v (code: %s)\n",
method, err, jerr.ErrorCode)
commandUsage(method)
os.Exit(1)
}
// The error is not a btcjson.Error and this really should not
// happen. Nevertheless, fallback to just showing the error
// if it should happen due to a bug in the package.
fmt.Fprintf(os.Stderr, "%s command: %v\n", method, err)
commandUsage(method)
os.Exit(1)
}
// Marshal the command into a JSON-RPC byte slice in preparation for
// sending it to the RPC server.
marshalledJSON, err := btcjson.MarshalCmd(1, cmd)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// Send the JSON-RPC request to the server using the user-specified
// connection configuration.
result, err := sendPostRequest(marshalledJSON, cfg)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// Choose how to display the result based on its type.
strResult := string(result)
if strings.HasPrefix(strResult, "{") || strings.HasPrefix(strResult, "[") {
var dst bytes.Buffer
if err := json.Indent(&dst, result, "", " "); err != nil {
fmt.Fprintf(os.Stderr, "Failed to format result: %v",
err)
os.Exit(1)
}
fmt.Println(dst.String())
} else if strings.HasPrefix(strResult, `"`) {
var str string
if err := json.Unmarshal(result, &str); err != nil {
fmt.Fprintf(os.Stderr, "Failed to unmarshal result: %v",
err)
os.Exit(1)
}
fmt.Println(str)
} else if strResult != "null" {
fmt.Println(strResult)
}
}
+349
View File
@@ -0,0 +1,349 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcutil"
flags "github.com/jessevdk/go-flags"
)
const (
// unusableFlags are the command usage flags which this utility are not
// able to use. In particular it doesn't support websockets and
// consequently notifications.
unusableFlags = btcjson.UFWebsocketOnly | btcjson.UFNotification
)
var (
btcdHomeDir = btcutil.AppDataDir("btcd", false)
btcctlHomeDir = btcutil.AppDataDir("btcctl", false)
btcwalletHomeDir = btcutil.AppDataDir("btcwallet", false)
defaultConfigFile = filepath.Join(btcctlHomeDir, "btcctl.conf")
defaultRPCServer = "localhost"
defaultRPCCertFile = filepath.Join(btcdHomeDir, "rpc.cert")
defaultWalletCertFile = filepath.Join(btcwalletHomeDir, "rpc.cert")
)
// listCommands categorizes and lists all of the usable commands along with
// their one-line usage.
func listCommands() {
const (
categoryChain uint8 = iota
categoryWallet
numCategories
)
// Get a list of registered commands and categorize and filter them.
cmdMethods := btcjson.RegisteredCmdMethods()
categorized := make([][]string, numCategories)
for _, method := range cmdMethods {
flags, err := btcjson.MethodUsageFlags(method)
if err != nil {
// This should never happen since the method was just
// returned from the package, but be safe.
continue
}
// Skip the commands that aren't usable from this utility.
if flags&unusableFlags != 0 {
continue
}
usage, err := btcjson.MethodUsageText(method)
if err != nil {
// This should never happen since the method was just
// returned from the package, but be safe.
continue
}
// Categorize the command based on the usage flags.
category := categoryChain
if flags&btcjson.UFWalletOnly != 0 {
category = categoryWallet
}
categorized[category] = append(categorized[category], usage)
}
// Display the command according to their categories.
categoryTitles := make([]string, numCategories)
categoryTitles[categoryChain] = "Chain Server Commands:"
categoryTitles[categoryWallet] = "Wallet Server Commands (--wallet):"
for category := uint8(0); category < numCategories; category++ {
fmt.Println(categoryTitles[category])
for _, usage := range categorized[category] {
fmt.Println(usage)
}
fmt.Println()
}
}
// config defines the configuration options for btcctl.
//
// See loadConfig for details on the configuration load process.
type config struct {
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
ListCommands bool `short:"l" long:"listcommands" description:"List all of the supported commands and exit"`
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
RPCUser string `short:"u" long:"rpcuser" description:"RPC username"`
RPCPassword string `short:"P" long:"rpcpass" default-mask:"-" description:"RPC password"`
RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"`
RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"`
NoTLS bool `long:"notls" description:"Disable TLS"`
Proxy string `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
ProxyUser string `long:"proxyuser" description:"Username for proxy server"`
ProxyPass string `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
TestNet3 bool `long:"testnet" description:"Connect to testnet"`
SimNet bool `long:"simnet" description:"Connect to the simulation test network"`
TLSSkipVerify bool `long:"skipverify" description:"Do not verify tls certificates (not recommended!)"`
Wallet bool `long:"wallet" description:"Connect to wallet"`
}
// normalizeAddress returns addr with the passed default port appended if
// there is not already a port specified.
func normalizeAddress(addr string, useTestNet3, useSimNet, useWallet bool) string {
_, _, err := net.SplitHostPort(addr)
if err != nil {
var defaultPort string
switch {
case useTestNet3:
if useWallet {
defaultPort = "18332"
} else {
defaultPort = "18334"
}
case useSimNet:
if useWallet {
defaultPort = "18554"
} else {
defaultPort = "18556"
}
default:
if useWallet {
defaultPort = "8332"
} else {
defaultPort = "8334"
}
}
return net.JoinHostPort(addr, defaultPort)
}
return addr
}
// cleanAndExpandPath expands environement variables and leading ~ in the
// passed path, cleans the result, and returns it.
func cleanAndExpandPath(path string) string {
// Expand initial ~ to OS specific home directory.
if strings.HasPrefix(path, "~") {
homeDir := filepath.Dir(btcctlHomeDir)
path = strings.Replace(path, "~", homeDir, 1)
}
// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
// but they variables can still be expanded via POSIX-style $VARIABLE.
return filepath.Clean(os.ExpandEnv(path))
}
// loadConfig initializes and parses the config using a config file and 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 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{
ConfigFile: defaultConfigFile,
RPCServer: defaultRPCServer,
RPCCert: defaultRPCCertFile,
}
// Pre-parse the command line options to see if an alternative config
// file, the version flag, or the list commands flag was specified. Any
// errors aside from the help message error can be ignored here since
// they will be caught by the final parse below.
preCfg := cfg
preParser := flags.NewParser(&preCfg, flags.HelpFlag)
_, err := preParser.Parse()
if err != nil {
if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "The special parameter `-` "+
"indicates that a parameter should be read "+
"from the\nnext unread line from standard "+
"input.")
return nil, nil, err
}
}
// Show the version and exit if the version flag was specified.
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
usageMessage := fmt.Sprintf("Use %s -h to show options", appName)
if preCfg.ShowVersion {
fmt.Println(appName, "version", version())
os.Exit(0)
}
// Show the available commands and exit if the associated flag was
// specified.
if preCfg.ListCommands {
listCommands()
os.Exit(0)
}
if _, err := os.Stat(preCfg.ConfigFile); os.IsNotExist(err) {
// Use config file for RPC server to create default btcctl config
var serverConfigPath string
if preCfg.Wallet {
serverConfigPath = filepath.Join(btcwalletHomeDir, "btcwallet.conf")
} else {
serverConfigPath = filepath.Join(btcdHomeDir, "btcd.conf")
}
err := createDefaultConfigFile(preCfg.ConfigFile, serverConfigPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating a default config file: %v\n", err)
}
}
// Load additional config from file.
parser := flags.NewParser(&cfg, flags.Default)
err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
if err != nil {
if _, ok := err.(*os.PathError); !ok {
fmt.Fprintf(os.Stderr, "Error parsing config file: %v\n",
err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, nil, err
}
}
// Parse command line options again to ensure they take precedence.
remainingArgs, err := parser.Parse()
if err != nil {
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
fmt.Fprintln(os.Stderr, usageMessage)
}
return nil, nil, err
}
// Multiple networks can't be selected simultaneously.
numNets := 0
if cfg.TestNet3 {
numNets++
}
if cfg.SimNet {
numNets++
}
if numNets > 1 {
str := "%s: The testnet and simnet params can't be used " +
"together -- choose one of the two"
err := fmt.Errorf(str, "loadConfig")
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
}
// Override the RPC certificate if the --wallet flag was specified and
// the user did not specify one.
if cfg.Wallet && cfg.RPCCert == defaultRPCCertFile {
cfg.RPCCert = defaultWalletCertFile
}
// Handle environment variable expansion in the RPC certificate path.
cfg.RPCCert = cleanAndExpandPath(cfg.RPCCert)
// Add default port to RPC server based on --testnet and --wallet flags
// if needed.
cfg.RPCServer = normalizeAddress(cfg.RPCServer, cfg.TestNet3,
cfg.SimNet, cfg.Wallet)
return &cfg, remainingArgs, nil
}
// createDefaultConfig creates a basic config file at the given destination path.
// For this it tries to read the config file for the RPC server (either btcd or
// btcwallet), and extract the RPC user and password from it.
func createDefaultConfigFile(destinationPath, serverConfigPath string) error {
// Read the RPC server config
serverConfigFile, err := os.Open(serverConfigPath)
if err != nil {
return err
}
defer serverConfigFile.Close()
content, err := ioutil.ReadAll(serverConfigFile)
if err != nil {
return err
}
// Extract the rpcuser
rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser=([^\s]+)`)
if err != nil {
return err
}
userSubmatches := rpcUserRegexp.FindSubmatch(content)
if userSubmatches == nil {
// No user found, nothing to do
return nil
}
// Extract the rpcpass
rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpass=([^\s]+)`)
if err != nil {
return err
}
passSubmatches := rpcPassRegexp.FindSubmatch(content)
if passSubmatches == nil {
// No password found, nothing to do
return nil
}
// Extract the notls
noTLSRegexp, err := regexp.Compile(`(?m)^\s*notls=(0|1)(?:\s|$)`)
if err != nil {
return err
}
noTLSSubmatches := noTLSRegexp.FindSubmatch(content)
// Create the destination directory if it does not exists
err = os.MkdirAll(filepath.Dir(destinationPath), 0700)
if err != nil {
return err
}
// Create the destination file and write the rpcuser and rpcpass to it
dest, err := os.OpenFile(destinationPath,
os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer dest.Close()
destString := fmt.Sprintf("rpcuser=%s\nrpcpass=%s\n",
string(userSubmatches[1]), string(passSubmatches[1]))
if noTLSSubmatches != nil {
destString += fmt.Sprintf("notls=%s\n", noTLSSubmatches[1])
}
dest.WriteString(destString)
return nil
}
+128
View File
@@ -0,0 +1,128 @@
package main
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/go-socks/socks"
)
// newHTTPClient returns a new HTTP client that is configured according to the
// proxy and TLS settings in the associated connection configuration.
func newHTTPClient(cfg *config) (*http.Client, error) {
// Configure proxy if needed.
var dial func(network, addr string) (net.Conn, error)
if cfg.Proxy != "" {
proxy := &socks.Proxy{
Addr: cfg.Proxy,
Username: cfg.ProxyUser,
Password: cfg.ProxyPass,
}
dial = func(network, addr string) (net.Conn, error) {
c, err := proxy.Dial(network, addr)
if err != nil {
return nil, err
}
return c, nil
}
}
// Configure TLS if needed.
var tlsConfig *tls.Config
if !cfg.NoTLS && cfg.RPCCert != "" {
pem, err := ioutil.ReadFile(cfg.RPCCert)
if err != nil {
return nil, err
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(pem)
tlsConfig = &tls.Config{
RootCAs: pool,
InsecureSkipVerify: cfg.TLSSkipVerify,
}
}
// Create and return the new HTTP client potentially configured with a
// proxy and TLS.
client := http.Client{
Transport: &http.Transport{
Dial: dial,
TLSClientConfig: tlsConfig,
},
}
return &client, nil
}
// sendPostRequest sends the marshalled JSON-RPC command using HTTP-POST mode
// to the server described in the passed config struct. It also attempts to
// unmarshal the response as a JSON-RPC response and returns either the result
// field or the error field depending on whether or not there is an error.
func sendPostRequest(marshalledJSON []byte, cfg *config) ([]byte, error) {
// Generate a request to the configured RPC server.
protocol := "http"
if !cfg.NoTLS {
protocol = "https"
}
url := protocol + "://" + cfg.RPCServer
bodyReader := bytes.NewReader(marshalledJSON)
httpRequest, err := http.NewRequest("POST", url, bodyReader)
if err != nil {
return nil, err
}
httpRequest.Close = true
httpRequest.Header.Set("Content-Type", "application/json")
// Configure basic access authorization.
httpRequest.SetBasicAuth(cfg.RPCUser, cfg.RPCPassword)
// Create the new HTTP client that is configured according to the user-
// specified options and submit the request.
httpClient, err := newHTTPClient(cfg)
if err != nil {
return nil, err
}
httpResponse, err := httpClient.Do(httpRequest)
if err != nil {
return nil, err
}
// Read the raw bytes and close the response.
respBytes, err := ioutil.ReadAll(httpResponse.Body)
httpResponse.Body.Close()
if err != nil {
err = fmt.Errorf("error reading json reply: %v", err)
return nil, err
}
// Handle unsuccessful HTTP responses
if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
// Generate a standard error to return if the server body is
// empty. This should not happen very often, but it's better
// than showing nothing in case the target server has a poor
// implementation.
if len(respBytes) == 0 {
return nil, fmt.Errorf("%d %s", httpResponse.StatusCode,
http.StatusText(httpResponse.StatusCode))
}
return nil, fmt.Errorf("%s", respBytes)
}
// Unmarshal the response.
var resp btcjson.Response
if err := json.Unmarshal(respBytes, &resp); err != nil {
return nil, err
}
if resp.Error != nil {
return nil, resp.Error
}
return resp.Result, nil
}
+75
View File
@@ -0,0 +1,75 @@
// Copyright (c) 2013 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"fmt"
"strings"
)
// semanticAlphabet
const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
// These constants define the application version and follow the semantic
// versioning 2.0.0 spec (http://semver.org/).
const (
appMajor uint = 0
appMinor uint = 12
appPatch uint = 0
// appPreRelease MUST only contain characters from semanticAlphabet
// per the semantic versioning spec.
appPreRelease = "beta"
)
// appBuild is defined as a variable so it can be overridden during the build
// process with '-ldflags "-X main.appBuild foo' if needed. It MUST only
// contain characters from semanticAlphabet per the semantic versioning spec.
var appBuild string
// version returns the application version as a properly formed string per the
// semantic versioning 2.0.0 spec (http://semver.org/).
func version() string {
// Start with the major, minor, and patch versions.
version := fmt.Sprintf("%d.%d.%d", appMajor, appMinor, appPatch)
// Append pre-release version if there is one. The hyphen called for
// by the semantic versioning spec is automatically appended and should
// not be contained in the pre-release string. The pre-release version
// is not appended if it contains invalid characters.
preRelease := normalizeVerString(appPreRelease)
if preRelease != "" {
version = fmt.Sprintf("%s-%s", version, preRelease)
}
// Append build metadata if there is any. The plus called for
// by the semantic versioning spec is automatically appended and should
// not be contained in the build metadata string. The build metadata
// string is not appended if it contains invalid characters.
build := normalizeVerString(appBuild)
if build != "" {
version = fmt.Sprintf("%s+%s", version, build)
}
return version
}
// normalizeVerString returns the passed string stripped of all characters which
// are not valid according to the semantic versioning guidelines for pre-release
// version and build metadata strings. In particular they MUST only contain
// characters in semanticAlphabet.
func normalizeVerString(str string) string {
var result bytes.Buffer
for _, r := range str {
if strings.ContainsRune(semanticAlphabet, r) {
// Ignoring the error here since it can only fail if
// the the system is out of memory and there are much
// bigger issues at that point.
_, _ = result.WriteRune(r)
}
}
return result.String()
}
+150
View File
@@ -0,0 +1,150 @@
// Copyright (c) 2013-2016 The btcsuite developers
// 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/btcd/chaincfg"
"github.com/btcsuite/btcd/database"
_ "github.com/btcsuite/btcd/database/ffldb"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
flags "github.com/jessevdk/go-flags"
)
const (
minCandidates = 1
maxCandidates = 20
defaultNumCandidates = 5
defaultDbType = "ffldb"
)
var (
btcdHomeDir = btcutil.AppDataDir("btcd", false)
defaultDataDir = filepath.Join(btcdHomeDir, "data")
knownDbTypes = database.SupportedDrivers()
activeNetParams = &chaincfg.MainNetParams
)
// 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"`
DbType string `long:"dbtype" description:"Database backend to use for the Block Chain"`
TestNet3 bool `long:"testnet" description:"Use the test network"`
RegressionTest bool `long:"regtest" description:"Use the regression test network"`
SimNet bool `long:"simnet" description:"Use the simulation test network"`
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"`
}
// validDbType returns whether or not dbType is a supported database type.
func validDbType(dbType string) bool {
for _, knownType := range knownDbTypes {
if dbType == knownType {
return true
}
}
return false
}
// netName returns the name used when referring to a bitcoin network. At the
// time of writing, btcd currently places blocks for testnet version 3 in the
// data and log directory "testnet", which does not match the Name field of the
// chaincfg parameters. This function can be used to override this directory name
// as "testnet" when the passed active network matches wire.TestNet3.
//
// A proper upgrade to move the data and log directories for this network to
// "testnet3" is planned for the future, at which point this function can be
// removed and the network parameter's name used instead.
func netName(chainParams *chaincfg.Params) string {
switch chainParams.Net {
case wire.TestNet3:
return "testnet"
default:
return chainParams.Name
}
}
// loadConfig initializes and parses the config using command line options.
func loadConfig() (*config, []string, error) {
// Default config.
cfg := config{
DataDir: defaultDataDir,
DbType: defaultDbType,
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
}
// Multiple networks can't be selected simultaneously.
funcName := "loadConfig"
numNets := 0
// Count number of network flags passed; assign active network params
// while we're at it
if cfg.TestNet3 {
numNets++
activeNetParams = &chaincfg.TestNet3Params
}
if cfg.RegressionTest {
numNets++
activeNetParams = &chaincfg.RegressionNetParams
}
if cfg.SimNet {
numNets++
activeNetParams = &chaincfg.SimNetParams
}
if numNets > 1 {
str := "%s: The testnet, regtest, and simnet params can't be " +
"used together -- choose one of the three"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
parser.WriteHelp(os.Stderr)
return nil, nil, err
}
// Validate database type.
if !validDbType(cfg.DbType) {
str := "%s: The specified database type [%v] is invalid -- " +
"supported types %v"
err := fmt.Errorf(str, "loadConfig", cfg.DbType, knownDbTypes)
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 = filepath.Join(cfg.DataDir, netName(activeNetParams))
// 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
}
return &cfg, remainingArgs, nil
}
+185
View File
@@ -0,0 +1,185 @@
// Copyright (c) 2013-2016 The btcsuite developers
// 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/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/database"
)
const blockDbNamePrefix = "blocks"
var (
cfg *config
)
// loadBlockDB opens the block database and returns a handle to it.
func loadBlockDB() (database.DB, error) {
// The database name is based on the database type.
dbName := blockDbNamePrefix + "_" + cfg.DbType
dbPath := filepath.Join(cfg.DataDir, dbName)
fmt.Printf("Loading block database from '%s'\n", dbPath)
db, err := database.Open(cfg.DbType, dbPath, activeNetParams.Net)
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(chain *blockchain.BlockChain, latestHash *chainhash.Hash) ([]*chaincfg.Checkpoint, error) {
// Start with the latest block of the main chain.
block, err := chain.BlockByHash(latestHash)
if err != nil {
return nil, err
}
// Get the latest known checkpoint.
latestCheckpoint := chain.LatestCheckpoint()
if latestCheckpoint == nil {
// Set the latest checkpoint to the genesis block if there isn't
// already one.
latestCheckpoint = &chaincfg.Checkpoint{
Hash: activeNetParams.GenesisHash,
Height: 0,
}
}
// The latest known block must be at least the last known checkpoint
// plus required checkpoint confirmations.
checkpointConfirmations := int32(blockchain.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)
}
// 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
}
// 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([]*chaincfg.Checkpoint, 0, cfg.NumCandidates)
numTested := int32(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 {
checkpoint := chaincfg.Checkpoint{
Height: block.Height(),
Hash: block.Hash(),
}
candidates = append(candidates, &checkpoint)
}
prevHash := &block.MsgBlock().Header.PrevBlock
block, err = chain.BlockByHash(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 *chaincfg.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.Fprintln(os.Stderr, "failed to load database:", err)
return
}
defer db.Close()
// Setup chain. Ignore notifications since they aren't needed for this
// util.
chain, err := blockchain.New(&blockchain.Config{
DB: db,
ChainParams: activeNetParams,
TimeSource: blockchain.NewMedianTime(),
})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to initialize chain: %v\n", err)
return
}
// 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)
// Find checkpoint candidates.
candidates, err := findCandidates(chain, &best.Hash)
if err != nil {
fmt.Fprintln(os.Stderr, "Unable to identify candidates:", 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)
}
}
+102
View File
@@ -0,0 +1,102 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"github.com/btcsuite/btcutil"
flags "github.com/jessevdk/go-flags"
)
type config struct {
Directory string `short:"d" long:"directory" description:"Directory to write certificate pair"`
Years int `short:"y" long:"years" description:"How many years a certificate is valid for"`
Organization string `short:"o" long:"org" description:"Organization in certificate"`
ExtraHosts []string `short:"H" long:"host" description:"Additional hosts/IPs to create certificate for"`
Force bool `short:"f" long:"force" description:"Force overwriting of any old certs and keys"`
}
func main() {
cfg := config{
Years: 10,
Organization: "gencerts",
}
parser := flags.NewParser(&cfg, flags.Default)
_, err := parser.Parse()
if err != nil {
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
parser.WriteHelp(os.Stderr)
}
return
}
if cfg.Directory == "" {
var err error
cfg.Directory, err = os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "no directory specified and cannot get working directory\n")
os.Exit(1)
}
}
cfg.Directory = cleanAndExpandPath(cfg.Directory)
certFile := filepath.Join(cfg.Directory, "rpc.cert")
keyFile := filepath.Join(cfg.Directory, "rpc.key")
if !cfg.Force {
if fileExists(certFile) || fileExists(keyFile) {
fmt.Fprintf(os.Stderr, "%v: certificate and/or key files exist; use -f to force\n", cfg.Directory)
os.Exit(1)
}
}
validUntil := time.Now().Add(time.Duration(cfg.Years) * 365 * 24 * time.Hour)
cert, key, err := btcutil.NewTLSCertPair(cfg.Organization, validUntil, cfg.ExtraHosts)
if err != nil {
fmt.Fprintf(os.Stderr, "cannot generate certificate pair: %v\n", err)
os.Exit(1)
}
// Write cert and key files.
if err = ioutil.WriteFile(certFile, cert, 0666); err != nil {
fmt.Fprintf(os.Stderr, "cannot write cert: %v\n", err)
os.Exit(1)
}
if err = ioutil.WriteFile(keyFile, key, 0600); err != nil {
os.Remove(certFile)
fmt.Fprintf(os.Stderr, "cannot write key: %v\n", err)
os.Exit(1)
}
}
// cleanAndExpandPath expands environement variables and leading ~ in the
// passed path, cleans the result, and returns it.
func cleanAndExpandPath(path string) string {
// Expand initial ~ to OS specific home directory.
if strings.HasPrefix(path, "~") {
appHomeDir := btcutil.AppDataDir("gencerts", false)
homeDir := filepath.Dir(appHomeDir)
path = strings.Replace(path, "~", homeDir, 1)
}
// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
// but they variables can still be expanded via POSIX-style $VARIABLE.
return filepath.Clean(os.ExpandEnv(path))
}
// filesExists reports whether the named file or directory exists.
func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}