Improve btcctl.
This commit significantly reworks btcctl to use a map based approach to command handling. This reduces the number of lines of code needed, simplifies adding new commands, improves the error handling, and removes several cases where unexpected data was not handled properly (it could panic). This commit also adds the ability to specify the optional parameter on getrawtransaction. Discussed with dhill@.
This commit is contained in:
parent
102fc5f513
commit
2ea4239f5e
1 changed files with 195 additions and 209 deletions
|
@ -17,231 +17,178 @@ type config struct {
|
|||
RpcServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"`
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNoData = errors.New("No data returned.")
|
||||
)
|
||||
// conversionHandler is a handler that is used to convert parameters from the
|
||||
// command line to a specific type. This is needed since the btcjson API
|
||||
// expects certain types for various parameters.
|
||||
type conversionHandler func(string) (interface{}, error)
|
||||
|
||||
func main() {
|
||||
cfg := config{
|
||||
RpcServer: "127.0.0.1:8334",
|
||||
}
|
||||
parser := flags.NewParser(&cfg, flags.None)
|
||||
// displayHandler is a handler that takes an interface and displays it to
|
||||
// standard out. It is used by the handler data to type assert replies and
|
||||
// show them formatted as desired.
|
||||
type displayHandler func(interface{}) error
|
||||
|
||||
args, err := parser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
usage(parser)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) < 1 || cfg.Help {
|
||||
usage(parser)
|
||||
return
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
default:
|
||||
usage(parser)
|
||||
case "addnode":
|
||||
if len(args) != 3 {
|
||||
usage(parser)
|
||||
break
|
||||
}
|
||||
msg, err := btcjson.CreateMessage("addnode", args[1], args[2])
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
spew.Dump(reply)
|
||||
case "decoderawtransaction":
|
||||
if len(args) != 2 {
|
||||
usage(parser)
|
||||
break
|
||||
}
|
||||
msg, err := btcjson.CreateMessage("decoderawtransaction", args[1])
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
spew.Dump(reply)
|
||||
case "getbestblockhash":
|
||||
msg, err := btcjson.CreateMessage("getbestblockhash")
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
fmt.Printf("%s\n", reply.(string))
|
||||
case "getblock":
|
||||
if len(args) != 2 {
|
||||
usage(parser)
|
||||
break
|
||||
}
|
||||
msg, err := btcjson.CreateMessage("getblock", args[1])
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
spew.Dump(reply.(btcjson.BlockResult))
|
||||
case "getblockcount":
|
||||
msg, err := btcjson.CreateMessage("getblockcount")
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
fmt.Printf("%d\n", int(reply.(float64)))
|
||||
case "getblockhash":
|
||||
if len(args) != 2 {
|
||||
usage(parser)
|
||||
break
|
||||
}
|
||||
idx, err := strconv.Atoi(args[1])
|
||||
if err != nil {
|
||||
fmt.Printf("Atoi: %v\n", err)
|
||||
break
|
||||
}
|
||||
msg, err := btcjson.CreateMessage("getblockhash", idx)
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
fmt.Printf("%v\n", reply)
|
||||
case "getconnectioncount":
|
||||
msg, err := btcjson.CreateMessage("getconnectioncount")
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
fmt.Printf("%d\n", int(reply.(float64)))
|
||||
case "getdifficulty":
|
||||
msg, err := btcjson.CreateMessage("getdifficulty")
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
fmt.Printf("%f\n", reply.(float64))
|
||||
case "getgenerate":
|
||||
msg, err := btcjson.CreateMessage("getgenerate")
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
fmt.Printf("%v\n", reply.(bool))
|
||||
case "getpeerinfo":
|
||||
msg, err := btcjson.CreateMessage("getpeerinfo")
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
spew.Dump(reply)
|
||||
case "getrawmempool":
|
||||
msg, err := btcjson.CreateMessage("getrawmempool")
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
spew.Dump(reply)
|
||||
case "getrawtransaction":
|
||||
if len(args) != 2 {
|
||||
usage(parser)
|
||||
break
|
||||
}
|
||||
msg, err := btcjson.CreateMessage("getrawtransaction", args[1], 1)
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
spew.Dump(reply)
|
||||
case "stop":
|
||||
msg, err := btcjson.CreateMessage("stop")
|
||||
if err != nil {
|
||||
fmt.Printf("CreateMessage: %v\n", err)
|
||||
break
|
||||
}
|
||||
reply, err := send(&cfg, msg)
|
||||
if err != nil {
|
||||
fmt.Printf("RpcCommand: %v\n", err)
|
||||
break
|
||||
}
|
||||
fmt.Printf("%s\n", reply.(string))
|
||||
}
|
||||
// handlerData contains information about how a command should be handled.
|
||||
type handlerData struct {
|
||||
requiredArgs int
|
||||
optionalArgs int
|
||||
displayHandler displayHandler
|
||||
conversionHandlers []conversionHandler
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNoData = errors.New("No data returned.")
|
||||
ErrNoDisplayHandler = errors.New("No display handler specified.")
|
||||
ErrUsage = errors.New("btcctl usage") // Real usage is shown.
|
||||
)
|
||||
|
||||
// commandHandlers is a map of commands and associate handler data
|
||||
// that is used to validate correctness and perform the command.
|
||||
var commandHandlers = map[string]*handlerData{
|
||||
"addnode": &handlerData{2, 0, displaySpewDump, nil},
|
||||
"decoderawtransaction": &handlerData{1, 0, displaySpewDump, nil},
|
||||
"getbestblockhash": &handlerData{0, 0, displayGeneric, nil},
|
||||
"getblock": &handlerData{1, 0, displaySpewDump, nil},
|
||||
"getblockcount": &handlerData{0, 0, displayFloat64, nil},
|
||||
"getblockhash": &handlerData{1, 0, displayGeneric, []conversionHandler{toInt}},
|
||||
"getconnectioncount": &handlerData{0, 0, displayFloat64, nil},
|
||||
"getdifficulty": &handlerData{0, 0, displayFloat64, nil},
|
||||
"getgenerate": &handlerData{0, 0, displayGeneric, nil},
|
||||
"getpeerinfo": &handlerData{0, 0, displaySpewDump, nil},
|
||||
"getrawmempool": &handlerData{0, 0, displaySpewDump, nil},
|
||||
"getrawtransaction": &handlerData{1, 1, displaySpewDump, []conversionHandler{nil, toInt}},
|
||||
"stop": &handlerData{0, 0, displayGeneric, nil},
|
||||
}
|
||||
|
||||
// toInt attempt to convert the passed string to an integer. It returns the
|
||||
// integer packed into interface so it can be used in the calls which expect
|
||||
// interfaces. An error will be returned if the string can't be converted to an
|
||||
// integer.
|
||||
func toInt(val string) (interface{}, error) {
|
||||
idx, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
// displayGeneric is a displayHandler that simply displays the passed interface
|
||||
// using fmt.Println.
|
||||
func displayGeneric(reply interface{}) error {
|
||||
fmt.Println(reply)
|
||||
return nil
|
||||
}
|
||||
|
||||
// displayFloat64 is a displayHandler that ensures the concrete type of the
|
||||
// passed interface is a float64 and displays it using fmt.Println. An error
|
||||
// is returned if a float64 is not passed.
|
||||
func displayFloat64(reply interface{}) error {
|
||||
if val, ok := reply.(float64); ok {
|
||||
fmt.Println(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("reply type is not a float64: %v", spew.Sdump(reply))
|
||||
}
|
||||
|
||||
// displaySpewDump is a displayHandler that simply uses spew.Dump to display the
|
||||
// passed interface.
|
||||
func displaySpewDump(reply interface{}) error {
|
||||
spew.Dump(reply)
|
||||
return nil
|
||||
}
|
||||
|
||||
// send sends a JSON-RPC command to the specified RPC server and examines the
|
||||
// results for various error conditions. It either returns a valid result or
|
||||
// an appropriate error.
|
||||
func send(cfg *config, msg []byte) (interface{}, error) {
|
||||
reply, err := btcjson.RpcCommand(cfg.RpcUser, cfg.RpcPassword, cfg.RpcServer, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reply.Error != nil {
|
||||
return nil, reply.Error
|
||||
}
|
||||
|
||||
if reply.Result == nil {
|
||||
err := ErrNoData
|
||||
return nil, err
|
||||
return nil, ErrNoData
|
||||
}
|
||||
|
||||
return reply.Result, nil
|
||||
}
|
||||
|
||||
// sendCommand creates a JSON-RPC command using the passed command and arguments
|
||||
// and then sends it. A prefix is added to any errors that occur indicating
|
||||
// what step failed.
|
||||
func sendCommand(cfg *config, command string, args ...interface{}) (interface{}, error) {
|
||||
msg, err := btcjson.CreateMessage(command, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CreateMessage: %v", err.Error())
|
||||
}
|
||||
|
||||
reply, err := send(cfg, msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("RpcCommand: %v", err.Error())
|
||||
}
|
||||
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
// commandHandler handles commands provided via the cli using the specific
|
||||
// handler data to instruct the handler what to do.
|
||||
func commandHandler(cfg *config, command string, data *handlerData, args []string) error {
|
||||
// Ensure the number of arguments are the expected value.
|
||||
if len(args) < data.requiredArgs {
|
||||
return ErrUsage
|
||||
}
|
||||
if len(args) > data.requiredArgs+data.optionalArgs {
|
||||
return ErrUsage
|
||||
}
|
||||
|
||||
// Ensure the number of conversion handlers is valid if any are
|
||||
// specified.
|
||||
convHandlers := data.conversionHandlers
|
||||
if convHandlers != nil && len(convHandlers) < len(args) {
|
||||
return fmt.Errorf("The number of conversion handlers is invalid.")
|
||||
}
|
||||
|
||||
// Convert input parameters per the conversion handlers.
|
||||
iargs := make([]interface{}, len(args))
|
||||
for i, arg := range args {
|
||||
iargs[i] = arg
|
||||
}
|
||||
for i := range iargs {
|
||||
converter := convHandlers[i]
|
||||
if converter != nil {
|
||||
convertedArg, err := converter(args[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iargs[i] = convertedArg
|
||||
}
|
||||
}
|
||||
|
||||
// Create and send the appropriate JSON-RPC command.
|
||||
reply, err := sendCommand(cfg, command, iargs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure there is a valid display handler and use it to display the
|
||||
// results of the JSON-RPC command.
|
||||
if data.displayHandler == nil {
|
||||
return ErrNoDisplayHandler
|
||||
}
|
||||
err = data.displayHandler(reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// usage displays the command usage.
|
||||
func usage(parser *flags.Parser) {
|
||||
parser.WriteHelp(os.Stderr)
|
||||
fmt.Fprintf(os.Stderr,
|
||||
|
@ -257,6 +204,45 @@ func usage(parser *flags.Parser) {
|
|||
"\tgetgenerate\n"+
|
||||
"\tgetpeerinfo\n"+
|
||||
"\tgetrawmempool\n"+
|
||||
"\tgetrawtransaction <txhash>\n"+
|
||||
"\tgetrawtransaction <txhash> [verbose=0]\n"+
|
||||
"\tstop\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Parse command line and show usage if needed.
|
||||
cfg := config{
|
||||
RpcServer: "127.0.0.1:8334",
|
||||
}
|
||||
parser := flags.NewParser(&cfg, flags.PassDoubleDash)
|
||||
args, err := parser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
usage(parser)
|
||||
}
|
||||
return
|
||||
}
|
||||
if cfg.Help {
|
||||
usage(parser)
|
||||
return
|
||||
}
|
||||
|
||||
// Display usage if the command is not supported.
|
||||
data, exists := commandHandlers[args[0]]
|
||||
if !exists {
|
||||
fmt.Fprintf(os.Stderr, "Unrecognized command: %s\n", args[0])
|
||||
usage(parser)
|
||||
return
|
||||
}
|
||||
|
||||
// Execute the command.
|
||||
err = commandHandler(&cfg, args[0], data, args[1:])
|
||||
if err != nil {
|
||||
if err == ErrUsage {
|
||||
usage(parser)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue