lbcwallet/rpc.go
Josh Rickmar 15ffc674a9 Rework the btcd RPC connection.
This change greatly cleans up the RPC connection between btcwallet and
btcd.  Proper (JSON-RPC spec-following) notifications are now expected
rather than Responses with a non-empty IDs.

A new RPCConn interface type has also been introduced with a
BtcdRPCConn concrete type for btcd RPC connections.  Non-btcd-specific
code handles the RPCConn, while the btcd details have been abstracted
away to a handful of functions.  This will make it easier to write
tests by creating a new fake RPC connection with hardcoded expected
replies.
2014-01-09 14:07:17 -05:00

154 lines
5.6 KiB
Go

/*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// This file implements the RPC connection interface and functions to
// communicate with a bitcoin RPC server.
package main
import (
"github.com/conformal/btcjson"
"github.com/conformal/btcwire"
"github.com/conformal/btcws"
)
// RPCRequest is a type responsible for handling RPC requests and providing
// a method to access the response.
type RPCRequest struct {
request btcjson.Cmd
result interface{}
response chan *RPCResponse
}
// NewRPCRequest creates a new RPCRequest from a btcjson.Cmd. request may be
// nil to create a new var for the result (with types determined by the
// unmarshaling rules described in the json package), or set to a var with
// an expected type (i.e. *btcjson.BlockResult) to directly unmarshal the
// response's result into a convenient type.
func NewRPCRequest(request btcjson.Cmd, result interface{}) *RPCRequest {
return &RPCRequest{
request: request,
result: result,
response: make(chan *RPCResponse),
}
}
// RPCResponse holds a response's result and error returned from sending a
// RPCRequest.
type RPCResponse struct {
// Result will be set to a concrete type (i.e. *btcjson.BlockResult)
// and may be type asserted to that type if a non-nil result was used
// to create the originating RPCRequest. Otherwise, Result will be
// set to new memory allocated by json.Unmarshal, and the type rules
// for unmarshaling described in the json package should be followed
// when type asserting Result.
Result interface{}
// Err points to an unmarshaled error, or nil if result is valid.
Err *btcjson.Error
}
// RPCConn is an interface representing a client connection to a bitcoin RPC
// server.
type RPCConn interface {
// SendRequest sends a bitcoin RPC request, returning a channel to
// read the reply. A channel is used so both synchronous and
// asynchronous RPC can be supported.
SendRequest(request *RPCRequest) chan *RPCResponse
}
// GetBestBlockResult holds the result of a getbestblock response.
//
// TODO(jrick): shove this in btcws.
type GetBestBlockResult struct {
Hash string `json:"hash"`
Height int32 `json:"height"`
}
// GetBestBlock gets both the block height and hash of the best block
// in the main chain.
func GetBestBlock(rpc RPCConn) (*GetBestBlockResult, *btcjson.Error) {
cmd := btcws.NewGetBestBlockCmd(<-NewJSONID)
request := NewRPCRequest(cmd, new(GetBestBlockResult))
response := <-rpc.SendRequest(request)
if response.Err != nil {
return nil, response.Err
}
return response.Result.(*GetBestBlockResult), nil
}
// GetBlock requests details about a block with the given hash.
func GetBlock(rpc RPCConn, blockHash string) (*btcjson.BlockResult, *btcjson.Error) {
// NewGetBlockCmd cannot fail with no optargs, so omit the check.
cmd, _ := btcjson.NewGetBlockCmd(<-NewJSONID, blockHash)
request := NewRPCRequest(cmd, new(btcjson.BlockResult))
response := <-rpc.SendRequest(request)
if response.Err != nil {
return nil, response.Err
}
return response.Result.(*btcjson.BlockResult), nil
}
// GetCurrentNet requests the network a bitcoin RPC server is running on.
func GetCurrentNet(rpc RPCConn) (btcwire.BitcoinNet, *btcjson.Error) {
cmd := btcws.NewGetCurrentNetCmd(<-NewJSONID)
request := NewRPCRequest(cmd, nil)
response := <-rpc.SendRequest(request)
if response.Err != nil {
return 0, response.Err
}
return btcwire.BitcoinNet(uint32(response.Result.(float64))), nil
}
// NotifyNewTXs requests notifications for new transactions that spend
// to any of the addresses in addrs.
func NotifyNewTXs(rpc RPCConn, addrs []string) *btcjson.Error {
cmd := btcws.NewNotifyNewTXsCmd(<-NewJSONID, addrs)
request := NewRPCRequest(cmd, nil)
response := <-rpc.SendRequest(request)
return response.Err
}
// NotifySpent requests notifications for when a transaction is processed which
// spends op.
func NotifySpent(rpc RPCConn, op *btcwire.OutPoint) *btcjson.Error {
cmd := btcws.NewNotifySpentCmd(<-NewJSONID, op)
request := NewRPCRequest(cmd, nil)
response := <-rpc.SendRequest(request)
return response.Err
}
// Rescan requests a blockchain rescan for transactions to any number of
// addresses and notifications to inform wallet about such transactions.
func Rescan(rpc RPCConn, beginBlock int32, addrs map[string]struct{}) *btcjson.Error {
// NewRescanCmd cannot fail with no optargs, so omit the check.
cmd, _ := btcws.NewRescanCmd(<-NewJSONID, beginBlock, addrs)
request := NewRPCRequest(cmd, nil)
response := <-rpc.SendRequest(request)
return response.Err
}
// SendRawTransaction sends a hex-encoded transaction for relay.
func SendRawTransaction(rpc RPCConn, hextx string) (txid string, error *btcjson.Error) {
// NewSendRawTransactionCmd cannot fail, so omit the check.
cmd, _ := btcjson.NewSendRawTransactionCmd(<-NewJSONID, hextx)
request := NewRPCRequest(cmd, new(string))
response := <-rpc.SendRequest(request)
if response.Err != nil {
return "", response.Err
}
return *response.Result.(*string), nil
}