From 1ec6dde39ce626d5a12f55c10618adda0acbb911 Mon Sep 17 00:00:00 2001 From: Josh Rickmar Date: Mon, 9 Jun 2014 19:49:41 -0500 Subject: [PATCH] Add custom/raw request/response support. --- chain.go | 141 ++++++++-------- extensions.go | 95 ++++++----- infrastructure.go | 226 +++++++++++++------------- mining.go | 94 +++++------ net.go | 80 ++++----- notify.go | 392 ++++++++++++++++++++++++++++++++++++--------- rawrequest.go | 90 +++++++++++ rawtransactions.go | 94 +++++------ wallet.go | 374 +++++++++++++++++++++--------------------- 9 files changed, 948 insertions(+), 638 deletions(-) create mode 100644 rawrequest.go diff --git a/chain.go b/chain.go index 5808dd2a..53e6fdf9 100644 --- a/chain.go +++ b/chain.go @@ -7,7 +7,7 @@ package btcrpcclient import ( "bytes" "encoding/hex" - "fmt" + "encoding/json" "github.com/conformal/btcjson" "github.com/conformal/btcutil" "github.com/conformal/btcwire" @@ -15,23 +15,22 @@ import ( // FutureGetBestBlockHashResult is a future promise to deliver the result of a // GetBestBlockAsync RPC invocation (or an applicable error). -type FutureGetBestBlockHashResult chan *futureResult +type FutureGetBestBlockHashResult chan *response // Receive waits for the response promised by the future and returns the hash of // the best block in the longest block chain. func (r FutureGetBestBlockHashResult) Receive() (*btcwire.ShaHash, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - txHashStr, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getbestblockhash: %T\n", reply) + // Unmarshal result as a string. + var txHashStr string + err = json.Unmarshal(res, &txHashStr) + if err != nil { + return nil, err } - return btcwire.NewShaHashFromStr(txHashStr) } @@ -58,21 +57,21 @@ func (c *Client) GetBestBlockHash() (*btcwire.ShaHash, error) { // FutureGetBlockResult is a future promise to deliver the result of a // GetBlockAsync RPC invocation (or an applicable error). -type FutureGetBlockResult chan *futureResult +type FutureGetBlockResult chan *response // Receive waits for the response promised by the future and returns the raw // block requested from the server given its hash. func (r FutureGetBlockResult) Receive() (*btcutil.Block, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - blockHex, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getblock (verbose=0): %T\n", reply) + // Unmarshal result as a string. + var blockHex string + err = json.Unmarshal(res, &blockHex) + if err != nil { + return nil, err } // Decode the serialized block hex to raw bytes. @@ -120,24 +119,23 @@ func (c *Client) GetBlock(blockHash *btcwire.ShaHash) (*btcutil.Block, error) { // FutureGetBlockVerboseResult is a future promise to deliver the result of a // GetBlockVerboseAsync RPC invocation (or an applicable error). -type FutureGetBlockVerboseResult chan *futureResult +type FutureGetBlockVerboseResult chan *response // Receive waits for the response promised by the future and returns the data // structure from the server with information about the requested block. func (r FutureGetBlockVerboseResult) Receive() (*btcjson.BlockResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - blockResult, ok := reply.(*btcjson.BlockResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getblock (verbose=1): %T\n", reply) + // Unmarshal the raw result into a BlockResult. + var blockResult btcjson.BlockResult + err = json.Unmarshal(res, &blockResult) + if err != nil { + return nil, err } - - return blockResult, nil + return &blockResult, nil } // GetBlockVerboseAsync returns an instance of a type that can be used to get @@ -170,24 +168,23 @@ func (c *Client) GetBlockVerbose(blockHash *btcwire.ShaHash, verboseTx bool) (*b // FutureGetBlockCountResult is a future promise to deliver the result of a // GetBlockCountAsync RPC invocation (or an applicable error). -type FutureGetBlockCountResult chan *futureResult +type FutureGetBlockCountResult chan *response // Receive waits for the response promised by the future and returns the number // of blocks in the longest block chain. func (r FutureGetBlockCountResult) Receive() (int64, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return 0, err } - // Ensure the returned data is the expected type. - count, ok := reply.(float64) - if !ok { - return 0, fmt.Errorf("unexpected response type for "+ - "getblockcount: %T\n", reply) + // Unmarshal the result as an int64. + var count int64 + err = json.Unmarshal(res, &count) + if err != nil { + return 0, err } - - return int64(count), nil + return count, nil } // GetBlockCountAsync returns an instance of a type that can be used to get the @@ -212,23 +209,22 @@ func (c *Client) GetBlockCount() (int64, error) { // FutureGetDifficultyResult is a future promise to deliver the result of a // GetDifficultyAsync RPC invocation (or an applicable error). -type FutureGetDifficultyResult chan *futureResult +type FutureGetDifficultyResult chan *response // Receive waits for the response promised by the future and returns the // proof-of-work difficulty as a multiple of the minimum difficulty. func (r FutureGetDifficultyResult) Receive() (float64, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return 0, err } - // Ensure the returned data is the expected type. - difficulty, ok := reply.(float64) - if !ok { - return 0, fmt.Errorf("unexpected response type for "+ - "getdifficulty: %T\n", reply) + // Unmarshal the result as a float64. + var difficulty float64 + err = json.Unmarshal(res, &difficulty) + if err != nil { + return 0, err } - return difficulty, nil } @@ -255,23 +251,22 @@ func (c *Client) GetDifficulty() (float64, error) { // FutureGetBlockHashResult is a future promise to deliver the result of a // GetBlockHashAsync RPC invocation (or an applicable error). -type FutureGetBlockHashResult chan *futureResult +type FutureGetBlockHashResult chan *response // Receive waits for the response promised by the future and returns the hash of // the block in the best block chain at the given height. func (r FutureGetBlockHashResult) Receive() (*btcwire.ShaHash, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - txHashStr, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getblockhash: %T\n", reply) + // Unmarshal the result as a string-encoded sha. + var txHashStr string + err = json.Unmarshal(res, &txHashStr) + if err != nil { + return nil, err } - return btcwire.NewShaHashFromStr(txHashStr) } @@ -298,23 +293,24 @@ func (c *Client) GetBlockHash(blockHeight int64) (*btcwire.ShaHash, error) { // FutureGetRawMempoolResult is a future promise to deliver the result of a // GetRawMempoolAsync RPC invocation (or an applicable error). -type FutureGetRawMempoolResult chan *futureResult +type FutureGetRawMempoolResult chan *response // Receive waits for the response promised by the future and returns the hashes // of all transactions in the memory pool. func (r FutureGetRawMempoolResult) Receive() ([]*btcwire.ShaHash, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - txHashStrs, ok := reply.([]string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getrawmempool (verbose=false): %T\n", reply) + // Unmarshal the result as an array of strings. + var txHashStrs []string + err = json.Unmarshal(res, &txHashStrs) + if err != nil { + return nil, err } + // Create a slice of ShaHash arrays from the string slice. txHashes := make([]*btcwire.ShaHash, 0, len(txHashStrs)) for _, hashStr := range txHashStrs { txHash, err := btcwire.NewShaHashFromStr(hashStr) @@ -352,24 +348,24 @@ func (c *Client) GetRawMempool() ([]*btcwire.ShaHash, error) { // FutureGetRawMempoolVerboseResult is a future promise to deliver the result of // a GetRawMempoolVerboseAsync RPC invocation (or an applicable error). -type FutureGetRawMempoolVerboseResult chan *futureResult +type FutureGetRawMempoolVerboseResult chan *response // Receive waits for the response promised by the future and returns a map of // transaction hashes to an associated data structure with information about the // transaction for all transactions in the memory pool. func (r FutureGetRawMempoolVerboseResult) Receive() (map[string]btcjson.GetRawMempoolResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - mempoolItems, ok := reply.(map[string]btcjson.GetRawMempoolResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getrawmempool (verbose=true): %T\n", reply) + // Unmarshal the result as a map of strings (tx shas) to their detailed + // results. + var mempoolItems map[string]btcjson.GetRawMempoolResult + err = json.Unmarshal(res, &mempoolItems) + if err != nil { + return nil, err } - return mempoolItems, nil } @@ -400,24 +396,23 @@ func (c *Client) GetRawMempoolVerbose() (map[string]btcjson.GetRawMempoolResult, // FutureVerifyChainResult is a future promise to deliver the result of a // VerifyChainAsync, VerifyChainLevelAsyncRPC, or VerifyChainBlocksAsync // invocation (or an applicable error). -type FutureVerifyChainResult chan *futureResult +type FutureVerifyChainResult chan *response // Receive waits for the response promised by the future and returns whether // or not the chain verified based on the check level and number of blocks // to verify specified in the original call. func (r FutureVerifyChainResult) Receive() (bool, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return false, err } - // Ensure the returned data is the expected type. - verified, ok := reply.(bool) - if !ok { - return false, fmt.Errorf("unexpected response type for "+ - "verifychain: %T\n", reply) + // Unmarshal the result as a boolean. + var verified bool + err = json.Unmarshal(res, &verified) + if err != nil { + return false, err } - return verified, nil } diff --git a/extensions.go b/extensions.go index 7337e40d..82a2582d 100644 --- a/extensions.go +++ b/extensions.go @@ -6,6 +6,7 @@ package btcrpcclient import ( "encoding/base64" + "encoding/json" "fmt" "github.com/conformal/btcjson" "github.com/conformal/btcutil" @@ -15,24 +16,23 @@ import ( // FutureDebugLevelResult is a future promise to deliver the result of a // DebugLevelAsync RPC invocation (or an applicable error). -type FutureDebugLevelResult chan *futureResult +type FutureDebugLevelResult chan *response // Receive waits for the response promised by the future and returns the result // of setting the debug logging level to the passed level specification or the // list of of the available subsystems for the special keyword 'show'. func (r FutureDebugLevelResult) Receive() (string, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return "", err } - // Ensure the returned data is the expected type. - result, ok := reply.(string) - if !ok { - return "", fmt.Errorf("unexpected response type for "+ - "debuglevel: %T\n", reply) + // Unmashal the result as a string. + var result string + err = json.Unmarshal(res, &result) + if err != nil { + return "", err } - return result, nil } @@ -69,7 +69,7 @@ func (c *Client) DebugLevel(levelSpec string) (string, error) { // FutureCreateEncryptedWalletResult is a future promise to deliver the error // result of a CreateEncryptedWalletAsync RPC invocation. -type FutureCreateEncryptedWalletResult chan *futureResult +type FutureCreateEncryptedWalletResult chan *response // Receive waits for and returns the error response promised by the future. func (r FutureCreateEncryptedWalletResult) Receive() error { @@ -104,28 +104,22 @@ func (c *Client) CreateEncryptedWallet(passphrase string) error { // FutureListAddressTransactionsResult is a future promise to deliver the result // of a ListAddressTransactionsAsync RPC invocation (or an applicable error). -type FutureListAddressTransactionsResult chan *futureResult +type FutureListAddressTransactionsResult chan *response // Receive waits for the response promised by the future and returns information // about all transactions associated with the provided addresses. func (r FutureListAddressTransactionsResult) Receive() ([]btcjson.ListTransactionsResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // No transactions. - if reply == nil { - return nil, nil + // Unmarshal the result as an array of listtransactions objects. + var transactions []btcjson.ListTransactionsResult + err = json.Unmarshal(res, &transactions) + if err != nil { + return nil, err } - - // Ensure the returned data is the expected type. - transactions, ok := reply.([]btcjson.ListTransactionsResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "listaddresstransactions: %T\n", reply) - } - return transactions, nil } @@ -161,30 +155,30 @@ func (c *Client) ListAddressTransactions(addresses []btcutil.Address, account st // FutureGetBestBlockResult is a future promise to deliver the result of a // GetBestBlockAsync RPC invocation (or an applicable error). -type FutureGetBestBlockResult chan *futureResult +type FutureGetBestBlockResult chan *response // Receive waits for the response promised by the future and returns the hash // and height of the block in the longest (best) chain. func (r FutureGetBestBlockResult) Receive() (*btcwire.ShaHash, int32, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, 0, err } - // Ensure the returned data is the expected type. - result, ok := reply.(*btcws.GetBestBlockResult) - if !ok { - return nil, 0, fmt.Errorf("unexpected response type for "+ - "getbestblock: %T\n", reply) + // Unmarsal result as a getbestblock result object. + var bestBlock btcws.GetBestBlockResult + err = json.Unmarshal(res, &bestBlock) + if err != nil { + return nil, 0, err } // Convert hash string. - hash, err := btcwire.NewShaHashFromStr(result.Hash) + hash, err := btcwire.NewShaHashFromStr(bestBlock.Hash) if err != nil { return nil, 0, err } - return hash, result.Height, nil + return hash, bestBlock.Height, nil } // GetBestBlockAsync returns an instance of a type that can be used to get the @@ -211,24 +205,24 @@ func (c *Client) GetBestBlock() (*btcwire.ShaHash, int32, error) { // FutureGetCurrentNetResult is a future promise to deliver the result of a // GetCurrentNetAsync RPC invocation (or an applicable error). -type FutureGetCurrentNetResult chan *futureResult +type FutureGetCurrentNetResult chan *response // Receive waits for the response promised by the future and returns the network // the server is running on. func (r FutureGetCurrentNetResult) Receive() (btcwire.BitcoinNet, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return 0, err } - // Ensure the returned data is the expected type. - fnet, ok := reply.(float64) - if !ok { - return 0, fmt.Errorf("unexpected response type for "+ - "getcurrentnet: %T\n", reply) + // Unmarshal result as an int64. + var net int64 + err = json.Unmarshal(res, &net) + if err != nil { + return 0, err } - return btcwire.BitcoinNet(fnet), nil + return btcwire.BitcoinNet(net), nil } // GetCurrentNetAsync returns an instance of a type that can be used to get the @@ -254,34 +248,35 @@ func (c *Client) GetCurrentNet() (btcwire.BitcoinNet, error) { // FutureExportWatchingWalletResult is a future promise to deliver the result of // an ExportWatchingWalletAsync RPC invocation (or an applicable error). -type FutureExportWatchingWalletResult chan *futureResult +type FutureExportWatchingWalletResult chan *response // Receive waits for the response promised by the future and returns the // exported wallet. func (r FutureExportWatchingWalletResult) Receive() ([]byte, []byte, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, nil, err } - // Ensure the returned data is the expected type. - result, ok := reply.(map[string]interface{}) - if !ok { - return nil, nil, fmt.Errorf("unexpected response type for "+ - "exportwatchingwallet: %T\n", reply) + // Unmarshal result as a JSON object. + var obj map[string]interface{} + err = json.Unmarshal(res, &obj) + if err != nil { + return nil, nil, err } - base64Wallet, ok := result["wallet"].(string) + // Check for the wallet and tx string fields in the object. + base64Wallet, ok := obj["wallet"].(string) if !ok { return nil, nil, fmt.Errorf("unexpected response type for "+ "exportwatchingwallet 'wallet' field: %T\n", - result["wallet"]) + obj["wallet"]) } - base64TxStore, ok := result["tx"].(string) + base64TxStore, ok := obj["tx"].(string) if !ok { return nil, nil, fmt.Errorf("unexpected response type for "+ "exportwatchingwallet 'tx' field: %T\n", - result["tx"]) + obj["tx"]) } walletBytes, err := base64.StdEncoding.DecodeString(base64Wallet) diff --git a/infrastructure.go b/infrastructure.go index ed6220ab..c56dc398 100644 --- a/infrastructure.go +++ b/infrastructure.go @@ -64,27 +64,20 @@ const ( connectionRetryInterval = time.Second * 5 ) -// futureResult holds information about a future promise to deliver the result -// of an asynchronous request. -type futureResult struct { - reply *btcjson.Reply - err error -} - // sendPostDetails houses an HTTP POST request to send to an RPC server as well // as the original JSON-RPC command and a channel to reply on when the server // responds with the result. type sendPostDetails struct { command btcjson.Cmd request *http.Request - responseChan chan *futureResult + responseChan chan *response } // jsonRequest holds information about a json request that is used to properly // detect, interpret, and deliver a reply to it. type jsonRequest struct { cmd btcjson.Cmd - responseChan chan *futureResult + responseChan chan *response } // Client represents a Bitcoin RPC client which allows easy access to the @@ -231,70 +224,96 @@ func (c *Client) trackRegisteredNtfns(cmd btcjson.Cmd) { } } -// handleMessage is the main handler for incoming requests. It enforces -// authentication, parses the incoming json, looks up and executes handlers -// (including pass through for standard RPC commands), sends the appropriate -// response. It also detects commands which are marked as long-running and -// sends them off to the asyncHander for processing. +type ( + // inMessage is the first type that an incoming message is unmarshaled + // into. It supports both requests (for notification support) and + // responses. The partially-unmarshaled message is a notification if + // the embedded ID (from the response) is nil. Otherwise, it is a + // response. + inMessage struct { + ID *uint64 `json:"id"` + *rawNotification + *rawResponse + } + + // rawNotification is a partially-unmarshaled JSON-RPC notification. + rawNotification struct { + Method string `json:"method"` + Params []json.RawMessage `json:"params"` + } + + // rawResponse is a partially-unmarshaled JSON-RPC response. For this + // to be valid (according to JSON-RPC 1.0 spec), ID may not be nil. + rawResponse struct { + Result json.RawMessage `json:"result"` + Error *btcjson.Error `json:"error"` + } +) + +// response is the raw bytes of a JSON-RPC result, or the error if the response +// error object was non-null. +type response struct { + result []byte + err error +} + +// result checks whether the unmarshaled response contains a non-nil error, +// returning an unmarshaled btcjson.Error (or an unmarshaling error) if so. +// If the response is not an error, the raw bytes of the request are +// returned for further unmashaling into specific result types. +func (r rawResponse) result() (result []byte, err error) { + if r.Error != nil { + return nil, r.Error + } + return r.Result, nil +} + +// handleMessage is the main handler for incoming notifications and responses. func (c *Client) handleMessage(msg []byte) { - // Attempt to unmarshal the message as a known JSON-RPC command. - if cmd, err := btcjson.ParseMarshaledCmd(msg); err == nil { - // Commands that have an ID associated with them are not - // notifications. Since this is a client, it should not - // be receiving non-notifications. - if cmd.Id() != nil { - // Invalid response - log.Warnf("Remote server sent a non-notification "+ - "JSON-RPC Request (Id: %v)", cmd.Id()) + // Attempt to unmarshal the message as either a notifiation or response. + in := inMessage{} + err := json.Unmarshal(msg, &in) + if err != nil { + log.Warnf("Remote server sent invalid message: %v", err) + return + } + + // JSON-RPC 1.0 notifications are requests with a null id. + if in.ID == nil { + ntfn := in.rawNotification + if ntfn == nil { + log.Warn("Malformed notification: missing " + + "method and parameters") + return + } + if ntfn.Method == "" { + log.Warn("Malformed notification: missing method") + return + } + // params are not optional: nil isn't valid (but len == 0 is) + if ntfn.Params == nil { + log.Warn("Malformed notification: missing params") return } - // Deliver the notification. - log.Tracef("Received notification [%s]", cmd.Method()) - c.handleNotification(cmd) + log.Tracef("Received notification [%s]", in.Method) + c.handleNotification(in.rawNotification) return } - // The message was not a command/notification, so it should be a reply - // to a previous request. - - var r btcjson.Reply - if err := json.Unmarshal([]byte(msg), &r); err != nil { - log.Warnf("Unable to unmarshal inbound message as " + - "notification or response") + if in.rawResponse == nil { + log.Warn("Malformed response: missing result and error") return } - // Ensure the reply has an id. - if r.Id == nil { - log.Warnf("Received response with no id") - return - } - - // Ensure the id is the expected type. - fid, ok := (*r.Id).(float64) - if !ok { - log.Warnf("Received unexpected id type: %T (value %v)", - *r.Id, *r.Id) - return - } - id := uint64(fid) - log.Tracef("Received response for id %d (result %v)", id, r.Result) + id := *in.ID + log.Tracef("Received response for id %d (result %s)", id, in.Result) request := c.removeRequest(id) // Nothing more to do if there is no request associated with this reply. if request == nil || request.responseChan == nil { - log.Warnf("Received unexpected reply: %s (id %d)", r.Result, id) - return - } - - // Unmarshal the reply into a concrete result if possible and deliver - // it to the associated channel. - reply, err := btcjson.ReadResultCmd(request.cmd.Method(), []byte(msg)) - if err != nil { - log.Warnf("Failed to unmarshal reply to command [%s] "+ - "(id %d): %v", request.cmd.Method(), id, err) - request.responseChan <- &futureResult{reply: nil, err: err} + log.Warnf("Received unexpected reply: %s (id %d)", in.Result, + id) return } @@ -303,8 +322,9 @@ func (c *Client) handleMessage(msg []byte) { // can automatically be re-established on reconnect. c.trackRegisteredNtfns(request.cmd) - // Deliver the reply. - request.responseChan <- &futureResult{reply: &reply, err: nil} + // Deliver the response. + result, err := in.rawResponse.result() + request.responseChan <- &response{result: result, err: err} } // wsInHandler handles all incoming messages for the websocket connection @@ -576,24 +596,17 @@ func (c *Client) handleSendPostMessage(details *sendPostDetails) { log.Tracef("Sending command [%s] with id %d", cmd.Method(), cmd.Id()) httpResponse, err := c.httpClient.Do(details.request) if err != nil { - details.responseChan <- &futureResult{reply: nil, err: err} + details.responseChan <- &response{result: nil, err: err} return } // Read the raw bytes and close the response. - respBytes, err := btcjson.GetRaw(httpResponse.Body) + resp, err := btcjson.GetRaw(httpResponse.Body) if err != nil { - details.responseChan <- &futureResult{reply: nil, err: err} + details.responseChan <- &response{result: nil, err: err} return } - - // Unmarshal the reply into a concrete result if possible. - reply, err := btcjson.ReadResultCmd(cmd.Method(), respBytes) - if err != nil { - details.responseChan <- &futureResult{reply: nil, err: err} - return - } - details.responseChan <- &futureResult{reply: &reply, err: nil} + details.responseChan <- &response{result: resp, err: nil} } // sendPostHandler handles all outgoing messages when the client is running @@ -620,9 +633,9 @@ cleanup: for { select { case details := <-c.sendPostChan: - details.responseChan <- &futureResult{ - reply: nil, - err: ErrClientShutdown, + details.responseChan <- &response{ + result: nil, + err: ErrClientShutdown, } default: @@ -637,17 +650,17 @@ cleanup: // sendPostRequest sends the passed HTTP request to the RPC server using the // HTTP client associated with the client. It is backed by a buffered channel, // so it will not block until the send channel is full. -func (c *Client) sendPostRequest(req *http.Request, command btcjson.Cmd, responseChan chan *futureResult) { +func (c *Client) sendPostRequest(req *http.Request, command btcjson.Cmd, responseChan chan *response) { // Don't send the message if shutting down. select { case <-c.shutdown: - responseChan <- &futureResult{reply: nil, err: ErrClientShutdown} + responseChan <- &response{result: nil, err: ErrClientShutdown} default: } c.sendPostChan <- &sendPostDetails{ - request: req, command: command, + request: req, responseChan: responseChan, } } @@ -655,9 +668,9 @@ func (c *Client) sendPostRequest(req *http.Request, command btcjson.Cmd, respons // newFutureError returns a new future result channel that already has the // passed error waitin on the channel with the reply set to nil. This is useful // to easily return errors from the various Async functions. -func newFutureError(err error) chan *futureResult { - responseChan := make(chan *futureResult, 1) - responseChan <- &futureResult{err: err} +func newFutureError(err error) chan *response { + responseChan := make(chan *response, 1) + responseChan <- &response{err: err} return responseChan } @@ -665,27 +678,10 @@ func newFutureError(err error) chan *futureResult { // reply or any errors. The examined errors include an error in the // futureResult and the error in the reply from the server. This will block // until the result is available on the passed channel. -func receiveFuture(responseChan chan *futureResult) (interface{}, error) { +func receiveFuture(f chan *response) ([]byte, error) { // Wait for a response on the returned channel. - response := <-responseChan - if response.err != nil { - return nil, response.err - } - - // At this point, the command was either sent to the server and - // there is a response from it, or it is intentionally a nil result - // used to bybass sends for cases such a requesting notifications when - // there are no handlers. - reply := response.reply - if reply == nil { - return nil, nil - } - - if reply.Error != nil { - return nil, reply.Error - } - - return reply.Result, nil + r := <-f + return r.result, r.err } // marshalAndSendPost marshals the passed command to JSON-RPC and sends it to @@ -694,10 +690,10 @@ func receiveFuture(responseChan chan *futureResult) (interface{}, error) { // and closed for each command when using this method, however, the underlying // HTTP client might coalesce multiple commands depending on several factors // including the remote server configuration. -func (c *Client) marshalAndSendPost(cmd btcjson.Cmd, responseChan chan *futureResult) { +func (c *Client) marshalAndSendPost(cmd btcjson.Cmd, responseChan chan *response) { marshalledJSON, err := json.Marshal(cmd) if err != nil { - responseChan <- &futureResult{reply: nil, err: err} + responseChan <- &response{result: nil, err: err} return } @@ -709,7 +705,7 @@ func (c *Client) marshalAndSendPost(cmd btcjson.Cmd, responseChan chan *futureRe url := protocol + "://" + c.config.Host req, err := http.NewRequest("POST", url, bytes.NewReader(marshalledJSON)) if err != nil { - responseChan <- &futureResult{reply: nil, err: err} + responseChan <- &response{result: nil, err: err} return } req.Close = true @@ -724,10 +720,10 @@ func (c *Client) marshalAndSendPost(cmd btcjson.Cmd, responseChan chan *futureRe // marshalAndSend marshals the passed command to JSON-RPC and sends it to the // server. It returns a response channel on which the reply will be delivered. -func (c *Client) marshalAndSend(cmd btcjson.Cmd, responseChan chan *futureResult) { - marshalledJSON, err := json.Marshal(cmd) +func (c *Client) marshalAndSend(cmd btcjson.Cmd, responseChan chan *response) { + marshalledJSON, err := cmd.MarshalJSON() if err != nil { - responseChan <- &futureResult{reply: nil, err: err} + responseChan <- &response{result: nil, err: err} return } @@ -739,12 +735,12 @@ func (c *Client) marshalAndSend(cmd btcjson.Cmd, responseChan chan *futureResult // response channel on which the reply will be deliver at some point in the // future. It handles both websocket and HTTP POST mode depending on the // configuration of the client. -func (c *Client) sendCmd(cmd btcjson.Cmd) chan *futureResult { +func (c *Client) sendCmd(cmd btcjson.Cmd) chan *response { // Choose which marshal and send function to use depending on whether // the client running in HTTP POST mode or not. When running in HTTP // POST mode, the command is issued via an HTTP client. Otherwise, // the command is issued via the asynchronous websocket channels. - responseChan := make(chan *futureResult, 1) + responseChan := make(chan *response, 1) if c.config.HttpPostMode { c.marshalAndSendPost(cmd, responseChan) return responseChan @@ -804,9 +800,9 @@ func (c *Client) Disconnect() { c.requestLock.Lock() for e := c.requestList.Front(); e != nil; e = e.Next() { req := e.Value.(*jsonRequest) - req.responseChan <- &futureResult{ - reply: nil, - err: ErrClientDisconnect, + req.responseChan <- &response{ + result: nil, + err: ErrClientDisconnect, } } c.requestLock.Unlock() @@ -834,9 +830,9 @@ func (c *Client) Shutdown() { c.requestLock.Lock() for e := c.requestList.Front(); e != nil; e = e.Next() { req := e.Value.(*jsonRequest) - req.responseChan <- &futureResult{ - reply: nil, - err: ErrClientShutdown, + req.responseChan <- &response{ + result: nil, + err: ErrClientShutdown, } } c.requestLock.Unlock() diff --git a/mining.go b/mining.go index 8336d239..b98f89a5 100644 --- a/mining.go +++ b/mining.go @@ -6,28 +6,28 @@ package btcrpcclient import ( "encoding/hex" - "fmt" + "encoding/json" "github.com/conformal/btcjson" "github.com/conformal/btcutil" ) // FutureGetGenerateResult is a future promise to deliver the result of a // GetGenerateAsync RPC invocation (or an applicable error). -type FutureGetGenerateResult chan *futureResult +type FutureGetGenerateResult chan *response // Receive waits for the response promised by the future and returns true if the // server is set to mine, otherwise false. func (r FutureGetGenerateResult) Receive() (bool, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return false, err } - // Ensure the returned data is the expected type. - result, ok := reply.(bool) - if !ok { - return false, fmt.Errorf("unexpected response type for "+ - "getgenerate: %T\n", reply) + // Unmarshal result as a boolean. + var result bool + err = json.Unmarshal(res, &result) + if err != nil { + return false, err } return result, nil @@ -55,7 +55,7 @@ func (c *Client) GetGenerate() (bool, error) { // FutureSetGenerateResult is a future promise to deliver the result of a // SetGenerateAsync RPC invocation (or an applicable error). -type FutureSetGenerateResult chan *futureResult +type FutureSetGenerateResult chan *response // Receive waits for the response promised by the future and returns an error if // any occurred when setting the server to generate coins (mine) or not. @@ -90,22 +90,22 @@ func (c *Client) SetGenerate(enable bool, numCPUs int) error { // FutureGetHashesPerSecResult is a future promise to deliver the result of a // GetHashesPerSecAsync RPC invocation (or an applicable error). -type FutureGetHashesPerSecResult chan *futureResult +type FutureGetHashesPerSecResult chan *response // Receive waits for the response promised by the future and returns a recent // hashes per second performance measurement while generating coins (mining). // Zero is returned if the server is not mining. func (r FutureGetHashesPerSecResult) Receive() (int64, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return -1, err } - // Ensure the returned data is the expected type. - result, ok := reply.(int64) - if !ok { - return -1, fmt.Errorf("unexpected response type for "+ - "getnetworkhashps: %T\n", reply) + // Unmarshal result as an int64. + var result int64 + err = json.Unmarshal(res, &result) + if err != nil { + return 0, err } return result, nil @@ -135,24 +135,24 @@ func (c *Client) GetHashesPerSec() (int64, error) { // FutureGetMiningInfoResult is a future promise to deliver the result of a // GetMiningInfoAsync RPC invocation (or an applicable error). -type FutureGetMiningInfoResult chan *futureResult +type FutureGetMiningInfoResult chan *response // Receive waits for the response promised by the future and returns the mining // information. func (r FutureGetMiningInfoResult) Receive() (*btcjson.GetMiningInfoResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - result, ok := reply.(*btcjson.GetMiningInfoResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getmininginfo: %T\n", reply) + // Unmarshal result as a getmininginfo result object. + var infoResult btcjson.GetMiningInfoResult + err = json.Unmarshal(res, &infoResult) + if err != nil { + return nil, err } - return result, nil + return &infoResult, nil } // GetMiningInfoAsync returns an instance of a type that can be used to get @@ -177,22 +177,22 @@ func (c *Client) GetMiningInfo() (*btcjson.GetMiningInfoResult, error) { // FutureGetNetworkHashPS is a future promise to deliver the result of a // GetNetworkHashPSAsync RPC invocation (or an applicable error). -type FutureGetNetworkHashPS chan *futureResult +type FutureGetNetworkHashPS chan *response // Receive waits for the response promised by the future and returns the // estimated network hashes per second for the block heights provided by the // parameters. func (r FutureGetNetworkHashPS) Receive() (int64, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return -1, err } - // Ensure the returned data is the expected type. - result, ok := reply.(int64) - if !ok { - return -1, fmt.Errorf("unexpected response type for "+ - "getnetworkhashps: %T\n", reply) + // Unmarshal result as an int64. + var result int64 + err = json.Unmarshal(res, &result) + if err != nil { + return 0, err } return result, nil @@ -275,24 +275,24 @@ func (c *Client) GetNetworkHashPS3(blocks, height int) (int64, error) { // FutureGetWork is a future promise to deliver the result of a // GetWorkAsync RPC invocation (or an applicable error). -type FutureGetWork chan *futureResult +type FutureGetWork chan *response // Receive waits for the response promised by the future and returns the hash // data to work on. func (r FutureGetWork) Receive() (*btcjson.GetWorkResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - result, ok := reply.(*btcjson.GetWorkResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getwork (request data): %T\n", reply) + // Unmarshal result as a getwork result object. + var result btcjson.GetWorkResult + err = json.Unmarshal(res, &result) + if err != nil { + return nil, err } - return result, nil + return &result, nil } // GetWorkAsync returns an instance of a type that can be used to get the result @@ -319,21 +319,21 @@ func (c *Client) GetWork() (*btcjson.GetWorkResult, error) { // FutureGetWorkSubmit is a future promise to deliver the result of a // GetWorkSubmitAsync RPC invocation (or an applicable error). -type FutureGetWorkSubmit chan *futureResult +type FutureGetWorkSubmit chan *response // Receive waits for the response promised by the future and returns whether // or not the submitted block header was accepted. func (r FutureGetWorkSubmit) Receive() (bool, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return false, err } - // Ensure the returned data is the expected type. - accepted, ok := reply.(bool) - if !ok { - return false, fmt.Errorf("unexpected response type for "+ - "getwork (submit data): %T\n", reply) + // Unmarshal result as a boolean. + var accepted bool + err = json.Unmarshal(res, &accepted) + if err != nil { + return false, err } return accepted, nil @@ -364,7 +364,7 @@ func (c *Client) GetWorkSubmit(data string) (bool, error) { // FutureSubmitBlockResult is a future promise to deliver the result of a // SubmitBlockAsync RPC invocation (or an applicable error). -type FutureSubmitBlockResult chan *futureResult +type FutureSubmitBlockResult chan *response // Receive waits for the response promised by the future and returns an error if // any occurred when submitting the block. diff --git a/net.go b/net.go index 5cf6f4d4..3b37515c 100644 --- a/net.go +++ b/net.go @@ -5,7 +5,7 @@ package btcrpcclient import ( - "fmt" + "encoding/json" "github.com/conformal/btcjson" ) @@ -34,7 +34,7 @@ func (cmd AddNodeCommand) String() string { // FutureAddNodeResult is a future promise to deliver the result of an // AddNodeAsync RPC invocation (or an applicable error). -type FutureAddNodeResult chan *futureResult +type FutureAddNodeResult chan *response // Receive waits for the response promised by the future and returns an error if // any occurred when performing the specified command. @@ -73,21 +73,21 @@ func (c *Client) AddNode(host string, command AddNodeCommand) error { // FutureGetAddedNodeInfoResult is a future promise to deliver the result of a // GetAddedNodeInfoAsync RPC invocation (or an applicable error). -type FutureGetAddedNodeInfoResult chan *futureResult +type FutureGetAddedNodeInfoResult chan *response // Receive waits for the response promised by the future and returns information // about manually added (persistent) peers. func (r FutureGetAddedNodeInfoResult) Receive() ([]btcjson.GetAddedNodeInfoResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - nodeInfo, ok := reply.([]btcjson.GetAddedNodeInfoResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getaddednodeinfo (dns=true): %T\n", reply) + // Unmarshal as an array of getaddednodeinfo result objects. + var nodeInfo []btcjson.GetAddedNodeInfoResult + err = json.Unmarshal(res, &nodeInfo) + if err != nil { + return nil, err } return nodeInfo, nil @@ -118,21 +118,21 @@ func (c *Client) GetAddedNodeInfo(peer string) ([]btcjson.GetAddedNodeInfoResult // FutureGetAddedNodeInfoNoDNSResult is a future promise to deliver the result // of a GetAddedNodeInfoNoDNSAsync RPC invocation (or an applicable error). -type FutureGetAddedNodeInfoNoDNSResult chan *futureResult +type FutureGetAddedNodeInfoNoDNSResult chan *response // Receive waits for the response promised by the future and returns a list of // manually added (persistent) peers. func (r FutureGetAddedNodeInfoNoDNSResult) Receive() ([]string, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - nodes, ok := reply.([]string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getaddednodeinfo (dns=false): %T\n", reply) + // Unmarshal result as an array of strings. + var nodes []string + err = json.Unmarshal(res, &nodes) + if err != nil { + return nil, err } return nodes, nil @@ -164,24 +164,24 @@ func (c *Client) GetAddedNodeInfoNoDNS(peer string) ([]string, error) { // FutureGetConnectionCountResult is a future promise to deliver the result // of a GetConnectionCountAsync RPC invocation (or an applicable error). -type FutureGetConnectionCountResult chan *futureResult +type FutureGetConnectionCountResult chan *response // Receive waits for the response promised by the future and returns the number // of active connections to other peers. func (r FutureGetConnectionCountResult) Receive() (int64, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return 0, err } - // Ensure the returned data is the expected type. - fcount, ok := reply.(float64) - if !ok { - return 0, fmt.Errorf("unexpected response type for "+ - "getconnectioncount: %T\n", reply) + // Unmarshal result as an int64. + var count int64 + err = json.Unmarshal(res, &count) + if err != nil { + return 0, err } - return int64(fcount), nil + return count, nil } // GetConnectionCountAsync returns an instance of a type that can be used to get @@ -206,7 +206,7 @@ func (c *Client) GetConnectionCount() (int64, error) { // FuturePingResult is a future promise to deliver the result of a PingAsync RPC // invocation (or an applicable error). -type FuturePingResult chan *futureResult +type FuturePingResult chan *response // Receive waits for the response promised by the future and returns the result // of queueing a ping to be sent to each connected peer. @@ -244,21 +244,21 @@ func (c *Client) Ping() error { // FutureGetPeerInfoResult is a future promise to deliver the result of a // GetPeerInfoAsync RPC invocation (or an applicable error). -type FutureGetPeerInfoResult chan *futureResult +type FutureGetPeerInfoResult chan *response // Receive waits for the response promised by the future and returns data about // each connected network peer. func (r FutureGetPeerInfoResult) Receive() ([]btcjson.GetPeerInfoResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - peerInfo, ok := reply.([]btcjson.GetPeerInfoResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getpeerinfo: %T\n", reply) + // Unmarshal result as an array of getpeerinfo result objects. + var peerInfo []btcjson.GetPeerInfoResult + err = json.Unmarshal(res, &peerInfo) + if err != nil { + return nil, err } return peerInfo, nil @@ -286,24 +286,24 @@ func (c *Client) GetPeerInfo() ([]btcjson.GetPeerInfoResult, error) { // FutureGetNetTotalsResult is a future promise to deliver the result of a // GetNetTotalsAsync RPC invocation (or an applicable error). -type FutureGetNetTotalsResult chan *futureResult +type FutureGetNetTotalsResult chan *response // Receive waits for the response promised by the future and returns network // traffic statistics. func (r FutureGetNetTotalsResult) Receive() (*btcjson.GetNetTotalsResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - totals, ok := reply.(*btcjson.GetNetTotalsResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getnettotals: %T\n", reply) + // Unmarshal result as a getnettotals result object. + var totals btcjson.GetNetTotalsResult + err = json.Unmarshal(res, &totals) + if err != nil { + return nil, err } - return totals, nil + return &totals, nil } // GetNetTotalsAsync returns an instance of a type that can be used to get the diff --git a/notify.go b/notify.go index 10fd2198..198abb84 100644 --- a/notify.go +++ b/notify.go @@ -7,7 +7,9 @@ package btcrpcclient import ( "bytes" "encoding/hex" + "encoding/json" "errors" + "fmt" "github.com/conformal/btcjson" "github.com/conformal/btcutil" "github.com/conformal/btcwire" @@ -68,9 +70,9 @@ func newNotificationState() *notificationState { // result waiting on the channel with the reply set to nil. This is useful // to ignore things such as notifications when the caller didn't specify any // notification handlers. -func newNilFutureResult() chan *futureResult { - responseChan := make(chan *futureResult, 1) - responseChan <- &futureResult{reply: nil} +func newNilFutureResult() chan *response { + responseChan := make(chan *response, 1) + responseChan <- &response{result: nil, err: nil} return responseChan } @@ -161,183 +163,192 @@ type NotificationHandlers struct { // for this package needs to be updated for a new notification type or // the caller is using a custom notification this package does not know // about. - OnUnknownNotification func(ntfn interface{}) + OnUnknownNotification func(method string, params []json.RawMessage) } // handleNotification examines the passed notification type, performs // conversions to get the raw notification types into higher level types and // delivers the notification to the appropriate On handler registered with // the client. -func (c *Client) handleNotification(cmd btcjson.Cmd) { +func (c *Client) handleNotification(ntfn *rawNotification) { // Ignore the notification if the client is not interested in any // notifications. if c.ntfnHandlers == nil { return } - switch ntfn := cmd.(type) { + switch ntfn.Method { // OnBlockConnected - case *btcws.BlockConnectedNtfn: + case btcws.BlockConnectedNtfnMethod: // Ignore the notification is the client is not interested in // it. if c.ntfnHandlers.OnBlockConnected == nil { return } - hash, err := btcwire.NewShaHashFromStr(ntfn.Hash) + blockSha, blockHeight, err := parseChainNtfnParams(ntfn.Params) if err != nil { - log.Warnf("Received block connected notification with "+ - "invalid hash string: %q", ntfn.Hash) + log.Warnf("Received invalid block connected "+ + "notification: %v", err) return } - c.ntfnHandlers.OnBlockConnected(hash, ntfn.Height) + c.ntfnHandlers.OnBlockConnected(blockSha, blockHeight) // OnBlockDisconnected - case *btcws.BlockDisconnectedNtfn: + case btcws.BlockDisconnectedNtfnMethod: // Ignore the notification is the client is not interested in // it. if c.ntfnHandlers.OnBlockDisconnected == nil { return } - hash, err := btcwire.NewShaHashFromStr(ntfn.Hash) + blockSha, blockHeight, err := parseChainNtfnParams(ntfn.Params) if err != nil { - log.Warnf("Received block disconnected notification "+ - "with invalid hash string: %q", ntfn.Hash) + log.Warnf("Received invalid block connected "+ + "notification: %v", err) return } - c.ntfnHandlers.OnBlockDisconnected(hash, ntfn.Height) + c.ntfnHandlers.OnBlockDisconnected(blockSha, blockHeight) // OnRecvTx - case *btcws.RecvTxNtfn: + case btcws.RecvTxNtfnMethod: // Ignore the notification is the client is not interested in // it. if c.ntfnHandlers.OnRecvTx == nil { return } - // Decode the serialized transaction hex to raw bytes. - serializedTx, err := hex.DecodeString(ntfn.HexTx) + tx, block, err := parseChainTxNtfnParams(ntfn.Params) if err != nil { - log.Warnf("Received recvtx notification with invalid "+ - "transaction hex '%q': %v", ntfn.HexTx, err) - } - - // Deserialize the transaction. - var msgTx btcwire.MsgTx - err = msgTx.Deserialize(bytes.NewReader(serializedTx)) - if err != nil { - log.Warnf("Received recvtx notification with "+ - "transaction that failed to deserialize: %v", + log.Warnf("Received invalid recvtx notification: %v", err) + return } - c.ntfnHandlers.OnRecvTx(btcutil.NewTx(&msgTx), ntfn.Block) + c.ntfnHandlers.OnRecvTx(tx, block) // OnRedeemingTx - case *btcws.RedeemingTxNtfn: + case btcws.RedeemingTxNtfnMethod: // Ignore the notification is the client is not interested in // it. if c.ntfnHandlers.OnRedeemingTx == nil { return } - // Decode the serialized transaction hex to raw bytes. - serializedTx, err := hex.DecodeString(ntfn.HexTx) + tx, block, err := parseChainTxNtfnParams(ntfn.Params) if err != nil { - log.Warnf("Received redeemingtx notification with "+ - "invalid transaction hex '%q': %v", ntfn.HexTx, - err) + log.Warnf("Received invalid redeemingtx "+ + "notification: %v", err) + return } - // Deserialize the transaction. - var msgTx btcwire.MsgTx - err = msgTx.Deserialize(bytes.NewReader(serializedTx)) - if err != nil { - log.Warnf("Received redeemingtx notification with "+ - "transaction that failed to deserialize: %v", - err) - } - - c.ntfnHandlers.OnRedeemingTx(btcutil.NewTx(&msgTx), ntfn.Block) + c.ntfnHandlers.OnRedeemingTx(tx, block) // OnRescanProgress - case *btcws.RescanProgressNtfn: + case btcws.RescanProgressNtfnMethod: // Ignore the notification is the client is not interested in // it. if c.ntfnHandlers.OnRescanProgress == nil { return } - c.ntfnHandlers.OnRescanProgress(ntfn.LastProcessed) + lastProcessed, err := parseRescanProgressNtfnParams(ntfn.Params) + if err != nil { + log.Warnf("Received invalid rescanprogress "+ + "notification: %v", err) + return + } + + c.ntfnHandlers.OnRescanProgress(lastProcessed) // OnTxAccepted - case *btcws.TxAcceptedNtfn: + case btcws.TxAcceptedNtfnMethod: // Ignore the notification is the client is not interested in // it. if c.ntfnHandlers.OnTxAccepted == nil { return } - hash, err := btcwire.NewShaHashFromStr(ntfn.TxID) + hash, amt, err := parseTxAcceptedNtfnParams(ntfn.Params) if err != nil { - log.Warnf("Received tx accepted notification with "+ - "invalid hash string: %q", ntfn.TxID) + log.Warnf("Received invalid tx accepted "+ + "notification: %v", err) return } - c.ntfnHandlers.OnTxAccepted(hash, btcutil.Amount(ntfn.Amount)) + c.ntfnHandlers.OnTxAccepted(hash, amt) // OnTxAcceptedVerbose - case *btcws.TxAcceptedVerboseNtfn: + case btcws.TxAcceptedVerboseNtfnMethod: // Ignore the notification is the client is not interested in // it. if c.ntfnHandlers.OnTxAcceptedVerbose == nil { return } - c.ntfnHandlers.OnTxAcceptedVerbose(ntfn.RawTx) + rawTx, err := parseTxAcceptedVerboseNtfnParams(ntfn.Params) + if err != nil { + log.Warnf("Received invalid tx accepted verbose "+ + "notification: %v", err) + return + } + + c.ntfnHandlers.OnTxAcceptedVerbose(rawTx) // OnBtcdConnected - case *btcws.BtcdConnectedNtfn: + case btcws.BtcdConnectedNtfnMethod: // Ignore the notification is the client is not interested in // it. if c.ntfnHandlers.OnBtcdConnected == nil { return } - c.ntfnHandlers.OnBtcdConnected(ntfn.Connected) + connected, err := parseBtcdConnectedNtfnParams(ntfn.Params) + if err != nil { + log.Warnf("Received invalid btcd connected "+ + "notification: %v", err) + return + } + + c.ntfnHandlers.OnBtcdConnected(connected) // OnAccountBalance - case *btcws.AccountBalanceNtfn: + case btcws.AccountBalanceNtfnMethod: // Ignore the notification is the client is not interested in // it. if c.ntfnHandlers.OnAccountBalance == nil { return } - balance, err := btcjson.JSONToAmount(ntfn.Balance) + account, bal, conf, err := parseAccountBalanceNtfnParams(ntfn.Params) if err != nil { - log.Warnf("Received account balance notification with "+ - "an amount that does not parse: %v", - ntfn.Balance) + log.Warnf("Received invalid account balance "+ + "notification: %v", err) return } - c.ntfnHandlers.OnAccountBalance(ntfn.Account, - btcutil.Amount(balance), ntfn.Confirmed) + c.ntfnHandlers.OnAccountBalance(account, bal, conf) // OnWalletLockState - case *btcws.WalletLockStateNtfn: + case btcws.WalletLockStateNtfnMethod: // Ignore the notification is the client is not interested in // it. if c.ntfnHandlers.OnWalletLockState == nil { return } - c.ntfnHandlers.OnWalletLockState(ntfn.Locked) + // The account name is not notified, so the return value is + // discarded. + _, locked, err := parseWalletLockStateNtfnParams(ntfn.Params) + if err != nil { + log.Warnf("Received invalid wallet lock state "+ + "notification: %v", err) + return + } + + c.ntfnHandlers.OnWalletLockState(locked) // OnUnknownNotification default: @@ -345,13 +356,250 @@ func (c *Client) handleNotification(cmd btcjson.Cmd) { return } - c.ntfnHandlers.OnUnknownNotification(ntfn) + c.ntfnHandlers.OnUnknownNotification(ntfn.Method, ntfn.Params) } } +// wrongNumParams is an error type describing an unparseable JSON-RPC +// notificiation due to an incorrect number of parameters for the +// expected notification type. The value is the number of parameters +// of the invalid notification. +type wrongNumParams int + +// Error satisifies the builtin error interface. +func (e wrongNumParams) Error() string { + return fmt.Sprintf("wrong number of parameters (%d)", e) +} + +// parseChainNtfnParams parses out the block hash and height from the parameters +// of blockconnected and blockdisconnected notifications. +func parseChainNtfnParams(params []json.RawMessage) (*btcwire.ShaHash, + int32, error) { + + if len(params) != 2 { + return nil, 0, wrongNumParams(len(params)) + } + + // Unmarshal first parameter as a string. + var blockShaStr string + err := json.Unmarshal(params[0], &blockShaStr) + if err != nil { + return nil, 0, err + } + + // Unmarshal second parameter as an integer. + var blockHeight int32 + err = json.Unmarshal(params[1], &blockHeight) + if err != nil { + return nil, 0, err + } + + // Create ShaHash from block sha string. + blockSha, err := btcwire.NewShaHashFromStr(blockShaStr) + if err != nil { + return nil, 0, err + } + + return blockSha, blockHeight, nil +} + +// parseChainTxNtfnParams parses out the transaction and optional details about +// the block it's mined in from the parameters of recvtx and redeemingtx +// notifications. +func parseChainTxNtfnParams(params []json.RawMessage) (*btcutil.Tx, + *btcws.BlockDetails, error) { + + if len(params) == 0 || len(params) > 2 { + return nil, nil, wrongNumParams(len(params)) + } + + // Unmarshal first parameter as a string. + var txHex string + err := json.Unmarshal(params[0], &txHex) + if err != nil { + return nil, nil, err + } + + // If present, unmarshal second optional parameter as the block details + // JSON object. + var block *btcws.BlockDetails + if len(params) > 1 { + err = json.Unmarshal(params[1], &block) + if err != nil { + return nil, nil, err + } + } + + // Hex decode and deserialize the transaction. + serializedTx, err := hex.DecodeString(txHex) + if err != nil { + return nil, nil, err + } + var msgTx btcwire.MsgTx + err = msgTx.Deserialize(bytes.NewReader(serializedTx)) + if err != nil { + return nil, nil, err + } + + // TODO: Change recvtx and redeemingtx callback signatures to use + // nicer types for details about the block (block sha as a + // btcwire.ShaHash, block time as a time.Time, etc.). + return btcutil.NewTx(&msgTx), block, nil +} + +// parseRescanProgressNtfnParams parses out the height of the last rescanned +// from the parameters of a rescanprogress notification. +func parseRescanProgressNtfnParams(params []json.RawMessage) (int32, error) { + if len(params) != 1 { + return 0, wrongNumParams(len(params)) + } + + // Unmarshal first parameter as an integer. + var height int32 + err := json.Unmarshal(params[0], &height) + if err != nil { + return 0, err + } + + return height, nil +} + +// parseTxAcceptedNtfnParams parses out the transaction hash and total amount +// from the parameters of a txaccepted notification. +func parseTxAcceptedNtfnParams(params []json.RawMessage) (*btcwire.ShaHash, + btcutil.Amount, error) { + + if len(params) != 2 { + return nil, 0, wrongNumParams(len(params)) + } + + // Unmarshal first parameter as a string. + var txShaStr string + err := json.Unmarshal(params[0], &txShaStr) + if err != nil { + return nil, 0, err + } + + // Unmarshal second parameter as an integer. + var amt int64 + err = json.Unmarshal(params[1], &amt) + if err != nil { + return nil, 0, err + } + + // Decode string encoding of transaction sha. + txSha, err := btcwire.NewShaHashFromStr(txShaStr) + if err != nil { + return nil, 0, err + } + + return txSha, btcutil.Amount(amt), nil +} + +// parseTxAcceptedVerboseNtfnParams parses out details about a raw transaction +// from the parameters of a txacceptedverbose notification. +func parseTxAcceptedVerboseNtfnParams(params []json.RawMessage) (*btcjson.TxRawResult, + error) { + + if len(params) != 1 { + return nil, wrongNumParams(len(params)) + } + + // Unmarshal first parameter as a raw transaction result object. + var rawTx btcjson.TxRawResult + err := json.Unmarshal(params[0], &rawTx) + if err != nil { + return nil, err + } + + // TODO: change txacceptedverbose notifiation callbacks to use nicer + // types for all details about the transaction (i.e. decoding hashes + // from their string encoding). + return &rawTx, nil +} + +// parseBtcdConnectedNtfnParams parses out the connection status of btcd +// and btcwallet from the parameters of a btcdconnected notification. +func parseBtcdConnectedNtfnParams(params []json.RawMessage) (bool, error) { + if len(params) != 1 { + return false, wrongNumParams(len(params)) + } + + // Unmarshal first parameter as a boolean. + var connected bool + err := json.Unmarshal(params[0], &connected) + if err != nil { + return false, err + } + + return connected, nil +} + +// parseAccountBalanceNtfnParams parses out the account name, total balance, +// and whether or not the balance is confirmed or unconfirmed from the +// parameters of an accountbalance notification. +func parseAccountBalanceNtfnParams(params []json.RawMessage) (account string, + balance btcutil.Amount, confirmed bool, err error) { + + if len(params) != 3 { + return "", 0, false, wrongNumParams(len(params)) + } + + // Unmarshal first parameter as a string. + err = json.Unmarshal(params[0], &account) + if err != nil { + return "", 0, false, err + } + + // Unmarshal second parameter as a floating point number. + var fbal float64 + err = json.Unmarshal(params[1], &fbal) + if err != nil { + return "", 0, false, err + } + + // Unmarshal third parameter as a boolean. + err = json.Unmarshal(params[2], &confirmed) + if err != nil { + return "", 0, false, err + } + + // Bounds check amount. + bal, err := btcjson.JSONToAmount(fbal) + if err != nil { + return "", 0, false, err + } + + return account, btcutil.Amount(bal), confirmed, nil +} + +// parseWalletLockStateNtfnParams parses out the account name and locked +// state of an account from the parameters of a walletlockstate notification. +func parseWalletLockStateNtfnParams(params []json.RawMessage) (account string, + locked bool, err error) { + + if len(params) != 2 { + return "", false, wrongNumParams(len(params)) + } + + // Unmarshal first parameter as a string. + err = json.Unmarshal(params[0], &account) + if err != nil { + return "", false, err + } + + // Unmarshal second parameter as a boolean. + err = json.Unmarshal(params[1], &locked) + if err != nil { + return "", false, err + } + + return account, locked, nil +} + // FutureNotifyBlocksResult is a future promise to deliver the result of a // NotifyBlocksAsync RPC invocation (or an applicable error). -type FutureNotifyBlocksResult chan *futureResult +type FutureNotifyBlocksResult chan *response // Receive waits for the response promised by the future and returns an error // if the registration was not successful. @@ -405,7 +653,7 @@ func (c *Client) NotifyBlocks() error { // FutureNotifySpentResult is a future promise to deliver the result of a // NotifySpentAsync RPC invocation (or an applicable error). -type FutureNotifySpentResult chan *futureResult +type FutureNotifySpentResult chan *response // Receive waits for the response promised by the future and returns an error // if the registration was not successful. @@ -484,7 +732,7 @@ func (c *Client) NotifySpent(outpoints []*btcwire.OutPoint) error { // FutureNotifyNewTransactionsResult is a future promise to deliver the result // of a NotifyNewTransactionsAsync RPC invocation (or an applicable error). -type FutureNotifyNewTransactionsResult chan *futureResult +type FutureNotifyNewTransactionsResult chan *response // Receive waits for the response promised by the future and returns an error // if the registration was not successful. @@ -542,7 +790,7 @@ func (c *Client) NotifyNewTransactions(verbose bool) error { // FutureNotifyReceivedResult is a future promise to deliver the result of a // NotifyReceivedAsync RPC invocation (or an applicable error). -type FutureNotifyReceivedResult chan *futureResult +type FutureNotifyReceivedResult chan *response // Receive waits for the response promised by the future and returns an error // if the registration was not successful. @@ -630,7 +878,7 @@ func (c *Client) NotifyReceived(addresses []btcutil.Address) error { // FutureRescanResult is a future promise to deliver the result of a RescanAsync // or RescanEndHeightAsync RPC invocation (or an applicable error). -type FutureRescanResult chan *futureResult +type FutureRescanResult chan *response // Receive waits for the response promised by the future and returns an error // if the rescan was not successful. diff --git a/rawrequest.go b/rawrequest.go new file mode 100644 index 00000000..2792a433 --- /dev/null +++ b/rawrequest.go @@ -0,0 +1,90 @@ +// Copyright (c) 2014 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcrpcclient + +import ( + "encoding/json" + "errors" + "github.com/conformal/btcjson" +) + +// rawRequest satisifies the btcjson.Cmd interface for btcjson raw commands. +// This type exists here rather than making btcjson.RawCmd satisify the Cmd +// interface due to conflict between the Id and Method field names vs method +// names. +type rawRequest struct { + btcjson.RawCmd +} + +// Enforce that rawRequest is a btcjson.Cmd. +var _ btcjson.Cmd = &rawRequest{} + +// Id returns the JSON-RPC id of the request. +func (r *rawRequest) Id() interface{} { + return r.RawCmd.Id +} + +// Method returns the method string of the request. +func (r *rawRequest) Method() string { + return r.RawCmd.Method +} + +// MarshalJSON marshals the raw request as a JSON-RPC 1.0 object. +func (r *rawRequest) MarshalJSON() ([]byte, error) { + return json.Marshal(r.RawCmd) +} + +// UnmarshalJSON unmarshals a JSON-RPC 1.0 object into the raw request. +func (r *rawRequest) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, &r.RawCmd) +} + +// FutureRawResult is a future promise to deliver the result of a RawRequest RPC +// invocation (or an applicable error). +type FutureRawResult chan *response + +// Receive waits for the response promised by the future and returns the raw +// response, or an error if the request was unsuccessful. +func (r FutureRawResult) Receive() (json.RawMessage, error) { + return receiveFuture(r) +} + +// RawRequestAsync returns an instance of a type that can be used to get the +// result of a custom RPC request at some future time by invoking the Receive +// function on the returned instance. +// +// See RawRequest for the blocking version and more details. +func (c *Client) RawRequestAsync(method string, params []json.RawMessage) FutureRawResult { + // Method may not be empty. + if method == "" { + return newFutureError(errors.New("no method")) + } + + // Marshal parameters as "[]" instead of "null" when no parameters + // are passed. + if params == nil { + params = []json.RawMessage{} + } + + cmd := &rawRequest{ + RawCmd: btcjson.RawCmd{ + Jsonrpc: "1.0", + Id: c.NextID(), + Method: method, + Params: params, + }, + } + + return c.sendCmd(cmd) +} + +// RawRequest allows the caller to send a raw or custom request to the server. +// This method may be used to send and receive requests and responses for +// requests that are not handled by this client package, or to proxy partially +// unmarshaled requests to another JSON-RPC server if a request cannot be +// handled directly. +func (c *Client) RawRequest(method string, params []json.RawMessage) (json.RawMessage, error) { + return c.RawRequestAsync(method, params).Receive() +} diff --git a/rawtransactions.go b/rawtransactions.go index 95a7abec..bfd6cd9c 100644 --- a/rawtransactions.go +++ b/rawtransactions.go @@ -7,7 +7,7 @@ package btcrpcclient import ( "bytes" "encoding/hex" - "fmt" + "encoding/json" "github.com/conformal/btcjson" "github.com/conformal/btcutil" "github.com/conformal/btcwire" @@ -58,21 +58,21 @@ func (s SigHashType) String() string { // FutureGetRawTransactionResult is a future promise to deliver the result of a // GetRawTransactionAsync RPC invocation (or an applicable error). -type FutureGetRawTransactionResult chan *futureResult +type FutureGetRawTransactionResult chan *response // Receive waits for the response promised by the future and returns a // transaction given its hash. func (r FutureGetRawTransactionResult) Receive() (*btcutil.Tx, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - txHex, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getrawtransaction (verbose=0): %T\n", reply) + // Unmarshal result as a string. + var txHex string + err = json.Unmarshal(res, &txHex) + if err != nil { + return nil, err } // Decode the serialized transaction hex to raw bytes. @@ -120,24 +120,24 @@ func (c *Client) GetRawTransaction(txHash *btcwire.ShaHash) (*btcutil.Tx, error) // FutureGetRawTransactionVerboseResult is a future promise to deliver the // result of a GetRawTransactionVerboseAsync RPC invocation (or an applicable // error). -type FutureGetRawTransactionVerboseResult chan *futureResult +type FutureGetRawTransactionVerboseResult chan *response // Receive waits for the response promised by the future and returns information // about a transaction given its hash. func (r FutureGetRawTransactionVerboseResult) Receive() (*btcjson.TxRawResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - result, ok := reply.(*btcjson.TxRawResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getrawtransaction (verbose=1): %T\n", reply) + // Unmarshal result as a gettrawtransaction result object. + var rawTxResult btcjson.TxRawResult + err = json.Unmarshal(res, &rawTxResult) + if err != nil { + return nil, err } - return result, nil + return &rawTxResult, nil } // GetRawTransactionVerboseAsync returns an instance of a type that can be used @@ -170,24 +170,24 @@ func (c *Client) GetRawTransactionVerbose(txHash *btcwire.ShaHash) (*btcjson.TxR // FutureDecodeRawTransactionResult is a future promise to deliver the result // of a DecodeRawTransactionAsync RPC invocation (or an applicable error). -type FutureDecodeRawTransactionResult chan *futureResult +type FutureDecodeRawTransactionResult chan *response // Receive waits for the response promised by the future and returns information // about a transaction given its serialized bytes. func (r FutureDecodeRawTransactionResult) Receive() (*btcjson.TxRawResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - result, ok := reply.(*btcjson.TxRawResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "decoderawtransaction: %T\n", reply) + // Unmarshal result as a decoderawtransaction result object. + var rawTxResult btcjson.TxRawResult + err = json.Unmarshal(res, &rawTxResult) + if err != nil { + return nil, err } - return result, nil + return &rawTxResult, nil } // DecodeRawTransactionAsync returns an instance of a type that can be used to @@ -214,22 +214,22 @@ func (c *Client) DecodeRawTransaction(serializedTx []byte) (*btcjson.TxRawResult // FutureCreateRawTransactionResult is a future promise to deliver the result // of a CreateRawTransactionAsync RPC invocation (or an applicable error). -type FutureCreateRawTransactionResult chan *futureResult +type FutureCreateRawTransactionResult chan *response // Receive waits for the response promised by the future and returns a new // transaction spending the provided inputs and sending to the provided // addresses. func (r FutureCreateRawTransactionResult) Receive() (*btcwire.MsgTx, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - txHex, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "createrawtransaction: %T\n", reply) + // Unmarshal result as a string. + var txHex string + err = json.Unmarshal(res, &txHex) + if err != nil { + return nil, err } // Decode the serialized transaction hex to raw bytes. @@ -277,22 +277,22 @@ func (c *Client) CreateRawTransaction(inputs []btcjson.TransactionInput, // FutureSendRawTransactionResult is a future promise to deliver the result // of a SendRawTransactionAsync RPC invocation (or an applicable error). -type FutureSendRawTransactionResult chan *futureResult +type FutureSendRawTransactionResult chan *response // Receive waits for the response promised by the future and returns the result // of submitting the encoded transaction to the server which then relays it to // the network. func (r FutureSendRawTransactionResult) Receive() (*btcwire.ShaHash, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - txHashStr, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "decoderawtransaction: %T\n", reply) + // Unmarshal result as a string. + var txHashStr string + err = json.Unmarshal(res, &txHashStr) + if err != nil { + return nil, err } return btcwire.NewShaHashFromStr(txHashStr) @@ -332,25 +332,25 @@ func (c *Client) SendRawTransaction(tx *btcwire.MsgTx, allowHighFees bool) (*btc // FutureSignRawTransactionResult is a future promise to deliver the result // of one of the SignRawTransactionAsync family of RPC invocations (or an // applicable error). -type FutureSignRawTransactionResult chan *futureResult +type FutureSignRawTransactionResult chan *response // Receive waits for the response promised by the future and returns the // signed transaction as well as whether or not all inputs are now signed. func (r FutureSignRawTransactionResult) Receive() (*btcwire.MsgTx, bool, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, false, err } - // Ensure the returned data is the expected type. - result, ok := reply.(*btcjson.SignRawTransactionResult) - if !ok { - return nil, false, fmt.Errorf("unexpected response type for "+ - "signrawtransaction: %T\n", reply) + // Unmarshal as a signrawtransaction result. + var signRawTxResult btcjson.SignRawTransactionResult + err = json.Unmarshal(res, &signRawTxResult) + if err != nil { + return nil, false, err } // Decode the serialized transaction hex to raw bytes. - serializedTx, err := hex.DecodeString(result.Hex) + serializedTx, err := hex.DecodeString(signRawTxResult.Hex) if err != nil { return nil, false, err } @@ -361,7 +361,7 @@ func (r FutureSignRawTransactionResult) Receive() (*btcwire.MsgTx, bool, error) return nil, false, err } - return &msgTx, result.Complete, nil + return &msgTx, signRawTxResult.Complete, nil } // SignRawTransactionAsync returns an instance of a type that can be used to get diff --git a/wallet.go b/wallet.go index 5a47cca3..4ce4b2df 100644 --- a/wallet.go +++ b/wallet.go @@ -5,7 +5,7 @@ package btcrpcclient import ( - "fmt" + "encoding/json" "github.com/conformal/btcjson" "github.com/conformal/btcnet" "github.com/conformal/btcutil" @@ -19,24 +19,24 @@ import ( // FutureGetTransactionResult is a future promise to deliver the result // of a GetTransactionAsync RPC invocation (or an applicable error). -type FutureGetTransactionResult chan *futureResult +type FutureGetTransactionResult chan *response // Receive waits for the response promised by the future and returns detailed // information about a wallet transaction. func (r FutureGetTransactionResult) Receive() (*btcjson.GetTransactionResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - result, ok := reply.(*btcjson.GetTransactionResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "gettransaction: %T\n", reply) + // Unmarshal result as a gettransaction result object + var getTx btcjson.GetTransactionResult + err = json.Unmarshal(res, &getTx) + if err != nil { + return nil, err } - return result, nil + return &getTx, nil } // GetTransactionAsync returns an instance of a type that can be used to get the @@ -69,26 +69,21 @@ func (c *Client) GetTransaction(txHash *btcwire.ShaHash) (*btcjson.GetTransactio // FutureListTransactionsResult is a future promise to deliver the result of a // ListTransactionsAsync, ListTransactionsCountAsync, or // ListTransactionsCountFromAsync RPC invocation (or an applicable error). -type FutureListTransactionsResult chan *futureResult +type FutureListTransactionsResult chan *response // Receive waits for the response promised by the future and returns a list of // the most recent transactions. func (r FutureListTransactionsResult) Receive() ([]btcjson.ListTransactionsResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // No transactions. - if reply == nil { - return nil, nil - } - - // Ensure the returned data is the expected type. - transactions, ok := reply.([]btcjson.ListTransactionsResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "listtransactions: %T\n", reply) + // Unmarshal result as an array of listtransaction result objects. + var transactions []btcjson.ListTransactionsResult + err = json.Unmarshal(res, &transactions) + if err != nil { + return nil, err } return transactions, nil @@ -167,7 +162,7 @@ func (c *Client) ListTransactionsCountFrom(account string, count, from int) ([]b // FutureListUnspentResult is a future promise to deliver the result of a // ListUnspentAsync, ListUnspentMinAsync, ListUnspentMinMaxAsync, or // ListUnspentMinMaxAddressesAsync RPC invocation (or an applicable error). -type FutureListUnspentResult chan *futureResult +type FutureListUnspentResult chan *response // Receive waits for the response promised by the future and returns all // unspent wallet transaction outputs returned by the RPC call. If the @@ -175,21 +170,16 @@ type FutureListUnspentResult chan *futureResult // or ListUnspentMinMaxAddressesAsync, the range may be limited by the // parameters of the RPC invocation. func (r FutureListUnspentResult) Receive() ([]btcjson.ListUnspentResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // No unspent transaction outputs. - if reply == nil { - return nil, nil - } - - // Ensure the returned data is the expected type. - unspent, ok := reply.([]btcjson.ListUnspentResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "listunspent: %T\n", reply) + // Unmarshal result as an array of listunspent results. + var unspent []btcjson.ListUnspentResult + err = json.Unmarshal(res, &unspent) + if err != nil { + return nil, err } return unspent, nil @@ -291,25 +281,25 @@ func (c *Client) ListUnspentMinMaxAddresses(minConf, maxConf int, addrs []btcuti // FutureListSinceBlockResult is a future promise to deliver the result of a // ListSinceBlockAsync or ListSinceBlockMinConfAsync RPC invocation (or an // applicable error). -type FutureListSinceBlockResult chan *futureResult +type FutureListSinceBlockResult chan *response // Receive waits for the response promised by the future and returns all // transactions added in blocks since the specified block hash, or all // transactions if it is nil. func (r FutureListSinceBlockResult) Receive() (*btcjson.ListSinceBlockResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - listResult, ok := reply.(*btcjson.ListSinceBlockResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "listsinceblock: %T\n", reply) + // Unmarshal result as a listsinceblock result object. + var listResult btcjson.ListSinceBlockResult + err = json.Unmarshal(res, &listResult) + if err != nil { + return nil, err } - return listResult, nil + return &listResult, nil } // ListSinceBlockAsync returns an instance of a type that can be used to get @@ -376,7 +366,7 @@ func (c *Client) ListSinceBlockMinConf(blockHash *btcwire.ShaHash, minConfirms i // FutureSetTxFeeResult is a future promise to deliver the result of a // SetTxFeeAsync RPC invocation (or an applicable error). -type FutureSetTxFeeResult chan *futureResult +type FutureSetTxFeeResult chan *response // Receive waits for the response promised by the future and returns the result // of setting an optional transaction fee per KB that helps ensure transactions @@ -413,20 +403,21 @@ func (c *Client) SetTxFee(fee btcutil.Amount) error { // FutureSendToAddressResult is a future promise to deliver the result of a // SendToAddressAsync RPC invocation (or an applicable error). -type FutureSendToAddressResult chan *futureResult +type FutureSendToAddressResult chan *response // Receive waits for the response promised by the future and returns the hash // of the transaction sending the passed amount to the given address. func (r FutureSendToAddressResult) Receive() (*btcwire.ShaHash, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - txHash, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "sendtoaddress: %T\n", reply) + // Unmarshal result as a string. + var txHash string + err = json.Unmarshal(res, &txHash) + if err != nil { + return nil, err } return btcwire.NewShaHashFromStr(txHash) @@ -500,22 +491,22 @@ func (c *Client) SendToAddressComment(address btcutil.Address, amount btcutil.Am // FutureSendFromResult is a future promise to deliver the result of a // SendFromAsync, SendFromMinConfAsync, or SendFromCommentAsync RPC invocation // (or an applicable error). -type FutureSendFromResult chan *futureResult +type FutureSendFromResult chan *response // Receive waits for the response promised by the future and returns the hash // of the transaction sending amount to the given address using the provided // account as a source of funds. func (r FutureSendFromResult) Receive() (*btcwire.ShaHash, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - txHash, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "sendfrom: %T\n", reply) + // Unmarshal result as a string. + var txHash string + err = json.Unmarshal(res, &txHash) + if err != nil { + return nil, err } return btcwire.NewShaHashFromStr(txHash) @@ -622,22 +613,22 @@ func (c *Client) SendFromComment(fromAccount string, toAddress btcutil.Address, // FutureSendManyResult is a future promise to deliver the result of a // SendManyAsync, SendManyMinConfAsync, or SendManyCommentAsync RPC invocation // (or an applicable error). -type FutureSendManyResult chan *futureResult +type FutureSendManyResult chan *response // Receive waits for the response promised by the future and returns the hash // of the transaction sending multiple amounts to multiple addresses using the // provided account as a source of funds. func (r FutureSendManyResult) Receive() (*btcwire.ShaHash, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - txHash, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "sendmany: %T\n", reply) + // Unmashal result as a string. + var txHash string + err = json.Unmarshal(res, &txHash) + if err != nil { + return nil, err } return btcwire.NewShaHashFromStr(txHash) @@ -760,22 +751,22 @@ func (c *Client) SendManyComment(fromAccount string, // FutureAddMultisigAddressResult is a future promise to deliver the result of a // AddMultisigAddressAsync RPC invocation (or an applicable error). -type FutureAddMultisigAddressResult chan *futureResult +type FutureAddMultisigAddressResult chan *response // Receive waits for the response promised by the future and returns the // multisignature address that requires the specified number of signatures for // the provided addresses. func (r FutureAddMultisigAddressResult) Receive() (btcutil.Address, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - addr, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "addmultisigaddress: %T\n", reply) + // Unmarshal result as a string. + var addr string + err = json.Unmarshal(res, &addr) + if err != nil { + return nil, err } return btcutil.DecodeAddress(addr, &btcnet.MainNetParams) @@ -811,24 +802,24 @@ func (c *Client) AddMultisigAddress(requiredSigs int, addresses []btcutil.Addres // FutureCreateMultisigResult is a future promise to deliver the result of a // CreateMultisigAsync RPC invocation (or an applicable error). -type FutureCreateMultisigResult chan *futureResult +type FutureCreateMultisigResult chan *response // Receive waits for the response promised by the future and returns the // multisignature address and script needed to redeem it. func (r FutureCreateMultisigResult) Receive() (*btcjson.CreateMultiSigResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - result, ok := reply.(*btcjson.CreateMultiSigResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "createmultisig: %T\n", reply) + // Unmarshal result as a createmultisig result object. + var multisigRes btcjson.CreateMultiSigResult + err = json.Unmarshal(res, &multisigRes) + if err != nil { + return nil, err } - return result, nil + return &multisigRes, nil } // CreateMultisigAsync returns an instance of a type that can be used to get @@ -861,21 +852,21 @@ func (c *Client) CreateMultisig(requiredSigs int, addresses []btcutil.Address) ( // FutureGetNewAddressResult is a future promise to deliver the result of a // GetNewAddressAsync RPC invocation (or an applicable error). -type FutureGetNewAddressResult chan *futureResult +type FutureGetNewAddressResult chan *response // Receive waits for the response promised by the future and returns a new // address. func (r FutureGetNewAddressResult) Receive() (btcutil.Address, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - addr, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getnewaddress: %T\n", reply) + // Unmarshal result as a string. + var addr string + err = json.Unmarshal(res, &addr) + if err != nil { + return nil, err } return btcutil.DecodeAddress(addr, &btcnet.MainNetParams) @@ -903,22 +894,22 @@ func (c *Client) GetNewAddress() (btcutil.Address, error) { // FutureGetRawChangeAddressResult is a future promise to deliver the result of // a GetRawChangeAddressAsync RPC invocation (or an applicable error). -type FutureGetRawChangeAddressResult chan *futureResult +type FutureGetRawChangeAddressResult chan *response // Receive waits for the response promised by the future and returns a new // address for receiving change that will be associated with the provided // account. Note that this is only for raw transactions and NOT for normal use. func (r FutureGetRawChangeAddressResult) Receive() (btcutil.Address, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - addr, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getrawchangeaddress: %T\n", reply) + // Unmarshal result as a string. + var addr string + err = json.Unmarshal(res, &addr) + if err != nil { + return nil, err } return btcutil.DecodeAddress(addr, &btcnet.MainNetParams) @@ -948,21 +939,21 @@ func (c *Client) GetRawChangeAddress(account string) (btcutil.Address, error) { // FutureGetAccountAddressResult is a future promise to deliver the result of a // GetAccountAddressAsync RPC invocation (or an applicable error). -type FutureGetAccountAddressResult chan *futureResult +type FutureGetAccountAddressResult chan *response // Receive waits for the response promised by the future and returns the current // Bitcoin address for receiving payments to the specified account. func (r FutureGetAccountAddressResult) Receive() (btcutil.Address, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - addr, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getaccountaddress: %T\n", reply) + // Unmarshal result as a string. + var addr string + err = json.Unmarshal(res, &addr) + if err != nil { + return nil, err } return btcutil.DecodeAddress(addr, &btcnet.MainNetParams) @@ -991,21 +982,21 @@ func (c *Client) GetAccountAddress(account string) (btcutil.Address, error) { // FutureGetAccountResult is a future promise to deliver the result of a // GetAccountAsync RPC invocation (or an applicable error). -type FutureGetAccountResult chan *futureResult +type FutureGetAccountResult chan *response // Receive waits for the response promised by the future and returns the account // associated with the passed address. func (r FutureGetAccountResult) Receive() (string, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return "", err } - // Ensure the returned data is the expected type. - account, ok := reply.(string) - if !ok { - return "", fmt.Errorf("unexpected response type for "+ - "getaccount: %T\n", reply) + // Unmarshal result as a string. + var account string + err = json.Unmarshal(res, &account) + if err != nil { + return "", err } return account, nil @@ -1034,7 +1025,7 @@ func (c *Client) GetAccount(address btcutil.Address) (string, error) { // FutureSetAccountResult is a future promise to deliver the result of a // SetAccountAsync RPC invocation (or an applicable error). -type FutureSetAccountResult chan *futureResult +type FutureSetAccountResult chan *response // Receive waits for the response promised by the future and returns the result // of setting the account to be associated with the passed address. @@ -1070,21 +1061,21 @@ func (c *Client) SetAccount(address btcutil.Address, account string) error { // FutureGetAddressesByAccountResult is a future promise to deliver the result // of a GetAddressesByAccountAsync RPC invocation (or an applicable error). -type FutureGetAddressesByAccountResult chan *futureResult +type FutureGetAddressesByAccountResult chan *response // Receive waits for the response promised by the future and returns the list of // addresses associated with the passed account. func (r FutureGetAddressesByAccountResult) Receive() ([]btcutil.Address, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - addrStrings, ok := reply.([]string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "getaddressesbyaccount: %T\n", reply) + // Unmashal result as an array of string. + var addrStrings []string + err = json.Unmarshal(res, &addrStrings) + if err != nil { + return nil, err } addrs := make([]btcutil.Address, 0, len(addrStrings)) @@ -1122,24 +1113,24 @@ func (c *Client) GetAddressesByAccount(account string) ([]btcutil.Address, error // FutureValidateAddressResult is a future promise to deliver the result of a // ValidateAddressAsync RPC invocation (or an applicable error). -type FutureValidateAddressResult chan *futureResult +type FutureValidateAddressResult chan *response // Receive waits for the response promised by the future and returns information // about the given bitcoin address. func (r FutureValidateAddressResult) Receive() (*btcjson.ValidateAddressResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - result, ok := reply.(*btcjson.ValidateAddressResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "validateaddress: %T\n", reply) + // Unmarshal result as a validateaddress result object. + var addrResult btcjson.ValidateAddressResult + err = json.Unmarshal(res, &addrResult) + if err != nil { + return nil, err } - return result, nil + return &addrResult, nil } // ValidateAddressAsync returns an instance of a type that can be used to get @@ -1165,7 +1156,7 @@ func (c *Client) ValidateAddress(address btcutil.Address) (*btcjson.ValidateAddr // FutureKeyPoolRefillResult is a future promise to deliver the result of a // KeyPoolRefillAsync RPC invocation (or an applicable error). -type FutureKeyPoolRefillResult chan *futureResult +type FutureKeyPoolRefillResult chan *response // Receive waits for the response promised by the future and returns the result // of refilling the key pool. @@ -1228,21 +1219,21 @@ func (c *Client) KeyPoolRefillSize(newSize uint) error { // FutureListAccountsResult is a future promise to deliver the result of a // ListAccountsAsync or ListAccountsMinConfAsync RPC invocation (or an // applicable error). -type FutureListAccountsResult chan *futureResult +type FutureListAccountsResult chan *response // Receive waits for the response promised by the future and returns returns a // map of account names and their associated balances. func (r FutureListAccountsResult) Receive() (map[string]btcutil.Amount, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - accounts, ok := reply.(map[string]float64) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "listaccounts: %T\n", reply) + // Unmarshal result as a json object. + var accounts map[string]float64 + err = json.Unmarshal(res, &accounts) + if err != nil { + return nil, err } accountsMap := make(map[string]btcutil.Amount) @@ -1307,21 +1298,21 @@ func (c *Client) ListAccountsMinConf(minConfirms int) (map[string]btcutil.Amount // FutureGetBalanceResult is a future promise to deliver the result of a // GetBalanceAsync or GetBalanceMinConfAsync RPC invocation (or an applicable // error). -type FutureGetBalanceResult chan *futureResult +type FutureGetBalanceResult chan *response // Receive waits for the response promised by the future and returns the // available balance from the server for the specified account. func (r FutureGetBalanceResult) Receive() (btcutil.Amount, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return 0, err } - // Ensure the returned data is the expected type. - balance, ok := reply.(float64) - if !ok { - return 0, fmt.Errorf("unexpected response type for "+ - "getbalance: %T\n", reply) + // Unmarshal result as a floating point number. + var balance float64 + err = json.Unmarshal(res, &balance) + if err != nil { + return 0, err } satoshi, err := btcjson.JSONToAmount(balance) @@ -1391,21 +1382,21 @@ func (c *Client) GetBalanceMinConf(account string, minConfirms int) (btcutil.Amo // FutureGetReceivedByAccountResult is a future promise to deliver the result of // a GetReceivedByAccountAsync or GetReceivedByAccountMinConfAsync RPC // invocation (or an applicable error). -type FutureGetReceivedByAccountResult chan *futureResult +type FutureGetReceivedByAccountResult chan *response // Receive waits for the response promised by the future and returns the total // amount received with the specified account. func (r FutureGetReceivedByAccountResult) Receive() (btcutil.Amount, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return 0, err } - // Ensure the returned data is the expected type. - balance, ok := reply.(float64) - if !ok { - return 0, fmt.Errorf("unexpected response type for "+ - "getreceivedbyaccount: %T\n", reply) + // Unmarshal result as a floating point number. + var balance float64 + err = json.Unmarshal(res, &balance) + if err != nil { + return 0, err } satoshi, err := btcjson.JSONToAmount(balance) @@ -1466,21 +1457,21 @@ func (c *Client) GetReceivedByAccountMinConf(account string, minConfirms int) (b // FutureGetUnconfirmedBalanceResult is a future promise to deliver the result // of a GetUnconfirmedBalanceAsync RPC invocation (or an applicable error). -type FutureGetUnconfirmedBalanceResult chan *futureResult +type FutureGetUnconfirmedBalanceResult chan *response // Receive waits for the response promised by the future and returns returns the // unconfirmed balance from the server for the specified account. func (r FutureGetUnconfirmedBalanceResult) Receive() (btcutil.Amount, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return 0, err } - // Ensure the returned data is the expected type. - balance, ok := reply.(float64) - if !ok { - return 0, fmt.Errorf("unexpected response type for "+ - "getunconfirmedbalance: %T\n", reply) + // Unmarshal result as a floating point number. + var balance float64 + err = json.Unmarshal(res, &balance) + if err != nil { + return 0, err } satoshi, err := btcjson.JSONToAmount(balance) @@ -1515,21 +1506,21 @@ func (c *Client) GetUnconfirmedBalance(account string) (btcutil.Amount, error) { // FutureGetReceivedByAddressResult is a future promise to deliver the result of // a GetReceivedByAddressAsync or GetReceivedByAddressMinConfAsync RPC // invocation (or an applicable error). -type FutureGetReceivedByAddressResult chan *futureResult +type FutureGetReceivedByAddressResult chan *response // Receive waits for the response promised by the future and returns the total // amount received by the specified address. func (r FutureGetReceivedByAddressResult) Receive() (btcutil.Amount, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return 0, err } - // Ensure the returned data is the expected type. - balance, ok := reply.(float64) - if !ok { - return 0, fmt.Errorf("unexpected response type for "+ - "getreceivedbyaddress: %T\n", reply) + // Unmarshal result as a floating point number. + var balance float64 + err = json.Unmarshal(res, &balance) + if err != nil { + return 0, err } satoshi, err := btcjson.JSONToAmount(balance) @@ -1594,29 +1585,24 @@ func (c *Client) GetReceivedByAddressMinConf(address btcutil.Address, minConfirm // of a ListReceivedByAccountAsync, ListReceivedByAccountMinConfAsync, or // ListReceivedByAccountIncludeEmptyAsync RPC invocation (or an applicable // error). -type FutureListReceivedByAccountResult chan *futureResult +type FutureListReceivedByAccountResult chan *response // Receive waits for the response promised by the future and returns a list of // balances by account. func (r FutureListReceivedByAccountResult) Receive() ([]btcjson.ListReceivedByAccountResult, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // No results. - if reply == nil { - return nil, nil + // Unmarshal as an array of listreceivedbyaccount result objects. + var received []btcjson.ListReceivedByAccountResult + err = json.Unmarshal(res, &received) + if err != nil { + return nil, err } - // Ensure the returned data is the expected type. - result, ok := reply.([]btcjson.ListReceivedByAccountResult) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "listreceivedbyaccount: %T\n", reply) - } - - return result, nil + return received, nil } // ListReceivedByAccountAsync returns an instance of a type that can be used to @@ -1702,7 +1688,7 @@ func (c *Client) ListReceivedByAccountIncludeEmpty(minConfirms int, includeEmpty // FutureWalletLockResult is a future promise to deliver the result of a // WalletLockAsync RPC invocation (or an applicable error). -type FutureWalletLockResult chan *futureResult +type FutureWalletLockResult chan *response // Receive waits for the response promised by the future and returns the result // of locking the wallet. @@ -1759,7 +1745,7 @@ func (c *Client) WalletPassphrase(passphrase string, timeoutSecs int64) error { // FutureWalletPassphraseChangeResult is a future promise to deliver the result // of a WalletPassphraseChangeAsync RPC invocation (or an applicable error). -type FutureWalletPassphraseChangeResult chan *futureResult +type FutureWalletPassphraseChangeResult chan *response // Receive waits for the response promised by the future and returns the result // of changing the wallet passphrase. @@ -1799,21 +1785,21 @@ func (c *Client) WalletPassphraseChange(old, new string) error { // FutureSignMessageResult is a future promise to deliver the result of a // SignMessageAsync RPC invocation (or an applicable error). -type FutureSignMessageResult chan *futureResult +type FutureSignMessageResult chan *response // Receive waits for the response promised by the future and returns the message // signed with the private key of the specified address. func (r FutureSignMessageResult) Receive() (string, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return "", err } - // Ensure the returned data is the expected type. - b64, ok := reply.(string) - if !ok { - return "", fmt.Errorf("unexpected response type for "+ - "signmessage: %T\n", reply) + // Unmarshal result as a string. + var b64 string + err = json.Unmarshal(res, &b64) + if err != nil { + return "", err } return b64, nil @@ -1845,21 +1831,21 @@ func (c *Client) SignMessage(address btcutil.Address, message string) (string, e // FutureVerifyMessageResult is a future promise to deliver the result of a // VerifyMessageAsync RPC invocation (or an applicable error). -type FutureVerifyMessageResult chan *futureResult +type FutureVerifyMessageResult chan *response // Receive waits for the response promised by the future and returns whether or // not the message was successfully verified. func (r FutureVerifyMessageResult) Receive() (bool, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return false, err } - // Ensure the returned data is the expected type. - verified, ok := reply.(bool) - if !ok { - return false, fmt.Errorf("unexpected response type for "+ - "verifymessage: %T\n", reply) + // Unmarshal result as a boolean. + var verified bool + err = json.Unmarshal(res, &verified) + if err != nil { + return false, err } return verified, nil @@ -1895,22 +1881,22 @@ func (c *Client) VerifyMessage(address btcutil.Address, signature, message strin // FutureDumpPrivKeyResult is a future promise to deliver the result of a // DumpPrivKeyAsync RPC invocation (or an applicable error). -type FutureDumpPrivKeyResult chan *futureResult +type FutureDumpPrivKeyResult chan *response // Receive waits for the response promised by the future and returns the private // key corresponding to the passed address encoded in the wallet import format // (WIF) func (r FutureDumpPrivKeyResult) Receive() (*btcutil.WIF, error) { - reply, err := receiveFuture(r) + res, err := receiveFuture(r) if err != nil { return nil, err } - // Ensure the returned data is the expected type. - privKeyWIF, ok := reply.(string) - if !ok { - return nil, fmt.Errorf("unexpected response type for "+ - "dumpprivkey: %T\n", reply) + // Unmarshal result as a string. + var privKeyWIF string + err = json.Unmarshal(res, &privKeyWIF) + if err != nil { + return nil, err } return btcutil.DecodeWIF(privKeyWIF) @@ -1943,7 +1929,7 @@ func (c *Client) DumpPrivKey(address btcutil.Address) (*btcutil.WIF, error) { // FutureImportPrivKeyResult is a future promise to deliver the result of an // ImportPrivKeyAsync RPC invocation (or an applicable error). -type FutureImportPrivKeyResult chan *futureResult +type FutureImportPrivKeyResult chan *response // Receive waits for the response promised by the future and returns the result // of importing the passed private key which must be the wallet import format