commit b57628ea9f3b4f8f15198f77578b921b0b142b31 Author: Josh Rickmar Date: Wed Nov 6 11:10:22 2013 -0500 Initial commit. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..62a53cea --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2013 Conformal Systems LLC. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/cmds.go b/cmds.go new file mode 100644 index 00000000..8e8eb234 --- /dev/null +++ b/cmds.go @@ -0,0 +1,470 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcws + +import ( + "encoding/json" + "errors" + "github.com/conformal/btcdb" + "github.com/conformal/btcjson" + "github.com/conformal/btcwire" +) + +func init() { + btcjson.RegisterCustomCmd("getcurrentnet", parseGetCurrentNetCmd) + btcjson.RegisterCustomCmd("getbestblock", parseGetBestBlockCmd) + btcjson.RegisterCustomCmd("rescan", parseRescanCmd) + btcjson.RegisterCustomCmd("notifynewtxs", parseNotifyNewTXsCmd) + btcjson.RegisterCustomCmd("notifyspent", parseNotifySpentCmd) +} + +// GetCurrentNetCmd is a type handling custom marshaling and +// unmarshaling of getcurrentnet JSON websocket extension +// commands. +type GetCurrentNetCmd struct { + id interface{} +} + +// Enforce that GetCurrentNetCmd satisifies the btcjson.Cmd interface. +var _ btcjson.Cmd = &GetCurrentNetCmd{} + +// NewGetCurrentNetCmd creates a new GetCurrentNetCmd. +func NewGetCurrentNetCmd(id interface{}) *GetCurrentNetCmd { + return &GetCurrentNetCmd{id: id} +} + +// parseGetCurrentNetCmd parses a RawCmd into a concrete type satisifying +// the btcjson.Cmd interface. This is used when registering the custom +// command with the btcjson parser. +func parseGetCurrentNetCmd(r *btcjson.RawCmd) (btcjson.Cmd, error) { + if len(r.Params) != 0 { + return nil, btcjson.ErrWrongNumberOfParams + } + + return NewGetCurrentNetCmd(r.Id), nil +} + +// Id satisifies the Cmd interface by returning the ID of the command. +func (cmd *GetCurrentNetCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the RPC method. +func (cmd *GetCurrentNetCmd) Method() string { + return "getcurrentnet" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetCurrentNetCmd) MarshalJSON() ([]byte, error) { + // Fill a RawCmd and marshal. + raw := btcjson.RawCmd{ + Jsonrpc: "1.0", + Method: "getcurrentnet", + Id: cmd.id, + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetCurrentNetCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd. + var r btcjson.RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + newCmd, err := parseGetCurrentNetCmd(&r) + if err != nil { + return err + } + + concreteCmd, ok := newCmd.(*GetCurrentNetCmd) + if !ok { + return btcjson.ErrInternal + } + *cmd = *concreteCmd + return nil +} + +// GetBestBlockCmd is a type handling custom marshaling and +// unmarshaling of getbestblock JSON websocket extension +// commands. +type GetBestBlockCmd struct { + id interface{} +} + +// Enforce that GetBestBlockCmd satisifies the btcjson.Cmd interface. +var _ btcjson.Cmd = &GetBestBlockCmd{} + +// NewGetBestBlockCmd creates a new GetBestBlock. +func NewGetBestBlockCmd(id interface{}) *GetBestBlockCmd { + return &GetBestBlockCmd{id: id} +} + +// parseGetBestBlockCmd parses a RawCmd into a concrete type satisifying +// the btcjson.Cmd interface. This is used when registering the custom +// command with the btcjson parser. +func parseGetBestBlockCmd(r *btcjson.RawCmd) (btcjson.Cmd, error) { + if len(r.Params) != 0 { + return nil, btcjson.ErrWrongNumberOfParams + } + + return NewGetBestBlockCmd(r.Id), nil +} + +// Id satisifies the Cmd interface by returning the ID of the command. +func (cmd *GetBestBlockCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the RPC method. +func (cmd *GetBestBlockCmd) Method() string { + return "getbestblock" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetBestBlockCmd) MarshalJSON() ([]byte, error) { + // Fill a RawCmd and marshal. + raw := btcjson.RawCmd{ + Jsonrpc: "1.0", + Method: "getbestblock", + Id: cmd.id, + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetBestBlockCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd. + var r btcjson.RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + newCmd, err := parseGetBestBlockCmd(&r) + if err != nil { + return err + } + + concreteCmd, ok := newCmd.(*GetBestBlockCmd) + if !ok { + return btcjson.ErrInternal + } + *cmd = *concreteCmd + return nil +} + +// RescanCmd is a type handling custom marshaling and +// unmarshaling of rescan JSON websocket extension +// commands. +type RescanCmd struct { + id interface{} + BeginBlock int32 + Addresses map[string]struct{} + EndBlock int64 // TODO: switch this and btcdb.AllShas to int32 +} + +// Enforce that RescanCmd satisifies the btcjson.Cmd interface. +var _ btcjson.Cmd = &RescanCmd{} + +// NewRescanCmd creates a new RescanCmd, parsing the optional +// arguments optArgs which may either be empty or a single upper +// block height. +func NewRescanCmd(id interface{}, begin int32, addresses map[string]struct{}, + optArgs ...int64) (*RescanCmd, error) { + + // Optional parameters set to their defaults. + end := btcdb.AllShas + + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, btcjson.ErrTooManyOptArgs + } + end = optArgs[0] + } + + return &RescanCmd{ + id: id, + BeginBlock: begin, + Addresses: addresses, + EndBlock: end, + }, nil +} + +// parseRescanCmd parses a RawCmd into a concrete type satisifying +// the btcjson.Cmd interface. This is used when registering the custom +// command with the btcjson parser. +func parseRescanCmd(r *btcjson.RawCmd) (btcjson.Cmd, error) { + if len(r.Params) < 2 { + return nil, btcjson.ErrWrongNumberOfParams + } + + begin, ok := r.Params[0].(float64) + if !ok { + return nil, errors.New("first parameter must be a number") + } + iaddrs, ok := r.Params[1].(map[string]interface{}) + if !ok { + return nil, errors.New("second parameter must be a JSON object") + } + addresses := make(map[string]struct{}, len(iaddrs)) + for addr := range iaddrs { + addresses[addr] = struct{}{} + } + params := make([]int64, len(r.Params[2:])) + for i, val := range r.Params[2:] { + fval, ok := val.(float64) + if !ok { + return nil, errors.New("optional parameters must " + + "be be numbers") + } + params[i] = int64(fval) + } + + return NewRescanCmd(r.Id, int32(begin), addresses, params...) +} + +// Id satisifies the Cmd interface by returning the ID of the command. +func (cmd *RescanCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the RPC method. +func (cmd *RescanCmd) Method() string { + return "rescan" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *RescanCmd) MarshalJSON() ([]byte, error) { + // Fill a RawCmd and marshal. + raw := btcjson.RawCmd{ + Jsonrpc: "1.0", + Method: "rescan", + Id: cmd.id, + Params: []interface{}{ + cmd.BeginBlock, + cmd.Addresses, + }, + } + + if cmd.EndBlock != btcdb.AllShas { + raw.Params = append(raw.Params, cmd.EndBlock) + } + + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *RescanCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd. + var r btcjson.RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + newCmd, err := parseRescanCmd(&r) + if err != nil { + return err + } + + concreteCmd, ok := newCmd.(*RescanCmd) + if !ok { + return btcjson.ErrInternal + } + *cmd = *concreteCmd + return nil +} + +// NotifyNewTXsCmd is a type handling custom marshaling and +// unmarshaling of notifynewtxs JSON websocket extension +// commands. +type NotifyNewTXsCmd struct { + id interface{} + Addresses []string +} + +// Enforce that NotifyNewTXsCmd satisifies the btcjson.Cmd interface. +var _ btcjson.Cmd = &NotifyNewTXsCmd{} + +// NewNotifyNewTXsCmd creates a new RescanCmd, parsing the optional +// arguments optArgs which may either be empty or a +func NewNotifyNewTXsCmd(id interface{}, addresses []string) *NotifyNewTXsCmd { + return &NotifyNewTXsCmd{ + id: id, + Addresses: addresses, + } +} + +// parseNotifyNewTXsCmd parses a NotifyNewTXsCmd into a concrete type +// satisifying the btcjson.Cmd interface. This is used when registering +// the custom command with the btcjson parser. +func parseNotifyNewTXsCmd(r *btcjson.RawCmd) (btcjson.Cmd, error) { + if len(r.Params) != 1 { + return nil, btcjson.ErrWrongNumberOfParams + } + + iaddrs, ok := r.Params[0].([]interface{}) + if !ok { + return nil, errors.New("first parameter must be a JSON array") + } + addresses := make([]string, len(iaddrs)) + for i := range iaddrs { + addr, ok := iaddrs[i].(string) + if !ok { + return nil, errors.New("first parameter must be an " + + "array of strings") + } + addresses[i] = addr + } + + return NewNotifyNewTXsCmd(r.Id, addresses), nil +} + +// Id satisifies the Cmd interface by returning the ID of the command. +func (cmd *NotifyNewTXsCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the RPC method. +func (cmd *NotifyNewTXsCmd) Method() string { + return "notifynewtxs" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *NotifyNewTXsCmd) MarshalJSON() ([]byte, error) { + // Fill a RawCmd and marshal. + raw := btcjson.RawCmd{ + Jsonrpc: "1.0", + Method: "notifynewtxs", + Id: cmd.id, + Params: []interface{}{ + cmd.Addresses, + }, + } + + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *NotifyNewTXsCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd. + var r btcjson.RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + newCmd, err := parseNotifyNewTXsCmd(&r) + if err != nil { + return err + } + + concreteCmd, ok := newCmd.(*NotifyNewTXsCmd) + if !ok { + return btcjson.ErrInternal + } + *cmd = *concreteCmd + return nil +} + +// NotifySpentCmd is a type handling custom marshaling and +// unmarshaling of notifyspent JSON websocket extension +// commands. +type NotifySpentCmd struct { + id interface{} + *btcwire.OutPoint +} + +// Enforce that NotifySpentCmd satisifies the btcjson.Cmd interface. +var _ btcjson.Cmd = &NotifySpentCmd{} + +// NewNotifySpentCmd creates a new RescanCmd, parsing the optional +// arguments optArgs which may either be empty or a +func NewNotifySpentCmd(id interface{}, op *btcwire.OutPoint) *NotifySpentCmd { + return &NotifySpentCmd{ + id: id, + OutPoint: op, + } +} + +// parseNotifySpentCmd parses a NotifySpentCmd into a concrete type +// satisifying the btcjson.Cmd interface. This is used when registering +// the custom command with the btcjson parser. +func parseNotifySpentCmd(r *btcjson.RawCmd) (btcjson.Cmd, error) { + if len(r.Params) != 2 { + return nil, btcjson.ErrWrongNumberOfParams + } + + hashStr, ok := r.Params[0].(string) + if !ok { + return nil, errors.New("first parameter must be a string") + } + hash, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + return nil, errors.New("first parameter is not a valid " + + "hash string") + } + idx, ok := r.Params[1].(float64) + if !ok { + return nil, errors.New("second parameter is not a number") + } + if idx < 0 { + return nil, errors.New("second parameter cannot be negative") + } + + cmd := NewNotifySpentCmd(r.Id, btcwire.NewOutPoint(hash, uint32(idx))) + return cmd, nil +} + +// Id satisifies the Cmd interface by returning the ID of the command. +func (cmd *NotifySpentCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the RPC method. +func (cmd *NotifySpentCmd) Method() string { + return "notifyspent" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *NotifySpentCmd) MarshalJSON() ([]byte, error) { + // Fill a RawCmd and marshal. + raw := btcjson.RawCmd{ + Jsonrpc: "1.0", + Method: "notifyspent", + Id: cmd.id, + Params: []interface{}{ + cmd.OutPoint.Hash.String(), + cmd.OutPoint.Index, + }, + } + + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *NotifySpentCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd. + var r btcjson.RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + newCmd, err := parseNotifySpentCmd(&r) + if err != nil { + return err + } + + concreteCmd, ok := newCmd.(*NotifySpentCmd) + if !ok { + return btcjson.ErrInternal + } + *cmd = *concreteCmd + return nil +}