Initial commit.

This commit is contained in:
Dave Collins 2014-05-07 11:14:39 -05:00
commit e3f130ade5
13 changed files with 5609 additions and 0 deletions

28
.gitignore vendored Normal file
View file

@ -0,0 +1,28 @@
# Temp files
*~
# Log files
*.log
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

13
LICENSE Normal file
View file

@ -0,0 +1,13 @@
Copyright (c) 2014 Conformal Systems LLC.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

11
README.md Normal file
View file

@ -0,0 +1,11 @@
btcrpcclient
============
This package is currently under development.
You really probably don't want to use this yet!
## License
Package btcrpcclient is licensed under the [copyfree](http://copyfree.org) ISC
License.

494
chain.go Normal file
View file

@ -0,0 +1,494 @@
// 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 (
"bytes"
"encoding/hex"
"fmt"
"github.com/conformal/btcjson"
"github.com/conformal/btcutil"
"github.com/conformal/btcwire"
)
// FutureGetBestBlockHashResult is a future promise to deliver the result of a
// GetBestBlockAsync RPC invocation (or an applicable error).
type FutureGetBestBlockHashResult chan *futureResult
// 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)
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)
}
return btcwire.NewShaHashFromStr(txHashStr)
}
// GetBestBlockHashAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See GetBestBlockHash for the blocking version and more details.
func (c *Client) GetBestBlockHashAsync() FutureGetBestBlockHashResult {
id := c.NextID()
cmd, err := btcjson.NewGetBestBlockHashCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetBestBlockHash returns the hash of the best block in the longest block
// chain.
func (c *Client) GetBestBlockHash() (*btcwire.ShaHash, error) {
return c.GetBestBlockHashAsync().Receive()
}
// FutureGetBlockResult is a future promise to deliver the result of a
// GetBlockAsync RPC invocation (or an applicable error).
type FutureGetBlockResult chan *futureResult
// 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)
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)
}
// Decode the serialized block hex to raw bytes.
serializedBlock, err := hex.DecodeString(blockHex)
if err != nil {
return nil, err
}
// Deserialize the block and return it.
var msgBlock btcwire.MsgBlock
msgBlock.Deserialize(bytes.NewBuffer(serializedBlock))
if err != nil {
return nil, err
}
return btcutil.NewBlock(&msgBlock), nil
}
// GetBlockAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See GetBlock for the blocking version and more details.
func (c *Client) GetBlockAsync(blockHash *btcwire.ShaHash) FutureGetBlockResult {
id := c.NextID()
cmd, err := btcjson.NewGetBlockCmd(id, blockHash.String(), false)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetBlock returns a raw block from the server given its hash.
//
// See GetBlockVerbose to retrieve a data structure with information about the
// block instead.
func (c *Client) GetBlock(blockHash *btcwire.ShaHash) (*btcutil.Block, error) {
return c.GetBlockAsync(blockHash).Receive()
}
// FutureGetBlockVerboseResult is a future promise to deliver the result of a
// GetBlockVerboseAsync RPC invocation (or an applicable error).
type FutureGetBlockVerboseResult chan *futureResult
// 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)
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)
}
return &blockResult, nil
}
// GetBlockVerboseAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See GetBlockVerbose for the blocking version and more details.
func (c *Client) GetBlockVerboseAsync(blockHash *btcwire.ShaHash, verboseTx bool) FutureGetBlockVerboseResult {
id := c.NextID()
cmd, err := btcjson.NewGetBlockCmd(id, blockHash.String(), true,
verboseTx)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetBlockVerbose returns a data structure from the server with information
// about a block given its hash.
//
// See GetBlock to retrieve a raw block instead.
func (c *Client) GetBlockVerbose(blockHash *btcwire.ShaHash, verboseTx bool) (*btcjson.BlockResult, error) {
return c.GetBlockVerboseAsync(blockHash, verboseTx).Receive()
}
// FutureGetBlockCountResult is a future promise to deliver the result of a
// GetBlockCountAsync RPC invocation (or an applicable error).
type FutureGetBlockCountResult chan *futureResult
// 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)
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)
}
return int64(count), nil
}
// GetBlockCountAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See GetBlockCount for the blocking version and more details.
func (c *Client) GetBlockCountAsync() FutureGetBlockCountResult {
id := c.NextID()
cmd, err := btcjson.NewGetBlockCountCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetBlockCount returns the number of blocks in the longest block chain.
func (c *Client) GetBlockCount() (int64, error) {
return c.GetBlockCountAsync().Receive()
}
// FutureGetDifficultyResult is a future promise to deliver the result of a
// GetDifficultyAsync RPC invocation (or an applicable error).
type FutureGetDifficultyResult chan *futureResult
// 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)
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)
}
return difficulty, nil
}
// GetDifficultyAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See GetDifficulty for the blocking version and more details.
func (c *Client) GetDifficultyAsync() FutureGetDifficultyResult {
id := c.NextID()
cmd, err := btcjson.NewGetDifficultyCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetDifficulty returns the proof-of-work difficulty as a multiple of the
// minimum difficulty.
func (c *Client) GetDifficulty() (float64, error) {
return c.GetDifficultyAsync().Receive()
}
// FutureGetBlockHashResult is a future promise to deliver the result of a
// GetBlockHashAsync RPC invocation (or an applicable error).
type FutureGetBlockHashResult chan *futureResult
// 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)
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)
}
return btcwire.NewShaHashFromStr(txHashStr)
}
// GetBlockHashAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See GetBlockHash for the blocking version and more details.
func (c *Client) GetBlockHashAsync(blockHeight int64) FutureGetBlockHashResult {
id := c.NextID()
cmd, err := btcjson.NewGetBlockHashCmd(id, blockHeight)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetBlockHash returns the hash of the block in the best block chain at the
// given height.
func (c *Client) GetBlockHash(blockHeight int64) (*btcwire.ShaHash, error) {
return c.GetBlockHashAsync(blockHeight).Receive()
}
// FutureGetRawMempoolResult is a future promise to deliver the result of a
// GetRawMempoolAsync RPC invocation (or an applicable error).
type FutureGetRawMempoolResult chan *futureResult
// 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)
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)
}
txHashes := make([]*btcwire.ShaHash, 0, len(txHashStrs))
for _, hashStr := range txHashStrs {
txHash, err := btcwire.NewShaHashFromStr(hashStr)
if err != nil {
return nil, err
}
txHashes = append(txHashes, txHash)
}
return txHashes, nil
}
// GetRawMempoolAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See GetRawMempool for the blocking version and more details.
func (c *Client) GetRawMempoolAsync() FutureGetRawMempoolResult {
id := c.NextID()
cmd, err := btcjson.NewGetRawMempoolCmd(id, false)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetRawMempool returns the hashes of all transactions in the memory pool.
//
// See GetRawMempoolVerbose to retrieve data structures with information about
// the transactions instead.
func (c *Client) GetRawMempool() ([]*btcwire.ShaHash, error) {
return c.GetRawMempoolAsync().Receive()
}
// FutureGetRawMempoolVerboseResult is a future promise to deliver the result of
// a GetRawMempoolVerboseAsync RPC invocation (or an applicable error).
type FutureGetRawMempoolVerboseResult chan *futureResult
// 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)
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)
}
return mempoolItems, nil
}
// GetRawMempoolVerboseAsync returns an instance of a type that can be used to
// get the result of the RPC at some future time by invoking the Receive
// function on the returned instance.
//
// See GetRawMempoolVerbose for the blocking version and more details.
func (c *Client) GetRawMempoolVerboseAsync() FutureGetRawMempoolVerboseResult {
id := c.NextID()
cmd, err := btcjson.NewGetRawMempoolCmd(id, true)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetRawMempoolVerbose returns a map of transaction hashes to an associated
// data structure with information about the transaction for all transactions in
// the memory pool.
//
// See GetRawMempool to retrieve only the transaction hashes instead.
func (c *Client) GetRawMempoolVerbose() (map[string]btcjson.GetRawMempoolResult, error) {
return c.GetRawMempoolVerboseAsync().Receive()
}
// FutureVerifyChainResult is a future promise to deliver the result of a
// VerifyChainAsync, VerifyChainLevelAsyncRPC, or VerifyChainBlocksAsync
// invocation (or an applicable error).
type FutureVerifyChainResult chan *futureResult
// 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)
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)
}
return verified, nil
}
// VerifyChainAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See VerifyChain for the blocking version and more details.
func (c *Client) VerifyChainAsync() FutureVerifyChainResult {
id := c.NextID()
cmd, err := btcjson.NewVerifyChainCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// VerifyChain requests the server to verify the block chain database using
// the default check level and number of blocks to verify.
//
// See VerifyChainLevel and VerifyChainBlocks to override the defaults.
func (c *Client) VerifyChain() (bool, error) {
return c.VerifyChainAsync().Receive()
}
// VerifyChainLevelAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See VerifyChainLevel for the blocking version and more details.
func (c *Client) VerifyChainLevelAsync(checkLevel int32) FutureVerifyChainResult {
id := c.NextID()
cmd, err := btcjson.NewVerifyChainCmd(id, checkLevel)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// VerifyChainLevel requests the server to verify the block chain database using
// the passed check level and default number of blocks to verify.
//
// The check level controls how thorough the verification is with higher numbers
// increasing the amount of checks done as consequently how long the
// verification takes.
//
// See VerifyChain to use the default check level and VerifyChainBlocks to
// override the number of blocks to verify.
func (c *Client) VerifyChainLevel(checkLevel int32) (bool, error) {
return c.VerifyChainLevelAsync(checkLevel).Receive()
}
// VerifyChainBlocksAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See VerifyChainBlocks for the blocking version and more details.
func (c *Client) VerifyChainBlocksAsync(checkLevel, numBlocks int32) FutureVerifyChainResult {
id := c.NextID()
cmd, err := btcjson.NewVerifyChainCmd(id, checkLevel, numBlocks)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// VerifyChainBlocks requests the server to verify the block chain database
// using the passed check level and number of blocks to verify.
//
// The check level controls how thorough the verification is with higher numbers
// increasing the amount of checks done as consequently how long the
// verification takes.
//
// The number of blocks refers to the number of blocks from the end of the
// current longest chain.
//
// See VerifyChain and VerifyChainLevel to use defaults.
func (c *Client) VerifyChainBlocks(checkLevel, numBlocks int32) (bool, error) {
return c.VerifyChainBlocksAsync(checkLevel, numBlocks).Receive()
}

8
doc.go Normal file
View file

@ -0,0 +1,8 @@
// 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 implements a websocket-enabled Bitcoin JSON-RPC client.
//
// TODO(davec): Doco
package btcrpcclient

291
extensions.go Normal file
View file

@ -0,0 +1,291 @@
// 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/base64"
"fmt"
"github.com/conformal/btcjson"
"github.com/conformal/btcutil"
"github.com/conformal/btcwire"
"github.com/conformal/btcws"
)
// FutureDebugLevelResult is a future promise to deliver the result of a
// DebugLevelAsync RPC invocation (or an applicable error).
type FutureDebugLevelResult chan *futureResult
// 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)
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)
}
return result, nil
}
// DebugLevelAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See DebugLevel for the blocking version and more details.
//
// NOTE: This is a btcd extension.
func (c *Client) DebugLevelAsync(levelSpec string) FutureDebugLevelResult {
id := c.NextID()
cmd, err := btcjson.NewDebugLevelCmd(id, levelSpec)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// DebugLevel dynamically sets the debug logging level to the passed level
// specification.
//
// The levelspec can either a debug level or of the form:
// <subsystem>=<level>,<subsystem2>=<level2>,...
//
// Additionally, the special keyword 'show' can be used to get a list of the
// available subsystems.
//
// NOTE: This is a btcd extension.
func (c *Client) DebugLevel(levelSpec string) (string, error) {
return c.DebugLevelAsync(levelSpec).Receive()
}
// FutureListAddressTransactionsResult is a future promise to deliver the result
// of a ListAddressTransactionsAsync RPC invocation (or an applicable error).
type FutureListAddressTransactionsResult chan *futureResult
// 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)
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 "+
"listaddresstransactions: %T\n", reply)
}
return transactions, nil
}
// ListAddressTransactionsAsync returns an instance of a type that can be used
// to get the result of the RPC at some future time by invoking the Receive
// function on the returned instance.
//
// See ListAddressTransactions for the blocking version and more details.
//
// NOTE: This is a btcd extension.
func (c *Client) ListAddressTransactionsAsync(addresses []btcutil.Address, account string) FutureListAddressTransactionsResult {
// Convert addresses to strings.
addrs := make([]string, 0, len(addresses))
for _, addr := range addresses {
addrs = append(addrs, addr.EncodeAddress())
}
id := c.NextID()
cmd, err := btcws.NewListAddressTransactionsCmd(id, addrs, account)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// ListAddressTransactions returns information about all transactions associated
// with the provided addresses.
//
// NOTE: This is a btcwallet extension.
func (c *Client) ListAddressTransactions(addresses []btcutil.Address, account string) ([]btcjson.ListTransactionsResult, error) {
return c.ListAddressTransactionsAsync(addresses, account).Receive()
}
// FutureGetBestBlockResult is a future promise to deliver the result of a
// GetBestBlockAsync RPC invocation (or an applicable error).
type FutureGetBestBlockResult chan *futureResult
// 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)
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 "+
"listaddresstransactions: %T\n", reply)
}
// Convert hash string.
hash, err := btcwire.NewShaHashFromStr(result.Hash)
if err != nil {
return nil, 0, err
}
return hash, result.Height, nil
}
// GetBestBlockAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See GetBestBlock for the blocking version and more details.
//
// NOTE: This is a btcd extension.
func (c *Client) GetBestBlockAsync() FutureGetBestBlockResult {
id := c.NextID()
cmd := btcws.NewGetBestBlockCmd(id)
return c.sendCmd(cmd)
}
// GetBestBlock returns the hash and height of the block in the longest (best)
// chain.
//
// NOTE: This is a btcd extension.
func (c *Client) GetBestBlock() (*btcwire.ShaHash, int32, error) {
return c.GetBestBlockAsync().Receive()
}
// FutureGetCurrentNetResult is a future promise to deliver the result of a
// GetCurrentNetAsync RPC invocation (or an applicable error).
type FutureGetCurrentNetResult chan *futureResult
// 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)
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)
}
return btcwire.BitcoinNet(fnet), nil
}
// GetCurrentNetAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See GetCurrentNet for the blocking version and more details.
//
// NOTE: This is a btcd extension.
func (c *Client) GetCurrentNetAsync() FutureGetCurrentNetResult {
id := c.NextID()
cmd := btcws.NewGetCurrentNetCmd(id)
return c.sendCmd(cmd)
}
// GetCurrentNet returns the network the server is running on.
//
// NOTE: This is a btcd extension.
func (c *Client) GetCurrentNet() (btcwire.BitcoinNet, error) {
return c.GetCurrentNetAsync().Receive()
}
// FutureExportWatchingWalletResult is a future promise to deliver the result of
// an ExportWatchingWalletAsync RPC invocation (or an applicable error).
type FutureExportWatchingWalletResult chan *futureResult
// 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)
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)
}
base64Wallet, ok := result["wallet"].(string)
if !ok {
return nil, nil, fmt.Errorf("unexpected response type for "+
"exportwatchingwallet 'wallet' field: %T\n",
result["wallet"])
}
base64TxStore, ok := result["tx"].(string)
if !ok {
return nil, nil, fmt.Errorf("unexpected response type for "+
"exportwatchingwallet 'tx' field: %T\n",
result["tx"])
}
walletBytes, err := base64.StdEncoding.DecodeString(base64Wallet)
if err != nil {
return nil, nil, err
}
txStoreBytes, err := base64.StdEncoding.DecodeString(base64TxStore)
if err != nil {
return nil, nil, err
}
return walletBytes, txStoreBytes, nil
}
// ExportWatchingWalletAsync returns an instance of a type that can be used to
// get the result of the RPC at some future time by invoking the Receive
// function on the returned instance.
//
// See ExportWatchingWallet for the blocking version and more details.
//
// NOTE: This is a btcwallet extension.
func (c *Client) ExportWatchingWalletAsync(account string) FutureExportWatchingWalletResult {
id := c.NextID()
cmd, err := btcws.NewExportWatchingWalletCmd(id, account, true)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// ExportWatchingWallet returns the raw bytes for a watching-only version of
// wallet.bin and tx.bin, respectively, for the specified account that can be
// used by btcwallet to enable a wallet which does not have the private keys
// necessary to spend funds.
//
// NOTE: This is a btcwallet extension.
func (c *Client) ExportWatchingWallet(account string) ([]byte, []byte, error) {
return c.ExportWatchingWalletAsync(account).Receive()
}

937
infrastructure.go Normal file
View file

@ -0,0 +1,937 @@
// 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 (
"bytes"
"code.google.com/p/go.net/websocket"
"container/list"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/conformal/btcjson"
"github.com/conformal/go-socks"
"net"
"net/http"
"net/url"
"sync"
"sync/atomic"
"time"
)
var (
// ErrInvalidAuth is an error to describe the condition where the client
// is either unable to authenticate or the specified endpoint is
// incorrect.
ErrInvalidAuth = errors.New("authentication failure")
// ErrClientDisconnect is an error to describe the condition where the
// client has been disconnected from the RPC server. When the
// DisableAutoReconnect option is not set, any outstanding futures
// when a client disconnect occurs will return this error as will
// any new requests.
ErrClientDisconnect = errors.New("the client has been disconnected")
// ErrClientShutdown is an error to describe the condition where the
// client is either already shutdown, or in the process of shutting
// down. Any outstanding futures when a client shutdown occurs will
// return this error as will any new requests.
ErrClientShutdown = errors.New("the client has been shutdown")
)
const (
// sendBufferSize is the number of elements the websocket send channel
// can queue before blocking.
sendBufferSize = 50
// sendPostBufferSize is the number of elements the HTTP POST send
// channel can queue before blocking.
sendPostBufferSize = 100
// connectionRetryInterval is the amount of time to wait in between
// retries when automatically reconnecting to an RPC server.
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
}
// 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
}
// Client represents a Bitcoin RPC client which allows easy access to the
// various RPC methods available on a Bitcoin RPC server. Each of the wrapper
// functions handle the details of converting the passed and return types to and
// from the underlying JSON types which are required for the JSON-RPC
// invocations
//
// The client provides each RPC in both synchronous (blocking) and asynchronous
// (non-blocking) forms. The asynchronous forms are based on the concept of
// futures where they return an instance of a type that promises to deliver the
// result of the invocation at some future time. Invoking the Receive method on
// the returned future will block until the result is available if it's not
// already.
type Client struct {
id int64 // atomic, so must stay 64-bit aligned
// config holds the connection configuration assoiated with this client.
config *ConnConfig
// wsConn is the underlying websocket connection when not in HTTP POST
// mode.
wsConn *websocket.Conn
// httpClient is the underlying HTTP client to use when running in HTTP
// POST mode.
httpClient *http.Client
// mtx is a mutex to protect access to connection related fields.
mtx sync.Mutex
// disconnected indicated whether or not the server is disconnected.
disconnected bool
// retryCount holds the number of times the client has tried to
// reconnect to the RPC server.
retryCount int64
// Track command and their response channels by ID.
requestLock sync.Mutex
requestMap map[int64]*list.Element
requestList *list.List
// Notification handlers.
ntfnHandlers *NotificationHandlers
// Networking infrastructure.
sendChan chan []byte
sendPostChan chan *sendPostDetails
disconnect chan struct{}
shutdown chan struct{}
wg sync.WaitGroup
}
// NextID returns the next id to be used when sending a JSON-RPC message. This
// ID allows responses to be associated with particular requests per the
// JSON-RPC specification. Typically the consumer of the client does not need
// to call this function, however, if a custom request is being created and used
// this function should be used to ensure the ID is unique amongst all requests
// being made.
func (c *Client) NextID() int64 {
return atomic.AddInt64(&c.id, 1)
}
// addRequest associates the passed jsonRequest with the passed id. This allows
// the response from the remote server to be unmarshalled to the appropriate
// type and sent to the specified channel when it is received.
//
// This function is safe for concurrent access.
func (c *Client) addRequest(id int64, request *jsonRequest) {
c.requestLock.Lock()
defer c.requestLock.Unlock()
// TODO(davec): Already there?
element := c.requestList.PushBack(request)
c.requestMap[id] = element
}
// removeRequest returns and removes the jsonRequest which contains the response
// channel and original method associated with the passed id or nil if there is
// no association.
//
// This function is safe for concurrent access.
func (c *Client) removeRequest(id int64) *jsonRequest {
c.requestLock.Lock()
defer c.requestLock.Unlock()
element := c.requestMap[id]
if element != nil {
delete(c.requestMap, id)
request := c.requestList.Remove(element).(*jsonRequest)
return request
}
return nil
}
// removeAllRequests removes all the jsonRequests which contain the response
// channels for outstanding requests.
//
// This function is safe for concurrent access.
func (c *Client) removeAllRequests() {
c.requestLock.Lock()
defer c.requestLock.Unlock()
c.requestMap = make(map[int64]*list.Element)
c.requestList.Init()
}
// 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.
func (c *Client) handleMessage(msg string) {
// Attempt to unmarshal the message as a known JSON-RPC command.
if cmd, err := btcjson.ParseMarshaledCmd([]byte(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())
return
}
// Deliver the notification.
log.Tracef("Received notification [%s]", cmd.Method())
c.handleNotification(cmd)
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")
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 := int64(fid)
log.Tracef("Received response for id %d (result %v)", id, r.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}
return
}
request.responseChan <- &futureResult{reply: &reply, err: nil}
}
// wsInHandler handles all incoming messages for the websocket connection
// associated with the client. It must be run as a goroutine.
func (c *Client) wsInHandler() {
out:
for {
// Break out of the loop once the shutdown channel has been
// closed. Use a non-blocking select here so we fall through
// otherwise.
select {
case <-c.shutdown:
break out
default:
}
var msg string
if err := websocket.Message.Receive(c.wsConn, &msg); err != nil {
// Log the error if it's not due to disconnecting.
if _, ok := err.(*net.OpError); !ok {
log.Errorf("Websocket receive error from "+
"%s: %v", c.config.Host, err)
}
break out
}
c.handleMessage(msg)
}
// Ensure the connection is closed.
c.Disconnect()
c.wg.Done()
log.Tracef("RPC client input handler done for %s", c.config.Host)
}
// wsOutHandler handles all outgoing messages for the websocket connection. It
// uses a buffered channel to serialize output messages while allowing the
// sender to continue running asynchronously. It must be run as a goroutine.
func (c *Client) wsOutHandler() {
out:
for {
// Send any messages ready for send until the client is
// disconnected closed.
select {
case msg := <-c.sendChan:
err := websocket.Message.Send(c.wsConn, string(msg))
if err != nil {
c.Disconnect()
break out
}
case <-c.disconnect:
break out
}
}
// Drain any channels before exiting so nothing is left waiting around
// to send.
cleanup:
for {
select {
case <-c.sendChan:
default:
break cleanup
}
}
c.wg.Done()
log.Tracef("RPC client output handler done for %s", c.config.Host)
}
// sendMessage sends the passed JSON to the connected server using the
// websocket connection. It is backed by a buffered channel, so it will not
// block until the send channel is full.
func (c *Client) sendMessage(marshalledJSON []byte) {
// Don't send the message if disconnected.
if c.Disconnected() {
return
}
c.sendChan <- marshalledJSON
}
// resendCmds resends any commands that had not completed when the client
// disconnected. It is intended to be called once the client has reconnected.
func (c *Client) resendCmds() {
// Since it's possible to block on send and more commands might be
// added by the caller while resending, make a copy of all of the
// commands that need to be resent now and work from the copy. This
// also allows the lock to be released quickly.
c.requestLock.Lock()
resendCmds := make([]*jsonRequest, 0, c.requestList.Len())
for e := c.requestList.Front(); e != nil; e = e.Next() {
req := e.Value.(*jsonRequest)
resendCmds = append(resendCmds, req)
}
c.requestLock.Unlock()
for _, req := range resendCmds {
// Stop resending commands if the client disconnected again
// since the next reconnect will handle them.
if c.Disconnected() {
return
}
c.marshalAndSend(req.cmd, req.responseChan)
time.Sleep(time.Second * 2)
}
}
// wsReconnectHandler listens for client disconnects and automatically tries
// to reconnect with retry interval that scales based on the number of retries.
// It also resends any commands that had not completed when the client
// disconnected so the disconnect/reconnect process is largely transparent to
// the caller. This function is not run when the DisableAutoReconnect config
// options is set.
//
// This function must be run as a goroutine.
func (c *Client) wsReconnectHandler() {
out:
for {
select {
case <-c.disconnect:
// On disconnect, fallthrough to reestablish the
// connection.
case <-c.shutdown:
break out
}
reconnect:
for {
select {
case <-c.shutdown:
break out
default:
}
wsConn, err := dial(c.config)
if err != nil {
c.retryCount++
log.Infof("Failed to connect to %s: %v",
c.config.Host, err)
// Scale the retry interval by the number of
// retries so there is a backoff up to a max
// of 1 minute.
scaledInterval := connectionRetryInterval.Nanoseconds() * c.retryCount
scaledDuration := time.Duration(scaledInterval)
if scaledDuration > time.Minute {
scaledDuration = time.Minute
}
log.Infof("Retrying connection to %s in "+
"%s", c.config.Host, scaledDuration)
time.Sleep(scaledDuration)
continue reconnect
}
log.Infof("Reestablished connection to RPC server %s",
c.config.Host)
// Reset the connection state and signal the reconnect
// has happened.
c.wsConn = wsConn
c.retryCount = 0
c.disconnect = make(chan struct{})
c.mtx.Lock()
c.disconnected = false
c.mtx.Unlock()
// Start processing input and output for the
// new connection.
c.start()
// Reissue pending commands in another goroutine since
// the send can block.
go c.resendCmds()
// Break out of the reconnect loop back to wait for
// disconnect again.
break reconnect
}
}
c.wg.Done()
log.Tracef("RPC client reconnect handler done for %s", c.config.Host)
}
// handleSendPostMessage handles performing the passed HTTP request, reading the
// result, unmarshalling it, and delivering the unmarhsalled result to the
// provided response channel.
func (c *Client) handleSendPostMessage(details *sendPostDetails) {
// Post the request.
cmd := details.command
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}
return
}
// Read the raw bytes and close the response.
respBytes, err := btcjson.GetRaw(httpResponse.Body)
if err != nil {
details.responseChan <- &futureResult{reply: 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}
}
// sendPostHandler handles all outgoing messages when the client is running
// in HTTP POST mode. It uses a buffered channel to serialize output messages
// while allowing the sender to continue running asynchronously. It must be run
// as a goroutine.
func (c *Client) sendPostHandler() {
out:
for {
// Send any messages ready for send until the shutdown channel
// is closed.
select {
case details := <-c.sendPostChan:
c.handleSendPostMessage(details)
case <-c.shutdown:
break out
}
}
// Drain any wait channels before exiting so nothing is left waiting
// around to send.
cleanup:
for {
select {
case details := <-c.sendPostChan:
details.responseChan <- &futureResult{
reply: nil,
err: ErrClientShutdown,
}
default:
break cleanup
}
}
c.wg.Done()
log.Tracef("RPC client send handler done for %s", c.config.Host)
}
// 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) {
// Don't send the message if shutting down.
select {
case <-c.shutdown:
responseChan <- &futureResult{reply: nil, err: ErrClientShutdown}
default:
}
c.sendPostChan <- &sendPostDetails{
request: req,
command: command,
responseChan: responseChan,
}
}
// Disconnected returns whether or not the server is disconnected.
func (c *Client) Disconnected() bool {
c.mtx.Lock()
defer c.mtx.Unlock()
return c.disconnected
}
// 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}
return responseChan
}
// receiveFuture receives from the passed futureResult channel to extract a
// 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) {
// 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
}
// marshalAndSendPost marshals the passed command to JSON-RPC and sends it to
// the server by issuing an HTTP POST request and returns a response channel
// on which the reply will be delivered. Typically a new connection is opened
// 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) {
marshalledJSON, err := json.Marshal(cmd)
if err != nil {
responseChan <- &futureResult{reply: nil, err: err}
return
}
// Generate a request to the configured RPC server.
protocol := "http"
if !c.config.DisableTLS {
protocol = "https"
}
url := protocol + "://" + c.config.Host
req, err := http.NewRequest("POST", url, bytes.NewReader(marshalledJSON))
if err != nil {
responseChan <- &futureResult{reply: nil, err: err}
return
}
req.Close = true
req.Header.Set("Content-Type", "application/json")
// Configure basic access authorization.
req.SetBasicAuth(c.config.User, c.config.Pass)
log.Tracef("Sending command [%s] with id %d", cmd.Method(), cmd.Id())
c.sendPostRequest(req, cmd, responseChan)
}
// 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)
if err != nil {
responseChan <- &futureResult{reply: nil, err: err}
return
}
log.Tracef("Sending command [%s] with id %d", cmd.Method(), cmd.Id())
c.sendMessage(marshalledJSON)
}
// sendCmd sends the passed command to the associated server and returns a
// 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 {
// 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)
if c.config.HttpPostMode {
c.marshalAndSendPost(cmd, responseChan)
return responseChan
}
c.addRequest(cmd.Id().(int64), &jsonRequest{
cmd: cmd,
responseChan: responseChan,
})
c.marshalAndSend(cmd, responseChan)
return responseChan
}
// sendCmdAndWait sends the passed command to the associated server, waits
// for the reply, and returns the result from it. It will return the error
// field in the reply if there is one.
func (c *Client) sendCmdAndWait(cmd btcjson.Cmd) (interface{}, error) {
// Marshal the command to JSON-RPC, send it to the connected server, and
// wait for a response on the returned channel.
return receiveFuture(c.sendCmd(cmd))
}
// Disconnect disconnects the current websocket associated with the client. The
// connection will automatically be re-established
//
// This function has no effect when the client is running in HTTP POST mode.
func (c *Client) Disconnect() {
if c.config.HttpPostMode {
return
}
c.mtx.Lock()
defer c.mtx.Unlock()
// Nothing to do if already disconnected.
if c.disconnected {
return
}
log.Tracef("Disconnecting RPC client %s", c.config.Host)
close(c.disconnect)
c.wsConn.Close()
c.disconnected = true
// When operating without auto reconnect, send errors to any pending
// requests and shutdown the client.
if c.config.DisableAutoReconnect {
c.requestLock.Lock()
for e := c.requestList.Front(); e != nil; e = e.Next() {
req := e.Value.(*jsonRequest)
req.responseChan <- &futureResult{
reply: nil,
err: ErrClientDisconnect,
}
}
c.requestLock.Unlock()
c.removeAllRequests()
c.Shutdown()
}
}
// Shutdown shuts down the client by disconnecting any connections associated
// with the client and, when automatic reconnect is enabled, preventing future
// attempts to reconnect. It also stops all goroutines.
func (c *Client) Shutdown() {
// Ignore the shutdown request if the client is already in the process
// of shutting down or already shutdown.
select {
case <-c.shutdown:
return
default:
}
log.Tracef("Shutting down RPC client %s", c.config.Host)
close(c.shutdown)
// Send the ErrClientShutdown error to any pending requests.
c.requestLock.Lock()
for e := c.requestList.Front(); e != nil; e = e.Next() {
req := e.Value.(*jsonRequest)
req.responseChan <- &futureResult{
reply: nil,
err: ErrClientShutdown,
}
}
c.requestLock.Unlock()
c.removeAllRequests()
}
// Start begins processing input and output messages.
func (c *Client) start() {
log.Tracef("Starting RPC client %s", c.config.Host)
// Start the I/O processing handlers depending on whether the client is
// in HTTP POST mode or the default websocket mode.
if c.config.HttpPostMode {
c.wg.Add(1)
go c.sendPostHandler()
} else {
c.wg.Add(2)
go c.wsInHandler()
go c.wsOutHandler()
}
}
// WaitForShutdown blocks until the client goroutines are stopped and the
// connection is closed.
func (c *Client) WaitForShutdown() {
c.wg.Wait()
}
// ConnConfig describes the connection configuration parameters for the client.
// This
type ConnConfig struct {
// Host is the IP address and port of the RPC server you want to connect
// to.
Host string
// Endpoint is the websocket endpoint on the RPC server. This is
// typically "ws" or "frontend".
Endpoint string
// User is the username to use to authenticate to the RPC server.
User string
// Pass is the passphrase to use to authenticate to the RPC server.
Pass string
// DisableTLS specifies whether transport layer security should be
// disabled. It is recommended to always use TLS if the RPC server
// supports it as otherwise your username and password is sent across
// the wire in cleartext.
DisableTLS bool
// Certificates are the bytes for a PEM-encoded certificate chain used
// for the TLS connection. It has no effect if the DisableTLS parameter
// is true.
Certificates []byte
// Proxy specifies to connect through a SOCKS 5 proxy server. It may
// be an empty string if a proxy is not required.
Proxy string
// ProxyUser is an optional username to use for the proxy server if it
// requires authentication. It has no effect if the Proxy parameter
// is not set.
ProxyUser string
// ProxyPass is an optional password to use for the proxy server if it
// requires authentication. It has no effect if the Proxy parameter
// is not set.
ProxyPass string
// DisableAutoReconnect specifies the client should not automatically
// try to reconnect to the server when it has been disconnected.
DisableAutoReconnect bool
// HttpPostMode instructs the client to run using multiple independent
// connections issuing HTTP POST requests instead of using the default
// of websockets. Websockets are generally preferred as some of the
// features of the client such notifications only work with websockets,
// however, not all servers support the websocket extensions, so this
// flag can be set to true to use basic HTTP POST requests instead.
HttpPostMode bool
}
// newHttpClient returns a new http client that is configured according to the
// proxy and TLS settings in the associated connection configuration.
func newHttpClient(config *ConnConfig) (*http.Client, error) {
// Set proxy function if there is a proxy configured.
var proxyFunc func(*http.Request) (*url.URL, error)
if config.Proxy != "" {
proxyURL, err := url.Parse(config.Proxy)
if err != nil {
return nil, err
}
proxyFunc = http.ProxyURL(proxyURL)
}
// Configure TLS if needed.
var tlsConfig *tls.Config
if !config.DisableTLS {
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(config.Certificates)
tlsConfig = &tls.Config{
RootCAs: pool,
}
}
client := http.Client{
Transport: &http.Transport{
Proxy: proxyFunc,
TLSClientConfig: tlsConfig,
},
}
return &client, nil
}
// dial opens a websocket connection using the passed connection configuration
// details.
func dial(config *ConnConfig) (*websocket.Conn, error) {
// Connect to websocket.
url := fmt.Sprintf("wss://%s/%s", config.Host, config.Endpoint)
wsConfig, err := websocket.NewConfig(url, "https://localhost/")
if err != nil {
return nil, err
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(config.Certificates)
wsConfig.TlsConfig = &tls.Config{
RootCAs: pool,
MinVersion: tls.VersionTLS12,
}
// The wallet requires basic authorization, so use a custom config with
// with the Authorization header set.
login := config.User + ":" + config.Pass
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
wsConfig.Header.Add("Authorization", auth)
// Attempt to connect to running wallet instance using a proxy if one
// is configured.
if config.Proxy != "" {
proxy := &socks.Proxy{
Addr: config.Proxy,
Username: config.ProxyUser,
Password: config.ProxyPass,
}
conn, err := proxy.Dial("tcp", config.Host)
if err != nil {
return nil, err
}
tlsConn := tls.Client(conn, wsConfig.TlsConfig)
ws, err := websocket.NewClient(wsConfig, tlsConn)
if err != nil {
return nil, err
}
return ws, nil
}
// No proxy was specified, so attempt to connect to running wallet
// instance directly.
ws, err := websocket.DialConfig(wsConfig)
if err != nil {
// XXX(davec): This is not really accurate, but unfortunately
// the current websocket package does not expose the status
// code, so it's impossible to tell for sure.
if dialError, ok := err.(*websocket.DialError); ok {
if dialError.Err == websocket.ErrBadStatus {
return nil, ErrInvalidAuth
}
}
return nil, err
}
return ws, nil
}
// New create a new RPC client based on the provided connection configuration
// details. The notification handlers parameter may be nil if you are not
// interested in receiving notifications and will be ignored when if the
// configuration is set to run in HTTP POST mode.
func New(config *ConnConfig, ntfnHandlers *NotificationHandlers) (*Client, error) {
// Either open a websocket connection or create an HTTP client depending
// on the HTTP POST mode. Also, set the notification handlers to nil
// when running in HTTP POST mode.
var wsConn *websocket.Conn
var httpClient *http.Client
if config.HttpPostMode {
ntfnHandlers = nil
var err error
httpClient, err = newHttpClient(config)
if err != nil {
return nil, err
}
} else {
var err error
wsConn, err = dial(config)
if err != nil {
return nil, err
}
}
client := &Client{
config: config,
wsConn: wsConn,
httpClient: httpClient,
requestMap: make(map[int64]*list.Element),
requestList: list.New(),
ntfnHandlers: ntfnHandlers,
sendChan: make(chan []byte, sendBufferSize),
sendPostChan: make(chan *sendPostDetails, sendPostBufferSize),
disconnect: make(chan struct{}),
shutdown: make(chan struct{}),
}
client.start()
if !client.config.HttpPostMode && !client.config.DisableAutoReconnect {
client.wg.Add(1)
go client.wsReconnectHandler()
}
return client, nil
}

74
log.go Normal file
View file

@ -0,0 +1,74 @@
// 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 (
"errors"
"github.com/conformal/btclog"
"io"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
DisableLog()
}
// DisableLog disables all library log output. Logging output is disabled
// by default until either UseLogger or SetLogWriter are called.
func DisableLog() {
log = btclog.Disabled
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// SetLogWriter uses a specified io.Writer to output package logging info.
// This allows a caller to direct package logging output without needing a
// dependency on seelog. If the caller is also using btclog, UseLogger should
// be used instead.
func SetLogWriter(w io.Writer, level string) error {
if w == nil {
return errors.New("nil writer")
}
lvl, ok := btclog.LogLevelFromString(level)
if !ok {
return errors.New("invalid log level")
}
l, err := btclog.NewLoggerFromWriter(w, lvl)
if err != nil {
return err
}
UseLogger(l)
return nil
}
// LogClosure is a closure that can be printed with %v to be used to
// generate expensive-to-create data for a detailed log level and avoid doing
// the work if the data isn't printed.
type logClosure func() string
// String invokes the log closure and returns the results string.
func (c logClosure) String() string {
return c()
}
// newLogClosure returns a new closure over the passed function which allows
// it to be used as a parameter in a logging function that is only invoked when
// the logging level is such that the message will actually be logged.
func newLogClosure(c func() string) logClosure {
return logClosure(c)
}

361
mining.go Normal file
View file

@ -0,0 +1,361 @@
// 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/hex"
"fmt"
"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
// 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)
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)
}
return result, nil
}
// GetGenerateAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See GetGenerate for the blocking version and more details.
func (c *Client) GetGenerateAsync() FutureGetGenerateResult {
id := c.NextID()
cmd, err := btcjson.NewGetGenerateCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetGenerate returns true if the server is set to mine, otherwise false.
func (c *Client) GetGenerate() (bool, error) {
return c.GetGenerateAsync().Receive()
}
// FutureSetGenerateResult is a future promise to deliver the result of a
// SetGenerateAsync RPC invocation (or an applicable error).
type FutureSetGenerateResult chan *futureResult
// 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.
func (r FutureSetGenerateResult) Receive() error {
_, err := receiveFuture(r)
if err != nil {
return err
}
return nil
}
// SetGenerateAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See SetGenerate for the blocking version and more details.
func (c *Client) SetGenerateAsync(enable bool, numCPUs int) FutureSetGenerateResult {
id := c.NextID()
cmd, err := btcjson.NewSetGenerateCmd(id, enable, numCPUs)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// SetGenerate sets the server to generate coins (mine) or not.
func (c *Client) SetGenerate(enable bool, numCPUs int) error {
return c.SetGenerateAsync(enable, numCPUs).Receive()
}
// FutureGetHashesPerSecResult is a future promise to deliver the result of a
// GetHashesPerSecAsync RPC invocation (or an applicable error).
type FutureGetHashesPerSecResult chan *futureResult
// 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)
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)
}
return result, nil
}
// GetHashesPerSecAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See GetHashesPerSec for the blocking version and more details.
func (c *Client) GetHashesPerSecAsync() FutureGetHashesPerSecResult {
id := c.NextID()
cmd, err := btcjson.NewGetHashesPerSecCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetHashesPerSec returns a recent hashes per second performance measurement
// while generating coins (mining). Zero is returned if the server is not
// mining.
func (c *Client) GetHashesPerSec() (int64, error) {
return c.GetHashesPerSecAsync().Receive()
}
// FutureGetMiningInfoResult is a future promise to deliver the result of a
// GetMiningInfoAsync RPC invocation (or an applicable error).
type FutureGetMiningInfoResult chan *futureResult
// 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)
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)
}
return &result, nil
}
// GetMiningInfoAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See GetMiningInfo for the blocking version and more details.
func (c *Client) GetMiningInfoAsync() FutureGetMiningInfoResult {
id := c.NextID()
cmd, err := btcjson.NewGetMiningInfoCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetMiningInfo returns mining information.
func (c *Client) GetMiningInfo() (*btcjson.GetMiningInfoResult, error) {
return c.GetMiningInfoAsync().Receive()
}
// FutureGetNetworkHashPS is a future promise to deliver the result of a
// GetNetworkHashPSAsync RPC invocation (or an applicable error).
type FutureGetNetworkHashPS chan *futureResult
// 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)
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)
}
return result, nil
}
// GetNetworkHashPSAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See GetNetworkHashPS for the blocking version and more details.
func (c *Client) GetNetworkHashPSAsync() FutureGetNetworkHashPS {
id := c.NextID()
cmd, err := btcjson.NewGetNetworkHashPSCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetNetworkHashPS returns the estimated network hashes per second using the
// default number of blocks and the most recent block height.
//
// See GetNetworkHashPS2 to override the number of blocks to use and
// GetNetworkHashPS3 to override the height at which to calculate the estimate.
func (c *Client) GetNetworkHashPS() (int64, error) {
return c.GetNetworkHashPSAsync().Receive()
}
// GetNetworkHashPS2Async returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See GetNetworkHashPS2 for the blocking version and more details.
func (c *Client) GetNetworkHashPS2Async(blocks int) FutureGetNetworkHashPS {
id := c.NextID()
cmd, err := btcjson.NewGetNetworkHashPSCmd(id, blocks)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetNetworkHashPS2 returns the estimated network hashes per second for the
// specified previous number of blocks working backwards from the most recent
// block height. The blocks parameter can also be -1 in which case the number
// of blocks since the last difficulty change will be used.
//
// See GetNetworkHashPS to use defaults and GetNetworkHashPS3 to override the
// height at which to calculate the estimate.
func (c *Client) GetNetworkHashPS2(blocks int) (int64, error) {
return c.GetNetworkHashPS2Async(blocks).Receive()
}
// GetNetworkHashPS3Async returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See GetNetworkHashPS3 for the blocking version and more details.
func (c *Client) GetNetworkHashPS3Async(blocks, height int) FutureGetNetworkHashPS {
id := c.NextID()
cmd, err := btcjson.NewGetNetworkHashPSCmd(id, blocks, height)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetNetworkHashPS3 returns the estimated network hashes per second for the
// specified previous number of blocks working backwards from the specified
// block height. The blocks parameter can also be -1 in which case the number
// of blocks since the last difficulty change will be used.
//
// See GetNetworkHashPS and GetNetworkHashPS2 to use defaults.
func (c *Client) GetNetworkHashPS3(blocks, height int) (int64, error) {
return c.GetNetworkHashPS3Async(blocks, height).Receive()
}
// FutureGetWork is a future promise to deliver the result of a
// GetWorkAsync RPC invocation (or an applicable error).
type FutureGetWork chan *futureResult
// 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)
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: %T\n", reply)
}
return &result, nil
}
// GetWorkAsync returns an instance of a type that can be used to get the result
// of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See GetWork for the blocking version and more details.
func (c *Client) GetWorkAsync() FutureGetWork {
id := c.NextID()
cmd, err := btcjson.NewGetWorkCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// TODO(davec): Correct this
// GetWork returns hash data to work on.
func (c *Client) GetWork() (*btcjson.GetWorkResult, error) {
return c.GetWorkAsync().Receive()
}
// FutureSubmitBlockResult is a future promise to deliver the result of a
// SubmitBlockAsync RPC invocation (or an applicable error).
type FutureSubmitBlockResult chan *futureResult
// Receive waits for the response promised by the future and returns an error if
// any occurred when submitting the block.
func (r FutureSubmitBlockResult) Receive() error {
_, err := receiveFuture(r)
if err != nil {
return err
}
return nil
}
// SubmitBlockAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See SubmitBlock for the blocking version and more details.
func (c *Client) SubmitBlockAsync(block *btcutil.Block, options *btcjson.SubmitBlockOptions) FutureSubmitBlockResult {
id := c.NextID()
blockBytes, err := block.Bytes()
if err != nil {
return newFutureError(err)
}
blockHex := hex.EncodeToString(blockBytes)
cmd, err := btcjson.NewSubmitBlockCmd(id, blockHex, options)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// SubmitBlock attempts to submit a new block into the bitcoin network.
func (c *Client) SubmitBlock(block *btcutil.Block, options *btcjson.SubmitBlockOptions) error {
return c.SubmitBlockAsync(block, options).Receive()
}
// TODO(davec): Implement GetBlockTemplate

327
net.go Normal file
View file

@ -0,0 +1,327 @@
// 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 (
"fmt"
"github.com/conformal/btcjson"
)
// AddNodeCommand enumerates the available commands that the AddNode function
// accepts.
type AddNodeCommand string
// Constants used to indicate the command for the AddNode function.
const (
// ANAdd indicates the specified host should be added as a persistent
// peer.
ANAdd AddNodeCommand = "add"
// ANRemove indicates the specified peer should be removed.
ANRemove AddNodeCommand = "remove"
// ANOneTry indicates the specified host should try to connect once,
// but it should not be made persistent.
ANOneTry AddNodeCommand = "onetry"
)
// String returns the AddNodeCommand in human-readable form.
func (cmd AddNodeCommand) String() string {
return string(cmd)
}
// FutureAddNodeResult is a future promise to deliver the result of an
// AddNodeAsync RPC invocation (or an applicable error).
type FutureAddNodeResult chan *futureResult
// Receive waits for the response promised by the future and returns an error if
// any occurred when performing the specified command.
func (r FutureAddNodeResult) Receive() error {
_, err := receiveFuture(r)
if err != nil {
return err
}
return nil
}
// AddNodeAsync returns an instance of a type that can be used to get the result
// of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See AddNode for the blocking version and more details.
func (c *Client) AddNodeAsync(host string, command AddNodeCommand) FutureAddNodeResult {
id := c.NextID()
cmd, err := btcjson.NewAddNodeCmd(id, host, string(command))
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// AddNode attempts to perform the passed command on the passed persistent peer.
// For example, it can be used to add or a remove a persistent peer, or to do
// a one time connection to a peer.
//
// It may not be used to remove non-persistent peers.
func (c *Client) AddNode(host string, command AddNodeCommand) error {
return c.AddNodeAsync(host, command).Receive()
}
// FutureGetAddedNodeInfoResult is a future promise to deliver the result of a
// GetAddedNodeInfoAsync RPC invocation (or an applicable error).
type FutureGetAddedNodeInfoResult chan *futureResult
// 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)
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)
}
return nodeInfo, nil
}
// GetAddedNodeInfoAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See GetAddedNodeInfo for the blocking version and more details.
func (c *Client) GetAddedNodeInfoAsync(peer string) FutureGetAddedNodeInfoResult {
id := c.NextID()
cmd, err := btcjson.NewGetAddedNodeInfoCmd(id, true, peer)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetAddedNodeInfo returns information about manually added (persistent) peers.
//
// See GetAddedNodeInfoNoDNS to retrieve only a list of the added (persistent)
// peers.
func (c *Client) GetAddedNodeInfo(peer string) ([]btcjson.GetAddedNodeInfoResult, error) {
return c.GetAddedNodeInfoAsync(peer).Receive()
}
// FutureGetAddedNodeInfoNoDNSResult is a future promise to deliver the result
// of a GetAddedNodeInfoNoDNSAsync RPC invocation (or an applicable error).
type FutureGetAddedNodeInfoNoDNSResult chan *futureResult
// 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)
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)
}
return nodes, nil
}
// GetAddedNodeInfoNoDNSAsync returns an instance of a type that can be used to
// get the result of the RPC at some future time by invoking the Receive
// function on the returned instance.
//
// See GetAddedNodeInfoNoDNS for the blocking version and more details.
func (c *Client) GetAddedNodeInfoNoDNSAsync(peer string) FutureGetAddedNodeInfoNoDNSResult {
id := c.NextID()
cmd, err := btcjson.NewGetAddedNodeInfoCmd(id, false, peer)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetAddedNodeInfoNoDNS returns a list of manually added (persistent) peers.
// This works by setting the dns flag to false in the underlying RPC.
//
// See GetAddedNodeInfo to obtain more information about each added (persistent)
// peer.
func (c *Client) GetAddedNodeInfoNoDNS(peer string) ([]string, error) {
return c.GetAddedNodeInfoNoDNSAsync(peer).Receive()
}
// FutureGetConnectionCountResult is a future promise to deliver the result
// of a GetConnectionCountAsync RPC invocation (or an applicable error).
type FutureGetConnectionCountResult chan *futureResult
// 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)
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)
}
return int64(fcount), nil
}
// GetConnectionCountAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See GetConnectionCount for the blocking version and more details.
func (c *Client) GetConnectionCountAsync() FutureGetConnectionCountResult {
id := c.NextID()
cmd, err := btcjson.NewGetConnectionCountCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetConnectionCount returns the number of active connections to other peers.
func (c *Client) GetConnectionCount() (int64, error) {
return c.GetConnectionCountAsync().Receive()
}
// FuturePingResult is a future promise to deliver the result of a PingAsync RPC
// invocation (or an applicable error).
type FuturePingResult chan *futureResult
// Receive waits for the response promised by the future and returns the result
// of queueing a ping to be sent to each connected peer.
func (r FuturePingResult) Receive() error {
_, err := receiveFuture(r)
if err != nil {
return err
}
return nil
}
// PingAsync returns an instance of a type that can be used to get the result of
// the RPC at some future time by invoking the Receive function on the returned
// instance.
//
// See Ping for the blocking version and more details.
func (c *Client) PingAsync() FuturePingResult {
id := c.NextID()
cmd, err := btcjson.NewPingCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// Ping queues a ping to be sent to each connected peer.
//
// Use the GetPeerInfo function and examine the PingTime and PingWait fields to
// access the ping times.
func (c *Client) Ping() error {
return c.PingAsync().Receive()
}
// FutureGetPeerInfoResult is a future promise to deliver the result of a
// GetPeerInfoAsync RPC invocation (or an applicable error).
type FutureGetPeerInfoResult chan *futureResult
// 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)
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)
}
return peerInfo, nil
}
// GetPeerInfoAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See GetPeerInfo for the blocking version and more details.
func (c *Client) GetPeerInfoAsync() FutureGetPeerInfoResult {
id := c.NextID()
cmd, err := btcjson.NewGetPeerInfoCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetPeerInfo returns data about each connected network peer.
func (c *Client) GetPeerInfo() ([]btcjson.GetPeerInfoResult, error) {
return c.GetPeerInfoAsync().Receive()
}
// FutureGetNetTotalsResult is a future promise to deliver the result of a
// GetNetTotalsAsync RPC invocation (or an applicable error).
type FutureGetNetTotalsResult chan *futureResult
// 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)
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)
}
return &totals, nil
}
// GetNetTotalsAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See GetNetTotals for the blocking version and more details.
func (c *Client) GetNetTotalsAsync() FutureGetNetTotalsResult {
id := c.NextID()
cmd, err := btcjson.NewGetNetTotalsCmd(id)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetNetTotals returns network traffic statistics.
func (c *Client) GetNetTotals() (*btcjson.GetNetTotalsResult, error) {
return c.GetNetTotalsAsync().Receive()
}

687
notify.go Normal file
View file

@ -0,0 +1,687 @@
// 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 (
"bytes"
"encoding/hex"
"errors"
"github.com/conformal/btcjson"
"github.com/conformal/btcutil"
"github.com/conformal/btcwire"
"github.com/conformal/btcws"
)
var (
// ErrNotificationsNotSupported is an error to describe the condition
// where the caller is trying to request notifications when they are
// not supported due to the client being configured to run in HTTP POST
// mode.
ErrNotificationsNotSupported = errors.New("notifications are not " +
"supported when running in HTTP POST mode")
)
// newNilFutureResult returns a new future result channel that already has the
// 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}
return responseChan
}
// NotificationHandlers defines callback function pointers to invoke with
// notifications. Since all of the functions are nil by default, all
// notifications are effectively ignored until their handlers are set to a
// concrete callback.
//
// NOTE: These handlers must NOT directly call any blocking calls on the client
// instance since the input reader goroutine blocks until the callback has
// completed. Doing so will result in a deadlock situation.
type NotificationHandlers struct {
// OnBlockConnected is invoked when a block is connected to the longest
// (best) chain. It will only be invoked if a preceding call to
// NotifyBlocks has been made to register for the notification and the
// function is non-nil.
OnBlockConnected func(hash *btcwire.ShaHash, height int32)
// OnBlockDisconnected is invoked when a block is disconnected from the
// longest (best) chain. It will only be invoked if a preceding call to
// NotifyBlocks has been made to register for the notification and the
// function is non-nil.
OnBlockDisconnected func(hash *btcwire.ShaHash, height int32)
// OnRecvTx is invoked when a transaction that receives funds to a
// registered address is received into the memory pool and also
// connected to the longest (best) chain. It will only be invoked if a
// preceding call to NotifyReceived, Rescan, or RescanEndHeight has been
// made to register for the notification and the function is non-nil.
OnRecvTx func(transaction *btcutil.Tx, details *btcws.BlockDetails)
// OnRedeemingTx is invoked when a transaction that spends a registered
// outpoint is received into the memory pool and also connected to the
// longest (best) chain. It will only be invoked if a preceding call to
// NotifySpent, Rescan, or RescanEndHeight has been made to register for
// the notification and the function is non-nil.
//
// NOTE: The NotifyReceived will automatically register notifications
// for the outpoints that are now "owned" as a result of receiving
// funds to the registered addresses. This means it is possible for
// this to invoked indirectly as the result of a NotifyReceived call.
OnRedeemingTx func(transaction *btcutil.Tx, details *btcws.BlockDetails)
// OnRescanProgress is invoked periodically when a rescan is underway.
// It will only be invoked if a preceding call to Rescan or
// RescanEndHeight has been made and the function is non-nil.
OnRescanProgress func(lastProcessedHeight int32)
// OnTxAccepted is invoked when a transaction is accepted into the
// memory pool. It will only be invoked if a preceding call to
// NotifyNewTransactions with the verbose flag set to false has been
// made to register for the notification and the function is non-nil.
OnTxAccepted func(hash *btcwire.ShaHash, amount btcutil.Amount)
// OnTxAccepted is invoked when a transaction is accepted into the
// memory pool. It will only be invoked if a preceding call to
// NotifyNewTransactions with the verbose flag set to true has been
// made to register for the notification and the function is non-nil.
OnTxAcceptedVerbose func(txDetails *btcjson.TxRawResult)
// OnBtcdConnected is invoked when a wallet connects or disconnects from
// btcd.
//
// This will only be available when client is connected to a wallet
// server such as btcwallet.
OnBtcdConnected func(connected bool)
// OnAccountBalance is invoked with account balance updates.
//
// This will only be available when speaking to a wallet server
// such as btcwallet.
OnAccountBalance func(account string, balance btcutil.Amount, confirmed bool)
// OnWalletLockState is invoked when a wallet is locked or unlocked.
//
// This will only be available when client is connected to a wallet
// server such as btcwallet.
OnWalletLockState func(locked bool)
// OnUnknownNotification is invoked when an unrecognized notification
// is received. This typically means the notification handling code
// 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{})
}
// 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<X> handler registered with
// the client.
func (c *Client) handleNotification(cmd btcjson.Cmd) {
// Ignore the notification if the client is not interested in any
// notifications.
if c.ntfnHandlers == nil {
return
}
switch ntfn := cmd.(type) {
// OnBlockConnected
case *btcws.BlockConnectedNtfn:
// Ignore the notification is the client is not interested in
// it.
if c.ntfnHandlers.OnBlockConnected == nil {
return
}
hash, err := btcwire.NewShaHashFromStr(ntfn.Hash)
if err != nil {
log.Warnf("Received block connected notification with "+
"invalid hash string: %q", ntfn.Hash)
return
}
c.ntfnHandlers.OnBlockConnected(hash, ntfn.Height)
// OnBlockDisconnected
case *btcws.BlockDisconnectedNtfn:
// Ignore the notification is the client is not interested in
// it.
if c.ntfnHandlers.OnBlockDisconnected == nil {
return
}
hash, err := btcwire.NewShaHashFromStr(ntfn.Hash)
if err != nil {
log.Warnf("Received block disconnected notification "+
"with invalid hash string: %q", ntfn.Hash)
return
}
c.ntfnHandlers.OnBlockDisconnected(hash, ntfn.Height)
// OnRecvTx
case *btcws.RecvTxNtfn:
// 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)
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",
err)
}
c.ntfnHandlers.OnRecvTx(btcutil.NewTx(&msgTx), ntfn.Block)
// OnRedeemingTx
case *btcws.RedeemingTxNtfn:
// 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)
if err != nil {
log.Warnf("Received redeemingtx 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 redeemingtx notification with "+
"transaction that failed to deserialize: %v",
err)
}
c.ntfnHandlers.OnRedeemingTx(btcutil.NewTx(&msgTx), ntfn.Block)
// OnRescanProgress
case *btcws.RescanProgressNtfn:
// Ignore the notification is the client is not interested in
// it.
if c.ntfnHandlers.OnRescanProgress == nil {
return
}
c.ntfnHandlers.OnRescanProgress(ntfn.LastProcessed)
// OnTxAccepted
case *btcws.TxAcceptedNtfn:
// Ignore the notification is the client is not interested in
// it.
if c.ntfnHandlers.OnTxAccepted == nil {
return
}
hash, err := btcwire.NewShaHashFromStr(ntfn.TxID)
if err != nil {
log.Warnf("Received tx accepted notification with "+
"invalid hash string: %q", ntfn.TxID)
return
}
c.ntfnHandlers.OnTxAccepted(hash, btcutil.Amount(ntfn.Amount))
// OnTxAcceptedVerbose
case *btcws.TxAcceptedVerboseNtfn:
// Ignore the notification is the client is not interested in
// it.
if c.ntfnHandlers.OnTxAcceptedVerbose == nil {
return
}
c.ntfnHandlers.OnTxAcceptedVerbose(ntfn.RawTx)
// OnBtcdConnected
case *btcws.BtcdConnectedNtfn:
// Ignore the notification is the client is not interested in
// it.
if c.ntfnHandlers.OnBtcdConnected == nil {
return
}
c.ntfnHandlers.OnBtcdConnected(ntfn.Connected)
// OnAccountBalance
case *btcws.AccountBalanceNtfn:
// Ignore the notification is the client is not interested in
// it.
if c.ntfnHandlers.OnAccountBalance == nil {
return
}
balance, err := btcjson.JSONToAmount(ntfn.Balance)
if err != nil {
log.Warnf("Received account balance notification with "+
"an amount that does not parse: %v",
ntfn.Balance)
return
}
c.ntfnHandlers.OnAccountBalance(ntfn.Account,
btcutil.Amount(balance), ntfn.Confirmed)
// OnWalletLockState
case *btcws.WalletLockStateNtfn:
// Ignore the notification is the client is not interested in
// it.
if c.ntfnHandlers.OnWalletLockState == nil {
return
}
c.ntfnHandlers.OnWalletLockState(ntfn.Locked)
// OnUnknownNotification
default:
if c.ntfnHandlers.OnUnknownNotification == nil {
return
}
c.ntfnHandlers.OnUnknownNotification(ntfn)
}
}
// FutureNotifyBlocksResult is a future promise to deliver the result of a
// NotifyBlocksAsync RPC invocation (or an applicable error).
type FutureNotifyBlocksResult chan *futureResult
// Receive waits for the response promised by the future and returns an error
// if the registration was not successful.
func (r FutureNotifyBlocksResult) Receive() error {
_, err := receiveFuture(r)
if err != nil {
return err
}
return nil
}
// NotifyBlocksAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See NotifyBlocks for the blocking version and more details.
//
// NOTE: This is a btcd extension and requires a websocket connection.
func (c *Client) NotifyBlocksAsync() FutureNotifyBlocksResult {
// Not supported in HTTP POST mode.
if c.config.HttpPostMode {
return newFutureError(ErrNotificationsNotSupported)
}
// Ignore the notification if the client is not interested in
// notifications.
if c.ntfnHandlers == nil {
return newNilFutureResult()
}
id := c.NextID()
cmd := btcws.NewNotifyBlocksCmd(id)
return c.sendCmd(cmd)
}
// NotifyBlocks registers the client to receive notifications when blocks are
// connected and disconnected from the main chain. The notifications are
// delivered to the notification handlers associated with the client. Calling
// this function has no effect if there are no notification handlers and will
// result in an error if the client is configured to run in HTTP POST mode.
//
// The notifications delivered as a result of this call will be via one of
// OnBlockConnected or OnBlockDisconnected.
//
// NOTE: This is a btcd extension and requires a websocket connection.
func (c *Client) NotifyBlocks() error {
return c.NotifyBlocksAsync().Receive()
}
// FutureNotifySpentResult is a future promise to deliver the result of a
// NotifySpentAsync RPC invocation (or an applicable error).
type FutureNotifySpentResult chan *futureResult
// Receive waits for the response promised by the future and returns an error
// if the registration was not successful.
func (r FutureNotifySpentResult) Receive() error {
_, err := receiveFuture(r)
if err != nil {
return err
}
return nil
}
// NotifySpentAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See NotifySpent for the blocking version and more details.
//
// NOTE: This is a btcd extension and requires a websocket connection.
func (c *Client) NotifySpentAsync(outpoint *btcwire.OutPoint) FutureNotifySpentResult {
// Not supported in HTTP POST mode.
if c.config.HttpPostMode {
return newFutureError(ErrNotificationsNotSupported)
}
// Ignore the notification if the client is not interested in
// notifications.
if c.ntfnHandlers == nil {
return newNilFutureResult()
}
id := c.NextID()
cmd := btcws.NewNotifySpentCmd(id, btcws.NewOutPointFromWire(outpoint))
return c.sendCmd(cmd)
}
// NotifySpent registers the client to receive notifications when the passed
// transaction output is spent. The notifications are delivered to the
// notification handlers associated with the client. Calling this function has
// no effect if there are no notification handlers and will result in an error
// if the client is configured to run in HTTP POST mode.
//
// The notifications delivered as a result of this call will be via
// OnRedeemingTx.
//
// NOTE: This is a btcd extension and requires a websocket connection.
func (c *Client) NotifySpent(outpoint *btcwire.OutPoint) error {
return c.NotifySpentAsync(outpoint).Receive()
}
// FutureNotifyNewTransactionsResult is a future promise to deliver the result
// of a NotifyNewTransactionsAsync RPC invocation (or an applicable error).
type FutureNotifyNewTransactionsResult chan *futureResult
// Receive waits for the response promised by the future and returns an error
// if the registration was not successful.
func (r FutureNotifyNewTransactionsResult) Receive() error {
_, err := receiveFuture(r)
if err != nil {
return err
}
return nil
}
// NotifyNewTransactionsAsync returns an instance of a type that can be used to
// get the result of the RPC at some future time by invoking the Receive
// function on the returned instance.
//
// See NotifyNewTransactionsAsync for the blocking version and more details.
//
// NOTE: This is a btcd extension and requires a websocket connection.
func (c *Client) NotifyNewTransactionsAsync(verbose bool) FutureNotifyNewTransactionsResult {
// Not supported in HTTP POST mode.
if c.config.HttpPostMode {
return newFutureError(ErrNotificationsNotSupported)
}
// Ignore the notification if the client is not interested in
// notifications.
if c.ntfnHandlers == nil {
return newNilFutureResult()
}
id := c.NextID()
cmd, err := btcws.NewNotifyNewTransactionsCmd(id, verbose)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// NotifyNewTransactions registers the client to receive notifications every
// time a new transaction is accepted to the memory pool. The notifications are
// delivered to the notification handlers associated with the client. Calling
// this function has no effect if there are no notification handlers and will
// result in an error if the client is configured to run in HTTP POST mode.
//
// The notifications delivered as a result of this call will be via one of
// OnTxAccepted (when verbose is false) or OnTxAcceptedVerbose (when verbose is
// true).
//
// NOTE: This is a btcd extension and requires a websocket connection.
func (c *Client) NotifyNewTransactions(verbose bool) error {
return c.NotifyNewTransactionsAsync(verbose).Receive()
}
// FutureNotifyReceivedResult is a future promise to deliver the result of a
// NotifyReceivedAsync RPC invocation (or an applicable error).
type FutureNotifyReceivedResult chan *futureResult
// Receive waits for the response promised by the future and returns an error
// if the registration was not successful.
func (r FutureNotifyReceivedResult) Receive() error {
_, err := receiveFuture(r)
if err != nil {
return err
}
return nil
}
// NotifyReceivedAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See NotifyReceived for the blocking version and more details.
//
// NOTE: This is a btcd extension and requires a websocket connection.
func (c *Client) NotifyReceivedAsync(addresses []btcutil.Address) FutureNotifyReceivedResult {
// Not supported in HTTP POST mode.
if c.config.HttpPostMode {
return newFutureError(ErrNotificationsNotSupported)
}
// Ignore the notification if the client is not interested in
// notifications.
if c.ntfnHandlers == nil {
return newNilFutureResult()
}
// Convert addresses to strings.
addrs := make([]string, 0, len(addresses))
for _, addr := range addresses {
addrs = append(addrs, addr.EncodeAddress())
}
id := c.NextID()
cmd := btcws.NewNotifyReceivedCmd(id, addrs)
return c.sendCmd(cmd)
}
// NotifyReceived registers the client to receive notifications every time a
// new transaction which pays to one of the passed addresses is accepted to
// memory pool or in a block connected to the block chain. In addition, when
// one of these transactions is detected, the client is also automatically
// registered for notifications when the new transaction outpoints the address
// now has available are spent (See NotifySpent). The notifications are
// delivered to the notification handlers associated with the client. Calling
// this function has no effect if there are no notification handlers and will
// result in an error if the client is configured to run in HTTP POST mode.
//
// The notifications delivered as a result of this call will be via one of
// *OnRecvTx (for transactions that receive funds to one of the passed
// addresses) or OnRedeemingTx (for transactions which spend from one
// of the outpoints which are automatically registered upon receipt of funds to
// the address).
//
// NOTE: This is a btcd extension and requires a websocket connection.
func (c *Client) NotifyReceived(addresses []btcutil.Address) error {
return c.NotifyReceivedAsync(addresses).Receive()
}
// FutureRescanResult is a future promise to deliver the result of a RescanAsync
// or RescanEndHeightAsync RPC invocation (or an applicable error).
type FutureRescanResult chan *futureResult
// Receive waits for the response promised by the future and returns an error
// if the rescan was not successful.
func (r FutureRescanResult) Receive() error {
_, err := receiveFuture(r)
if err != nil {
return err
}
return nil
}
// RescanAsync returns an instance of a type that can be used to get the result
// of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See Rescan for the blocking version and more details.
//
// NOTE: This is a btcd extension and requires a websocket connection.
func (c *Client) RescanAsync(startHeight int32, addresses []btcutil.Address,
outpoints []*btcwire.OutPoint) FutureRescanResult {
// Not supported in HTTP POST mode.
if c.config.HttpPostMode {
return newFutureError(ErrNotificationsNotSupported)
}
// Ignore the notification if the client is not interested in
// notifications.
if c.ntfnHandlers == nil {
return newNilFutureResult()
}
// Convert addresses to strings.
addrs := make([]string, 0, len(addresses))
for _, addr := range addresses {
addrs = append(addrs, addr.EncodeAddress())
}
// Convert outpoints.
ops := make([]btcws.OutPoint, 0, len(outpoints))
for _, op := range outpoints {
ops = append(ops, *btcws.NewOutPointFromWire(op))
}
id := c.NextID()
cmd, err := btcws.NewRescanCmd(id, startHeight, addrs, ops)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// Rescan rescans the block chain starting from the provided start height to the
// end of the longest chain for transactions that pay to the passed addresses
// and transactions which spend the passed outpoints.
//
// The notifications of found transactions are delivered to the notification
// handlers associated with client and this call will not return until the
// rescan has completed. Calling this function has no effect if there are no
// notification handlers and will result in an error if the client is configured
// to run in HTTP POST mode.
//
// The notifications delivered as a result of this call will be via one of
// OnRedeemingTx (for transactions which spend from the one of the
// passed outpoints), OnRecvTx (for transactions that receive funds
// to one of the passed addresses), and OnRescanProgress (for rescan progress
// updates).
//
// See RescanEndHeight to also specify a block height at which to stop the
// rescan if a bounded rescan is desired instead.
//
// NOTE: This is a btcd extension and requires a websocket connection.
func (c *Client) Rescan(startHeight int32, addresses []btcutil.Address,
outpoints []*btcwire.OutPoint) error {
return c.RescanAsync(startHeight, addresses, outpoints).Receive()
}
// RescanEndHeightAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See RescanEndHeight for the blocking version and more details.
//
// NOTE: This is a btcd extension and requires a websocket connection.
func (c *Client) RescanEndHeightAsync(startHeight int32,
addresses []btcutil.Address, outpoints []*btcwire.OutPoint,
endHeight int64) FutureRescanResult {
// Not supported in HTTP POST mode.
if c.config.HttpPostMode {
return newFutureError(ErrNotificationsNotSupported)
}
// Ignore the notification if the client is not interested in
// notifications.
if c.ntfnHandlers == nil {
return newNilFutureResult()
}
// Convert addresses to strings.
addrs := make([]string, 0, len(addresses))
for _, addr := range addresses {
addrs = append(addrs, addr.EncodeAddress())
}
// Convert outpoints.
ops := make([]btcws.OutPoint, 0, len(outpoints))
for _, op := range outpoints {
ops = append(ops, *btcws.NewOutPointFromWire(op))
}
id := c.NextID()
cmd, err := btcws.NewRescanCmd(id, startHeight, addrs, ops, endHeight)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// RescanEndHeight rescans the block chain starting from the provided start
// height up to the provided end height for transactions that pay to the passed
// addresses and transactions which spend the passed outpoints.
//
// The notifications of found transactions are delivered to the notification
// handlers associated with client and this call will not return until the
// rescan has completed. Calling this function has no effect if there are no
// notification handlers and will result in an error if the client is configured
// to run in HTTP POST mode.
//
// The notifications delivered as a result of this call will be via one of
// OnRedeemingTx (for transactions which spend from the one of the
// passed outpoints), OnRecvTx (for transactions that receive funds
// to one of the passed addresses), and OnRescanProgress (for rescan progress
// updates).
//
// See Rescan to also perform a rescan through current end of the longest chain.
//
// NOTE: This is a btcd extension and requires a websocket connection.
func (c *Client) RescanEndHeight(startHeight int32, addresses []btcutil.Address,
outpoints []*btcwire.OutPoint, endHeight int64) error {
return c.RescanEndHeightAsync(startHeight, addresses, outpoints,
endHeight).Receive()
}

516
rawtransactions.go Normal file
View file

@ -0,0 +1,516 @@
// 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 (
"bytes"
"encoding/hex"
"fmt"
"github.com/conformal/btcjson"
"github.com/conformal/btcutil"
"github.com/conformal/btcwire"
)
// SigHashType enumerates the available signature hashing types that the
// SignRawTransaction function accepts.
type SigHashType string
// Constants used to indicate the signature hash type for SignRawTransaction.
const (
// SigHashAll indicates ALL of the outputs should be signed.
SigHashAll SigHashType = "ALL"
// SigHashNone indicates NONE of the outputs should be signed. This
// can be thought of as specifying the signer does not care where the
// bitcoins go.
SigHashNone SigHashType = "NONE"
// SigHashSingle indicates that a SINGLE output should be signed. This
// can be thought of specifying the signer only cares about where ONE of
// the outputs goes, but not any of the others.
SigHashSingle SigHashType = "SINGLE"
// SigHashAllAnyoneCanPay indicates that signer does not care where the
// other inputs to the transaction come from, so it allows other people
// to add inputs. In addition, it uses the SigHashAll signing method
// for outputs.
SigHashAllAnyoneCanPay SigHashType = "ALL|ANYONECANPAY"
// SigHashNoneAnyoneCanPay indicates that signer does not care where the
// other inputs to the transaction come from, so it allows other people
// to add inputs. In addition, it uses the SigHashNone signing method
// for outputs.
SigHashNoneAnyoneCanPay SigHashType = "NONE|ANYONECANPAY"
// SigHashAllAnyoneCanPay indicates that signer does not care where the
// other inputs to the transaction come from, so it allows other people
// to add inputs. In addition, it uses the SigHashSingle signing method
// for outputs.
SigHashSingleAnyoneCanPay SigHashType = "SINGLE|ANYONECANPAY"
)
// String returns the SighHashType in human-readable form.
func (s SigHashType) String() string {
return string(s)
}
// FutureGetRawTransactionResult is a future promise to deliver the result of a
// GetRawTransactionAsync RPC invocation (or an applicable error).
type FutureGetRawTransactionResult chan *futureResult
// 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)
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)
}
// Decode the serialized transaction hex to raw bytes.
serializedTx, err := hex.DecodeString(txHex)
if err != nil {
return nil, err
}
// Deserialize the transaction and return it.
var msgTx btcwire.MsgTx
if err := msgTx.Deserialize(bytes.NewBuffer(serializedTx)); err != nil {
return nil, err
}
return btcutil.NewTx(&msgTx), nil
}
// GetRawTransactionAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See GetRawTransaction for the blocking version and more details.
func (c *Client) GetRawTransactionAsync(txHash *btcwire.ShaHash) FutureGetRawTransactionResult {
id := c.NextID()
cmd, err := btcjson.NewGetRawTransactionCmd(id, txHash.String(), 0)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetRawTransaction returns a transaction given its hash.
//
// See GetRawTransactionVerbose to obtain additional information about the
// transaction.
func (c *Client) GetRawTransaction(txHash *btcwire.ShaHash) (*btcutil.Tx, error) {
return c.GetRawTransactionAsync(txHash).Receive()
}
// FutureGetRawTransactionVerboseResult is a future promise to deliver the
// result of a GetRawTransactionVerboseAsync RPC invocation (or an applicable
// error).
type FutureGetRawTransactionVerboseResult chan *futureResult
// 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)
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)
}
return result, nil
}
// GetRawTransactionVerboseAsync returns an instance of a type that can be used
// to get the result of the RPC at some future time by invoking the Receive
// function on the returned instance.
//
// See GetRawTransactionVerbose for the blocking version and more details.
func (c *Client) GetRawTransactionVerboseAsync(txHash *btcwire.ShaHash) FutureGetRawTransactionVerboseResult {
id := c.NextID()
cmd, err := btcjson.NewGetRawTransactionCmd(id, txHash.String(), 1)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// GetRawTransactionVerbose returns information about a transaction given
// its hash.
//
// See GetRawTransaction to obtain only the transaction already deserialized.
func (c *Client) GetRawTransactionVerbose(txHash *btcwire.ShaHash) (*btcjson.TxRawResult, error) {
return c.GetRawTransactionVerboseAsync(txHash).Receive()
}
// FutureDecodeRawTransactionResult is a future promise to deliver the result
// of a DecodeRawTransactionAsync RPC invocation (or an applicable error).
type FutureDecodeRawTransactionResult chan *futureResult
// 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)
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)
}
return result, nil
}
// DecodeRawTransactionAsync returns an instance of a type that can be used to
// get the result of the RPC at some future time by invoking the Receive
// function on the returned instance.
//
// See DecodeRawTransaction for the blocking version and more details.
func (c *Client) DecodeRawTransactionAsync(serializedTx []byte) FutureDecodeRawTransactionResult {
id := c.NextID()
txHex := hex.EncodeToString(serializedTx)
cmd, err := btcjson.NewDecodeRawTransactionCmd(id, txHex)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// DecodeRawTransaction returns information about a transaction given its
// serialized bytes.
func (c *Client) DecodeRawTransaction(serializedTx []byte) (*btcjson.TxRawResult, error) {
return c.DecodeRawTransactionAsync(serializedTx).Receive()
}
// FutureCreateRawTransactionResult is a future promise to deliver the result
// of a CreateRawTransactionAsync RPC invocation (or an applicable error).
type FutureCreateRawTransactionResult chan *futureResult
// 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)
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)
}
// Decode the serialized transaction hex to raw bytes.
serializedTx, err := hex.DecodeString(txHex)
if err != nil {
return nil, err
}
// Deserialize the transaction and return it.
var msgTx btcwire.MsgTx
if err := msgTx.Deserialize(bytes.NewBuffer(serializedTx)); err != nil {
return nil, err
}
return &msgTx, nil
}
// CreateRawTransactionAsync returns an instance of a type that can be used to
// get the result of the RPC at some future time by invoking the Receive
// function on the returned instance.
//
// See CreateRawTransaction for the blocking version and more details.
func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput, amounts map[string]int64) FutureCreateRawTransactionResult {
id := c.NextID()
cmd, err := btcjson.NewCreateRawTransactionCmd(id, inputs, amounts)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// CreateRawTransaction returns a new transaction spending the provided inputs
// and sending to the provided addresses.
func (c *Client) CreateRawTransaction(inputs []btcjson.TransactionInput, amounts map[string]int64) (*btcwire.MsgTx, error) {
return c.CreateRawTransactionAsync(inputs, amounts).Receive()
}
// FutureSendRawTransactionResult is a future promise to deliver the result
// of a SendRawTransactionAsync RPC invocation (or an applicable error).
type FutureSendRawTransactionResult chan *futureResult
// 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)
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)
}
return btcwire.NewShaHashFromStr(txHashStr)
}
// SendRawTransactionAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See SendRawTransaction for the blocking version and more details.
func (c *Client) SendRawTransactionAsync(tx *btcwire.MsgTx, allowHighFees bool) FutureSendRawTransactionResult {
// Serialize the transaction and convert to hex string.
buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
if err := tx.Serialize(buf); err != nil {
return newFutureError(err)
}
txHex := hex.EncodeToString(buf.Bytes())
id := c.NextID()
cmd, err := btcjson.NewSendRawTransactionCmd(id, txHex, allowHighFees)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// SendRawTransaction submits the encoded transaction to the server which will
// then relay it to the network.
func (c *Client) SendRawTransaction(tx *btcwire.MsgTx, allowHighFees bool) (*btcwire.ShaHash, error) {
return c.SendRawTransactionAsync(tx, allowHighFees).Receive()
}
// 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
// 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)
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)
}
// Decode the serialized transaction hex to raw bytes.
serializedTx, err := hex.DecodeString(result.Hex)
if err != nil {
return nil, false, err
}
// Deserialize the transaction and return it.
var msgTx btcwire.MsgTx
if err := msgTx.Deserialize(bytes.NewBuffer(serializedTx)); err != nil {
return nil, false, err
}
return &msgTx, result.Complete, nil
}
// SignRawTransactionAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See SignRawTransaction for the blocking version and more details.
func (c *Client) SignRawTransactionAsync(tx *btcwire.MsgTx) FutureSignRawTransactionResult {
// Serialize the transaction and convert to hex string.
buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
if err := tx.Serialize(buf); err != nil {
return newFutureError(err)
}
txHex := hex.EncodeToString(buf.Bytes())
id := c.NextID()
cmd, err := btcjson.NewSignRawTransactionCmd(id, txHex)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// SignRawTransaction signs inputs for the passed transaction and returns the
// signed transaction as well as whether or not all inputs are now signed.
//
// This function assumes the RPC server already knows the input transactions and
// private keys for the passed transaction which needs to be signed and uses the
// default signature hash type. Use one of the SignRawTransaction# variants to
// specify that information if needed.
func (c *Client) SignRawTransaction(tx *btcwire.MsgTx) (*btcwire.MsgTx, bool, error) {
return c.SignRawTransactionAsync(tx).Receive()
}
// SignRawTransaction2Async returns an instance of a type that can be used to
// get the result of the RPC at some future time by invoking the Receive
// function on the returned instance.
//
// See SignRawTransaction2 for the blocking version and more details.
func (c *Client) SignRawTransaction2Async(tx *btcwire.MsgTx, inputs []btcjson.RawTxInput) FutureSignRawTransactionResult {
// Serialize the transaction and convert to hex string.
buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
if err := tx.Serialize(buf); err != nil {
return newFutureError(err)
}
txHex := hex.EncodeToString(buf.Bytes())
id := c.NextID()
cmd, err := btcjson.NewSignRawTransactionCmd(id, txHex, inputs)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// SignRawTransaction2 signs inputs for the passed transaction given the list
// of information about the input transactions needed to perform the signing
// process.
//
// This only input transactions that need to be specified are ones the
// RPC server does not already know. Already known input transactions will be
// merged with the specified transactions.
//
// See SignRawTransaction if the RPC server already knows the input
// transactions.
func (c *Client) SignRawTransaction2(tx *btcwire.MsgTx, inputs []btcjson.RawTxInput) (*btcwire.MsgTx, bool, error) {
return c.SignRawTransaction2Async(tx, inputs).Receive()
}
// SignRawTransaction3Async returns an instance of a type that can be used to
// get the result of the RPC at some future time by invoking the Receive
// function on the returned instance.
//
// See SignRawTransaction3 for the blocking version and more details.
func (c *Client) SignRawTransaction3Async(tx *btcwire.MsgTx,
inputs []btcjson.RawTxInput,
privKeysWIF []string) FutureSignRawTransactionResult {
// Serialize the transaction and convert to hex string.
buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
if err := tx.Serialize(buf); err != nil {
return newFutureError(err)
}
txHex := hex.EncodeToString(buf.Bytes())
id := c.NextID()
cmd, err := btcjson.NewSignRawTransactionCmd(id, txHex, inputs,
privKeysWIF)
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// SignRawTransaction3 signs inputs for the passed transaction given the list
// of information about extra input transactions and a list of private keys
// needed to perform the signing process. The private keys must be in wallet
// import format (WIF).
//
// This only input transactions that need to be specified are ones the
// RPC server does not already know. Already known input transactions will be
// merged with the specified transactions. This means the list of transaction
// inputs can be nil if the RPC server already knows them all.
//
// NOTE: Unlike the merging functionality of the input transactions, ONLY the
// specified private keys will be used, so even if the server already knows some
// of the private keys, they will NOT be used.
//
// See SignRawTransaction if the RPC server already knows the input
// transactions and private keys or SignRawTransaction2 if it already knows the
// private keys.
func (c *Client) SignRawTransaction3(tx *btcwire.MsgTx,
inputs []btcjson.RawTxInput,
privKeysWIF []string) (*btcwire.MsgTx, bool, error) {
return c.SignRawTransaction3Async(tx, inputs, privKeysWIF).Receive()
}
// SignRawTransaction4Async returns an instance of a type that can be used to
// get the result of the RPC at some future time by invoking the Receive
// function on the returned instance.
//
// See SignRawTransaction4 for the blocking version and more details.
func (c *Client) SignRawTransaction4Async(tx *btcwire.MsgTx,
inputs []btcjson.RawTxInput, privKeysWIF []string,
hashType SigHashType) FutureSignRawTransactionResult {
// Serialize the transaction and convert to hex string.
buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
if err := tx.Serialize(buf); err != nil {
return newFutureError(err)
}
txHex := hex.EncodeToString(buf.Bytes())
id := c.NextID()
cmd, err := btcjson.NewSignRawTransactionCmd(id, txHex, inputs,
privKeysWIF, string(hashType))
if err != nil {
return newFutureError(err)
}
return c.sendCmd(cmd)
}
// SignRawTransaction4 signs inputs for the passed transaction using the
// the specified signature hash type given the list of information about extra
// input transactions and a potential list of private keys needed to perform
// the signing process. The private keys, if specified, must be in wallet
// import format (WIF).
//
// The only input transactions that need to be specified are ones the RPC server
// does not already know. This means the list of transaction inputs can be nil
// if the RPC server already knows them all.
//
// NOTE: Unlike the merging functionality of the input transactions, ONLY the
// specified private keys will be used, so even if the server already knows some
// of the private keys, they will NOT be used. The list of private keys can be
// nil in which case any private keys the RPC server knows will be used.
//
// This function should only used if a non-default signature hash type is
// desired. Otherwise, see SignRawTransaction if the RPC server already knows
// the input transactions and private keys, SignRawTransaction2 if it already
// knows the private keys, or SignRawTransaction3 if it does not know both.
func (c *Client) SignRawTransaction4(tx *btcwire.MsgTx,
inputs []btcjson.RawTxInput, privKeysWIF []string,
hashType SigHashType) (*btcwire.MsgTx, bool, error) {
return c.SignRawTransaction4Async(tx, inputs, privKeysWIF,
hashType).Receive()
}

1862
wallet.go Normal file

File diff suppressed because it is too large Load diff