Initial commit.
This commit is contained in:
commit
e3f130ade5
13 changed files with 5609 additions and 0 deletions
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal 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
13
LICENSE
Normal 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
11
README.md
Normal 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
494
chain.go
Normal 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
8
doc.go
Normal 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
291
extensions.go
Normal 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
937
infrastructure.go
Normal 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
74
log.go
Normal 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
361
mining.go
Normal 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
327
net.go
Normal 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
687
notify.go
Normal 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
516
rawtransactions.go
Normal 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()
|
||||||
|
}
|
Loading…
Reference in a new issue