multi: Split dcrctl to own repo and update docs
The dcrctl project is being split out of the dcrd repo and module into its own repo (https://github.com/decred/dcrctl) and module (decred.org/dcrctl). This commit removes the dcrctl main package and updates documentation throughout the repo mentioning dcrctl and its build instructions.
This commit is contained in:
parent
e12b0a7191
commit
787d3f5c0f
1
.gitignore
vendored
1
.gitignore
vendored
@ -37,7 +37,6 @@ _testmain.go
|
||||
dcrd
|
||||
cmd/addblock/addblock
|
||||
cmd/checkdevpremine/checkdevpremine
|
||||
cmd/dcrctl/dcrctl
|
||||
cmd/findcheckpoint/findcheckpoint
|
||||
cmd/gencerts/gencerts
|
||||
cmd/promptsecret/promptsecret
|
||||
|
||||
@ -24,6 +24,7 @@ Core software:
|
||||
|
||||
* dcrd: a Decred full node daemon (this)
|
||||
* [dcrwallet](https://github.com/decred/dcrwallet): a CLI Decred wallet daemon
|
||||
* [dcrctl](https://github.com/decred/dcrctl): a CLI client for dcrd and dcrwallet
|
||||
|
||||
Bundles:
|
||||
|
||||
@ -179,8 +180,9 @@ https://decred.org/downloads
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/decred/dcrd $HOME/src/dcrd
|
||||
$ cd $HOME/src/dcrd
|
||||
$ go install . ./cmd/...
|
||||
$ git clone https://github.com/decred/dcrctl $HOME/src/dcrctl
|
||||
$ (cd $HOME/src/dcrd && go install . ./...)
|
||||
$ (cd $HOME/src/dcrctl && go install)
|
||||
$ dcrd -V
|
||||
```
|
||||
|
||||
|
||||
@ -1,416 +0,0 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Copyright (c) 2015-2019 The Decred 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"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/decred/dcrd/dcrjson/v3"
|
||||
"github.com/decred/dcrd/dcrutil/v3"
|
||||
"github.com/decred/dcrd/internal/version"
|
||||
|
||||
dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v2"
|
||||
wallettypes "github.com/decred/dcrwallet/rpc/jsonrpc/types"
|
||||
|
||||
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 = dcrjson.UFWebsocketOnly | dcrjson.UFNotification
|
||||
)
|
||||
|
||||
var (
|
||||
dcrdHomeDir = dcrutil.AppDataDir("dcrd", false)
|
||||
dcrctlHomeDir = dcrutil.AppDataDir("dcrctl", false)
|
||||
dcrwalletHomeDir = dcrutil.AppDataDir("dcrwallet", false)
|
||||
defaultConfigFile = filepath.Join(dcrctlHomeDir, "dcrctl.conf")
|
||||
defaultRPCServer = "localhost"
|
||||
defaultWalletRPCServer = "localhost"
|
||||
defaultRPCCertFile = filepath.Join(dcrdHomeDir, "rpc.cert")
|
||||
defaultWalletCertFile = filepath.Join(dcrwalletHomeDir, "rpc.cert")
|
||||
)
|
||||
|
||||
// listCommands categorizes and lists all of the usable commands along with
|
||||
// their one-line usage.
|
||||
func listCommands() {
|
||||
var categories = []struct {
|
||||
Header string
|
||||
Method interface{}
|
||||
Usages []string
|
||||
}{{
|
||||
Header: "Chain Server Commands:",
|
||||
Method: dcrdtypes.Method(""),
|
||||
}, {
|
||||
Header: "Wallet Server Commands (--wallet):",
|
||||
Method: wallettypes.Method(""),
|
||||
}}
|
||||
|
||||
for i := range categories {
|
||||
method := categories[i].Method
|
||||
methods := dcrjson.RegisteredMethods(method)
|
||||
for _, methodStr := range methods {
|
||||
switch method.(type) {
|
||||
case dcrdtypes.Method:
|
||||
method = dcrdtypes.Method(methodStr)
|
||||
case wallettypes.Method:
|
||||
method = wallettypes.Method(methodStr)
|
||||
}
|
||||
|
||||
flags, err := dcrjson.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 := dcrjson.MethodUsageText(method)
|
||||
if err != nil {
|
||||
// This should never happen since the method was just
|
||||
// returned from the package, but be safe.
|
||||
continue
|
||||
}
|
||||
|
||||
categories[i].Usages = append(categories[i].Usages, usage)
|
||||
}
|
||||
}
|
||||
|
||||
// Display the command according to their categories.
|
||||
for i := range categories {
|
||||
fmt.Println(categories[i].Header)
|
||||
for _, usage := range categories[i].Usages {
|
||||
fmt.Println(usage)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// config defines the configuration options for dcrctl.
|
||||
//
|
||||
// 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"`
|
||||
WalletRPCServer string `short:"w" long:"walletrpcserver" description:"Wallet RPC server to connect to"`
|
||||
RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"`
|
||||
PrintJSON bool `short:"j" long:"json" description:"Print json messages sent and received"`
|
||||
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"`
|
||||
TestNet 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, useTestNet, useSimNet, useWallet bool) string {
|
||||
_, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
var defaultPort string
|
||||
switch {
|
||||
case useTestNet:
|
||||
if useWallet {
|
||||
defaultPort = "19110"
|
||||
} else {
|
||||
defaultPort = "19109"
|
||||
}
|
||||
case useSimNet:
|
||||
if useWallet {
|
||||
defaultPort = "19557"
|
||||
} else {
|
||||
defaultPort = "19556"
|
||||
}
|
||||
default:
|
||||
if useWallet {
|
||||
defaultPort = "9110"
|
||||
} else {
|
||||
defaultPort = "9109"
|
||||
}
|
||||
}
|
||||
|
||||
return net.JoinHostPort(addr, defaultPort)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// cleanAndExpandPath expands environment variables and leading ~ in the
|
||||
// passed path, cleans the result, and returns it.
|
||||
func cleanAndExpandPath(path string) string {
|
||||
// Nothing to do when no path is given.
|
||||
if path == "" {
|
||||
return path
|
||||
}
|
||||
|
||||
// NOTE: The os.ExpandEnv doesn't work with Windows cmd.exe-style
|
||||
// %VARIABLE%, but the variables can still be expanded via POSIX-style
|
||||
// $VARIABLE.
|
||||
path = os.ExpandEnv(path)
|
||||
|
||||
if !strings.HasPrefix(path, "~") {
|
||||
return filepath.Clean(path)
|
||||
}
|
||||
|
||||
// Expand initial ~ to the current user's home directory, or ~otheruser
|
||||
// to otheruser's home directory. On Windows, both forward and backward
|
||||
// slashes can be used.
|
||||
path = path[1:]
|
||||
|
||||
var pathSeparators string
|
||||
if runtime.GOOS == "windows" {
|
||||
pathSeparators = string(os.PathSeparator) + "/"
|
||||
} else {
|
||||
pathSeparators = string(os.PathSeparator)
|
||||
}
|
||||
|
||||
userName := ""
|
||||
if i := strings.IndexAny(path, pathSeparators); i != -1 {
|
||||
userName = path[:i]
|
||||
path = path[i:]
|
||||
}
|
||||
|
||||
homeDir := ""
|
||||
var u *user.User
|
||||
var err error
|
||||
if userName == "" {
|
||||
u, err = user.Current()
|
||||
} else {
|
||||
u, err = user.Lookup(userName)
|
||||
}
|
||||
if err == nil {
|
||||
homeDir = u.HomeDir
|
||||
}
|
||||
// Fallback to CWD if user lookup fails or user has no home directory.
|
||||
if homeDir == "" {
|
||||
homeDir = "."
|
||||
}
|
||||
|
||||
return filepath.Join(homeDir, path)
|
||||
}
|
||||
|
||||
// fileExists 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
|
||||
}
|
||||
|
||||
// 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,
|
||||
WalletRPCServer: defaultWalletRPCServer,
|
||||
}
|
||||
|
||||
// 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.")
|
||||
os.Exit(1)
|
||||
} else if ok && e.Type == flags.ErrHelp {
|
||||
fmt.Fprintln(os.Stdout, err)
|
||||
fmt.Fprintln(os.Stdout, "")
|
||||
fmt.Fprintln(os.Stdout, "The special parameter `-` "+
|
||||
"indicates that a parameter should be read "+
|
||||
"from the\nnext unread line from standard input.")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// 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.Printf("%s version %s (Go version %s %s/%s)\n", appName,
|
||||
version.String(), runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Show the available commands and exit if the associated flag was
|
||||
// specified.
|
||||
if preCfg.ListCommands {
|
||||
listCommands()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if !fileExists(preCfg.ConfigFile) {
|
||||
err := createDefaultConfigFile(preCfg.ConfigFile)
|
||||
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.TestNet {
|
||||
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
|
||||
}
|
||||
|
||||
// When the --wallet flag is specified, use the walletrpcserver port
|
||||
// if specified.
|
||||
if cfg.Wallet && cfg.WalletRPCServer != defaultWalletRPCServer {
|
||||
cfg.RPCServer = cfg.WalletRPCServer
|
||||
}
|
||||
// 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.TestNet,
|
||||
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 dcrd config file at its default path, and extract
|
||||
// the RPC user and password from it.
|
||||
func createDefaultConfigFile(destinationPath string) error {
|
||||
// Nothing to do when there is no existing dcrd conf file at the default
|
||||
// path to extract the details from.
|
||||
dcrdConfigPath := filepath.Join(dcrdHomeDir, "dcrd.conf")
|
||||
if !fileExists(dcrdConfigPath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read dcrd.conf from its default path
|
||||
dcrdConfigFile, err := os.Open(dcrdConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dcrdConfigFile.Close()
|
||||
content, err := ioutil.ReadAll(dcrdConfigFile)
|
||||
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
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
dest.WriteString(fmt.Sprintf("rpcuser=%s\nrpcpass=%s",
|
||||
string(userSubmatches[1]), string(passSubmatches[1])))
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,178 +0,0 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Copyright (c) 2015-2019 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/decred/dcrd/dcrjson/v3"
|
||||
dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v2"
|
||||
wallettypes "github.com/decred/dcrwallet/rpc/jsonrpc/types"
|
||||
)
|
||||
|
||||
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 interface{}) {
|
||||
usage, err := dcrjson.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.
|
||||
methodStr := args[0]
|
||||
var method interface{} = dcrdtypes.Method(methodStr)
|
||||
usageFlags, err := dcrjson.MethodUsageFlags(method)
|
||||
if err != nil {
|
||||
method = wallettypes.Method(methodStr)
|
||||
usageFlags, err = dcrjson.MethodUsageFlags(method)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unrecognized command %q\n", methodStr)
|
||||
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 := dcrjson.NewCmd(method, params...)
|
||||
if err != nil {
|
||||
// Show the error along with its error code when it's a
|
||||
// dcrjson.Error as it realistically will always be since the
|
||||
// NewCmd function is only supposed to return errors of that
|
||||
// type.
|
||||
if jerr, ok := err.(dcrjson.Error); ok {
|
||||
fmt.Fprintf(os.Stderr, "%s command: %v (code: %s)\n",
|
||||
method, err, jerr.Code)
|
||||
commandUsage(method)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// The error is not a dcrjson.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 := dcrjson.MarshalCmd("1.0", 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)
|
||||
}
|
||||
}
|
||||
@ -1,146 +0,0 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Copyright (c) 2015-2019 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/decred/dcrd/dcrjson/v3"
|
||||
"github.com/decred/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 {
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: cfg.TLSSkipVerify,
|
||||
}
|
||||
if !cfg.TLSSkipVerify && cfg.RPCCert != "" {
|
||||
pem, err := ioutil.ReadFile(cfg.RPCCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
if ok := pool.AppendCertsFromPEM(pem); !ok {
|
||||
return nil, fmt.Errorf("invalid certificate file: %v",
|
||||
cfg.RPCCert)
|
||||
}
|
||||
tlsConfig.RootCAs = pool
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
if cfg.PrintJSON {
|
||||
fmt.Println(string(marshalledJSON))
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
// If requested, print raw json response.
|
||||
if cfg.PrintJSON {
|
||||
fmt.Println(string(respBytes))
|
||||
}
|
||||
|
||||
// Unmarshal the response.
|
||||
var resp dcrjson.Response
|
||||
if err := json.Unmarshal(respBytes, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.Error != nil {
|
||||
return nil, resp.Error
|
||||
}
|
||||
return resp.Result, nil
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
[Application Options]
|
||||
|
||||
; ------------------------------------------------------------------------------
|
||||
; Network settings
|
||||
; ------------------------------------------------------------------------------
|
||||
|
||||
; Use testnet (cannot be used with simnet=1).
|
||||
; testnet=1
|
||||
|
||||
; Use simnet (cannot be used with testnet=1).
|
||||
; simnet=1
|
||||
|
||||
|
||||
; ------------------------------------------------------------------------------
|
||||
; RPC client settings
|
||||
; ------------------------------------------------------------------------------
|
||||
|
||||
; Connect via a SOCKS5 proxy.
|
||||
; proxy=127.0.0.1:9050
|
||||
; proxyuser=
|
||||
; proxypass=
|
||||
|
||||
; Username and password to authenticate connections to a Decred RPC server
|
||||
; (usually dcrd or dcrwallet)
|
||||
; rpcuser=
|
||||
; rpcpass=
|
||||
|
||||
; RPC server to connect to
|
||||
; rpcserver=localhost
|
||||
|
||||
; Wallet RPC server to connect to
|
||||
; walletrpcserver=localhost
|
||||
|
||||
; RPC server certificate chain file for validation
|
||||
; rpccert=~/.dcrd/rpc.cert
|
||||
|
||||
@ -55,10 +55,11 @@ options, which can be viewed by running: `$ dcrd --help`.
|
||||
|
||||
**2.3 Controlling and Querying dcrd via dcrctl**<br />
|
||||
|
||||
dcrctl is a command line utility that can be used to both control and query dcrd
|
||||
via [RPC](https://www.wikipedia.org/wiki/Remote_procedure_call). dcrd does
|
||||
**not** enable its RPC server by default; You must configure at minimum both an
|
||||
RPC username and password or both an RPC limited username and password:
|
||||
[dcrctl](https://github.com/decred/dcrctl) is a command line utility that can be
|
||||
used to both control and query dcrd via
|
||||
[RPC](https://www.wikipedia.org/wiki/Remote_procedure_call). dcrd does **not**
|
||||
enable its RPC server by default; You must configure at minimum both an RPC
|
||||
username and password or both an RPC limited username and password:
|
||||
|
||||
* dcrd.conf configuration file
|
||||
```
|
||||
|
||||
@ -100,11 +100,11 @@ authenticated will cause the websocket to be closed immediately.
|
||||
|
||||
==4. Command-line Utility==
|
||||
|
||||
dcrd comes with a separate utility named <code>dcrctl</code> which can be used to issue
|
||||
these RPC commands via HTTP POST requests to dcrd after configuring it with the
|
||||
information in the [[#3-authentication|Authentication]] section above. It can also
|
||||
be used to communicate with any server/daemon/service which provides a JSON-RPC
|
||||
API compatible with the original dcrd client.
|
||||
dcrd is built to work with [https://github.com/decred/dcrctl <code>dcrctl</code>]
|
||||
which can be used to issue these RPC commands via HTTP POST requests to dcrd
|
||||
after configuring it with the information in the [[#3-authentication|Authentication]]
|
||||
section above. It can also be used to communicate with any server/daemon/service
|
||||
which provides a JSON-RPC API compatible with the original dcrd client.
|
||||
|
||||
==5. Standard Methods==
|
||||
|
||||
|
||||
3
go.mod
3
go.mod
@ -14,7 +14,6 @@ require (
|
||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.2
|
||||
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200215031403-6b2ce76f0986
|
||||
github.com/decred/dcrd/connmgr/v3 v3.0.0-20200215031403-6b2ce76f0986
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0
|
||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.0
|
||||
github.com/decred/dcrd/database/v2 v2.0.1
|
||||
github.com/decred/dcrd/dcrec v1.0.0
|
||||
@ -32,7 +31,7 @@ require (
|
||||
github.com/decred/dcrd/rpcclient/v6 v6.0.0-20200215031403-6b2ce76f0986
|
||||
github.com/decred/dcrd/txscript/v3 v3.0.0-20200215031403-6b2ce76f0986
|
||||
github.com/decred/dcrd/wire v1.3.0
|
||||
github.com/decred/dcrwallet/rpc/jsonrpc/types v1.4.0
|
||||
github.com/decred/dcrwallet/rpc/jsonrpc/types v1.4.0 // indirect
|
||||
github.com/decred/go-socks v1.1.0
|
||||
github.com/decred/slog v1.0.0
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
|
||||
2
go.sum
2
go.sum
@ -10,8 +10,6 @@ github.com/decred/base58 v1.0.2 h1:yupIH6bg+q7KYfBk7oUv3xFjKGb5Ypm4+v/61X4keGY=
|
||||
github.com/decred/base58 v1.0.2/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E=
|
||||
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.0 h1:E5KszxGgpjpmW8vN811G6rBAZg0/S/DftdGqN4FW5x4=
|
||||
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.0/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 h1:3GIJYXQDAKpLEFriGFN8SbSffak10UXHGdIcFaMPykY=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0/go.mod h1:3s92l0paYkZoIHuj4X93Teg/HB7eGM9x/zokGw+u4mY=
|
||||
github.com/decred/dcrd/rpc/jsonrpc/types v1.0.1 h1:sWsGtWzdmrna6aysDCHwjANTJh+Lxt2xp6S10ahP79Y=
|
||||
github.com/decred/dcrd/rpc/jsonrpc/types v1.0.1/go.mod h1:dJUp9PoyFYklzmlImpVkVLOr6j4zKuUv66YgemP2sd8=
|
||||
github.com/decred/dcrwallet/rpc/jsonrpc/types v1.3.0 h1:yCxtFqK7X6GvZWQzHXjCwoGCy9YVe3tGEwxCjW5rYQk=
|
||||
|
||||
Loading…
Reference in New Issue
Block a user