Release v0.3.0

This commit is contained in:
Manu Herrera
2020-11-09 10:05:29 -03:00
parent 4e9aa7a3c5
commit 8107c4478b
1265 changed files with 440488 additions and 107809 deletions

View File

@@ -1149,18 +1149,9 @@ func (b *BlockChain) initChainState() error {
blockIndexBucket := dbTx.Metadata().Bucket(blockIndexBucketName)
// Determine how many blocks will be loaded into the index so we can
// allocate the right amount.
var blockCount int32
cursor := blockIndexBucket.Cursor()
for ok := cursor.First(); ok; ok = cursor.Next() {
blockCount++
}
blockNodes := make([]blockNode, blockCount)
var i int32
var lastNode *blockNode
cursor = blockIndexBucket.Cursor()
cursor := blockIndexBucket.Cursor()
for ok := cursor.First(); ok; ok = cursor.Next() {
header, status, err := deserializeBlockRow(cursor.Value())
if err != nil {
@@ -1193,7 +1184,7 @@ func (b *BlockChain) initChainState() error {
// Initialize the block node for the block, connect it,
// and add it to the block index.
node := &blockNodes[i]
node := new(blockNode)
initBlockNode(node, header, parent)
node.status = status
b.index.addNode(node)

View File

@@ -111,6 +111,22 @@ func (entry *UtxoEntry) Clone() *UtxoEntry {
}
}
// NewUtxoEntry returns a new UtxoEntry built from the arguments.
func NewUtxoEntry(
txOut *wire.TxOut, blockHeight int32, isCoinbase bool) *UtxoEntry {
var cbFlag txoFlags
if isCoinbase {
cbFlag |= tfCoinBase
}
return &UtxoEntry{
amount: txOut.Value,
pkScript: txOut.PkScript,
blockHeight: blockHeight,
packedFlags: cbFlag,
}
}
// UtxoViewpoint represents a view into the set of unspent transaction outputs
// from a specific point of view in the chain. For example, it could be for
// the end of the main chain, some point in the history of the main chain, or

View File

@@ -226,20 +226,24 @@ func (f *fieldVal) SetBytes(b *[32]byte) *fieldVal {
return f
}
// SetByteSlice packs the passed big-endian value into the internal field value
// representation. Only the first 32-bytes are used. As a result, it is up to
// the caller to ensure numbers of the appropriate size are used or the value
// will be truncated.
// SetByteSlice interprets the provided slice as a 256-bit big-endian unsigned
// integer (meaning it is truncated to the first 32 bytes), packs it into the
// internal field value representation, and returns the updated field value.
//
// Note that since passing a slice with more than 32 bytes is truncated, it is
// possible that the truncated value is less than the field prime. It is up to
// the caller to decide whether it needs to provide numbers of the appropriate
// size or if it is acceptable to use this function with the described
// truncation behavior.
//
// The field value is returned to support chaining. This enables syntax like:
// f := new(fieldVal).SetByteSlice(byteSlice)
func (f *fieldVal) SetByteSlice(b []byte) *fieldVal {
var b32 [32]byte
for i := 0; i < len(b); i++ {
if i < 32 {
b32[i+(32-len(b))] = b[i]
}
if len(b) > 32 {
b = b[:32]
}
copy(b32[32-len(b):], b)
return f.SetBytes(&b32)
}

View File

@@ -59,6 +59,23 @@ func NewDebugLevelCmd(levelSpec string) *DebugLevelCmd {
}
}
// GenerateToAddressCmd defines the generatetoaddress JSON-RPC command.
type GenerateToAddressCmd struct {
NumBlocks int64
Address string
MaxTries *int64 `jsonrpcdefault:"1000000"`
}
// NewGenerateToAddressCmd returns a new instance which can be used to issue a
// generatetoaddress JSON-RPC command.
func NewGenerateToAddressCmd(numBlocks int64, address string, maxTries *int64) *GenerateToAddressCmd {
return &GenerateToAddressCmd{
NumBlocks: numBlocks,
Address: address,
MaxTries: maxTries,
}
}
// GenerateCmd defines the generate JSON-RPC command.
type GenerateCmd struct {
NumBlocks uint32
@@ -131,6 +148,7 @@ func init() {
MustRegisterCmd("debuglevel", (*DebugLevelCmd)(nil), flags)
MustRegisterCmd("node", (*NodeCmd)(nil), flags)
MustRegisterCmd("generate", (*GenerateCmd)(nil), flags)
MustRegisterCmd("generatetoaddress", (*GenerateToAddressCmd)(nil), flags)
MustRegisterCmd("getbestblock", (*GetBestBlockCmd)(nil), flags)
MustRegisterCmd("getcurrentnet", (*GetCurrentNetCmd)(nil), flags)
MustRegisterCmd("getheaders", (*GetHeadersCmd)(nil), flags)

View File

@@ -8,6 +8,7 @@
package btcjson
import (
"encoding/hex"
"encoding/json"
"fmt"
@@ -63,10 +64,15 @@ type CreateRawTransactionCmd struct {
// NewCreateRawTransactionCmd returns a new instance which can be used to issue
// a createrawtransaction JSON-RPC command.
//
// Amounts are in BTC.
// Amounts are in BTC. Passing in nil and the empty slice as inputs is equivalent,
// both gets interpreted as the empty slice.
func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]float64,
lockTime *int64) *CreateRawTransactionCmd {
// to make sure we're serializing this to the empty list and not null, we
// explicitly initialize the list
if inputs == nil {
inputs = []TransactionInput{}
}
return &CreateRawTransactionCmd{
Inputs: inputs,
Amounts: amounts,
@@ -74,6 +80,37 @@ func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]fl
}
}
// FundRawTransactionOpts are the different options that can be passed to rawtransaction
type FundRawTransactionOpts struct {
ChangeAddress *string `json:"changeAddress,omitempty"`
ChangePosition *int `json:"changePosition,omitempty"`
ChangeType *string `json:"change_type,omitempty"`
IncludeWatching *bool `json:"includeWatching,omitempty"`
LockUnspents *bool `json:"lockUnspents,omitempty"`
FeeRate *float64 `json:"feeRate,omitempty"` // BTC/kB
SubtractFeeFromOutputs []int `json:"subtractFeeFromOutputs,omitempty"`
Replaceable *bool `json:"replaceable,omitempty"`
ConfTarget *int `json:"conf_target,omitempty"`
EstimateMode *EstimateSmartFeeMode `json:"estimate_mode,omitempty"`
}
// FundRawTransactionCmd defines the fundrawtransaction JSON-RPC command
type FundRawTransactionCmd struct {
HexTx string
Options FundRawTransactionOpts
IsWitness *bool
}
// NewFundRawTransactionCmd returns a new instance which can be used to issue
// a fundrawtransaction JSON-RPC command
func NewFundRawTransactionCmd(serializedTx []byte, opts FundRawTransactionOpts, isWitness *bool) *FundRawTransactionCmd {
return &FundRawTransactionCmd{
HexTx: hex.EncodeToString(serializedTx),
Options: opts,
IsWitness: isWitness,
}
}
// DecodeRawTransactionCmd defines the decoderawtransaction JSON-RPC command.
type DecodeRawTransactionCmd struct {
HexTx string
@@ -130,8 +167,7 @@ func NewGetBestBlockHashCmd() *GetBestBlockHashCmd {
// GetBlockCmd defines the getblock JSON-RPC command.
type GetBlockCmd struct {
Hash string
Verbose *bool `jsonrpcdefault:"true"`
VerboseTx *bool `jsonrpcdefault:"false"`
Verbosity *int `jsonrpcdefault:"1"`
}
// NewGetBlockCmd returns a new instance which can be used to issue a getblock
@@ -139,11 +175,10 @@ type GetBlockCmd struct {
//
// The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value.
func NewGetBlockCmd(hash string, verbose, verboseTx *bool) *GetBlockCmd {
func NewGetBlockCmd(hash string, verbosity *int) *GetBlockCmd {
return &GetBlockCmd{
Hash: hash,
Verbose: verbose,
VerboseTx: verboseTx,
Verbosity: verbosity,
}
}
@@ -193,6 +228,50 @@ func NewGetBlockHeaderCmd(hash string, verbose *bool) *GetBlockHeaderCmd {
}
}
// HashOrHeight defines a type that can be used as hash_or_height value in JSON-RPC commands.
type HashOrHeight struct {
Value interface{}
}
// MarshalJSON implements the json.Marshaler interface
func (h HashOrHeight) MarshalJSON() ([]byte, error) {
return json.Marshal(h.Value)
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (h *HashOrHeight) UnmarshalJSON(data []byte) error {
var unmarshalled interface{}
if err := json.Unmarshal(data, &unmarshalled); err != nil {
return err
}
switch v := unmarshalled.(type) {
case float64:
h.Value = int(v)
case string:
h.Value = v
default:
return fmt.Errorf("invalid hash_or_height value: %v", unmarshalled)
}
return nil
}
// GetBlockStatsCmd defines the getblockstats JSON-RPC command.
type GetBlockStatsCmd struct {
HashOrHeight HashOrHeight
Stats *[]string
}
// NewGetBlockStatsCmd returns a new instance which can be used to issue a
// getblockstats JSON-RPC command. Either height or hash must be specified.
func NewGetBlockStatsCmd(hashOrHeight HashOrHeight, stats *[]string) *GetBlockStatsCmd {
return &GetBlockStatsCmd{
HashOrHeight: hashOrHeight,
Stats: stats,
}
}
// TemplateRequest is a request object as defined in BIP22
// (https://en.bitcoin.it/wiki/BIP_0022), it is optionally provided as an
// pointer argument to GetBlockTemplateCmd.
@@ -321,6 +400,24 @@ func NewGetChainTipsCmd() *GetChainTipsCmd {
return &GetChainTipsCmd{}
}
// GetChainTxStatsCmd defines the getchaintxstats JSON-RPC command.
type GetChainTxStatsCmd struct {
NBlocks *int32
BlockHash *string
}
// NewGetChainTxStatsCmd returns a new instance which can be used to issue a
// getchaintxstats JSON-RPC command.
//
// The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value.
func NewGetChainTxStatsCmd(nBlocks *int32, blockHash *string) *GetChainTxStatsCmd {
return &GetChainTxStatsCmd{
NBlocks: nBlocks,
BlockHash: blockHash,
}
}
// GetConnectionCountCmd defines the getconnectioncount JSON-RPC command.
type GetConnectionCountCmd struct{}
@@ -791,6 +888,7 @@ func init() {
MustRegisterCmd("addnode", (*AddNodeCmd)(nil), flags)
MustRegisterCmd("createrawtransaction", (*CreateRawTransactionCmd)(nil), flags)
MustRegisterCmd("fundrawtransaction", (*FundRawTransactionCmd)(nil), flags)
MustRegisterCmd("decoderawtransaction", (*DecodeRawTransactionCmd)(nil), flags)
MustRegisterCmd("decodescript", (*DecodeScriptCmd)(nil), flags)
MustRegisterCmd("getaddednodeinfo", (*GetAddedNodeInfoCmd)(nil), flags)
@@ -800,10 +898,12 @@ func init() {
MustRegisterCmd("getblockcount", (*GetBlockCountCmd)(nil), flags)
MustRegisterCmd("getblockhash", (*GetBlockHashCmd)(nil), flags)
MustRegisterCmd("getblockheader", (*GetBlockHeaderCmd)(nil), flags)
MustRegisterCmd("getblockstats", (*GetBlockStatsCmd)(nil), flags)
MustRegisterCmd("getblocktemplate", (*GetBlockTemplateCmd)(nil), flags)
MustRegisterCmd("getcfilter", (*GetCFilterCmd)(nil), flags)
MustRegisterCmd("getcfilterheader", (*GetCFilterHeaderCmd)(nil), flags)
MustRegisterCmd("getchaintips", (*GetChainTipsCmd)(nil), flags)
MustRegisterCmd("getchaintxstats", (*GetChainTxStatsCmd)(nil), flags)
MustRegisterCmd("getconnectioncount", (*GetConnectionCountCmd)(nil), flags)
MustRegisterCmd("getdifficulty", (*GetDifficultyCmd)(nil), flags)
MustRegisterCmd("getgenerate", (*GetGenerateCmd)(nil), flags)

View File

@@ -4,7 +4,14 @@
package btcjson
import "encoding/json"
import (
"bytes"
"encoding/hex"
"encoding/json"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
// GetBlockHeaderVerboseResult models the data from the getblockheader command when
// the verbose flag is set. When the verbose flag is not set, getblockheader
@@ -24,9 +31,44 @@ type GetBlockHeaderVerboseResult struct {
NextHash string `json:"nextblockhash,omitempty"`
}
// GetBlockStatsResult models the data from the getblockstats command.
type GetBlockStatsResult struct {
AverageFee int64 `json:"avgfee"`
AverageFeeRate int64 `json:"avgfeerate"`
AverageTxSize int64 `json:"avgtxsize"`
FeeratePercentiles []int64 `json:"feerate_percentiles"`
Hash string `json:"blockhash"`
Height int64 `json:"height"`
Ins int64 `json:"ins"`
MaxFee int64 `json:"maxfee"`
MaxFeeRate int64 `json:"maxfeerate"`
MaxTxSize int64 `json:"maxtxsize"`
MedianFee int64 `json:"medianfee"`
MedianTime int64 `json:"mediantime"`
MedianTxSize int64 `json:"mediantxsize"`
MinFee int64 `json:"minfee"`
MinFeeRate int64 `json:"minfeerate"`
MinTxSize int64 `json:"mintxsize"`
Outs int64 `json:"outs"`
SegWitTotalSize int64 `json:"swtotal_size"`
SegWitTotalWeight int64 `json:"swtotal_weight"`
SegWitTxs int64 `json:"swtxs"`
Subsidy int64 `json:"subsidy"`
Time int64 `json:"time"`
TotalOut int64 `json:"total_out"`
TotalSize int64 `json:"total_size"`
TotalWeight int64 `json:"total_weight"`
Txs int64 `json:"txs"`
UTXOIncrease int64 `json:"utxo_increase"`
UTXOSizeIncrease int64 `json:"utxo_size_inc"`
}
// GetBlockVerboseResult models the data from the getblock command when the
// verbose flag is set. When the verbose flag is not set, getblock returns a
// hex-encoded string.
// verbose flag is set to 1. When the verbose flag is set to 0, getblock returns a
// hex-encoded string. When the verbose flag is set to 1, getblock returns an object
// whose tx field is an array of transaction hashes. When the verbose flag is set to 2,
// getblock returns an object whose tx field is an array of raw transactions.
// Use GetBlockVerboseTxResult to unmarshal data received from passing verbose=2 to getblock.
type GetBlockVerboseResult struct {
Hash string `json:"hash"`
Confirmations int64 `json:"confirmations"`
@@ -38,7 +80,7 @@ type GetBlockVerboseResult struct {
VersionHex string `json:"versionHex"`
MerkleRoot string `json:"merkleroot"`
Tx []string `json:"tx,omitempty"`
RawTx []TxRawResult `json:"rawtx,omitempty"`
RawTx []TxRawResult `json:"rawtx,omitempty"` // Note: this field is always empty when verbose != 2.
Time int64 `json:"time"`
Nonce uint32 `json:"nonce"`
Bits string `json:"bits"`
@@ -47,6 +89,43 @@ type GetBlockVerboseResult struct {
NextHash string `json:"nextblockhash,omitempty"`
}
// GetBlockVerboseTxResult models the data from the getblock command when the
// verbose flag is set to 2. When the verbose flag is set to 0, getblock returns a
// hex-encoded string. When the verbose flag is set to 1, getblock returns an object
// whose tx field is an array of transaction hashes. When the verbose flag is set to 2,
// getblock returns an object whose tx field is an array of raw transactions.
// Use GetBlockVerboseResult to unmarshal data received from passing verbose=1 to getblock.
type GetBlockVerboseTxResult struct {
Hash string `json:"hash"`
Confirmations int64 `json:"confirmations"`
StrippedSize int32 `json:"strippedsize"`
Size int32 `json:"size"`
Weight int32 `json:"weight"`
Height int64 `json:"height"`
Version int32 `json:"version"`
VersionHex string `json:"versionHex"`
MerkleRoot string `json:"merkleroot"`
Tx []TxRawResult `json:"tx,omitempty"`
Time int64 `json:"time"`
Nonce uint32 `json:"nonce"`
Bits string `json:"bits"`
Difficulty float64 `json:"difficulty"`
PreviousHash string `json:"previousblockhash"`
NextHash string `json:"nextblockhash,omitempty"`
}
// GetChainTxStatsResult models the data from the getchaintxstats command.
type GetChainTxStatsResult struct {
Time int64 `json:"time"`
TxCount int64 `json:"txcount"`
WindowFinalBlockHash string `json:"window_final_block_hash"`
WindowFinalBlockHeight int32 `json:"window_final_block_height"`
WindowBlockCount int32 `json:"window_block_count"`
WindowTxCount int32 `json:"window_tx_count"`
WindowInterval int32 `json:"window_interval"`
TxRate float64 `json:"txrate"`
}
// CreateMultiSigResult models the data returned from the createmultisig
// command.
type CreateMultiSigResult struct {
@@ -206,23 +285,35 @@ type GetBlockTemplateResult struct {
RejectReasion string `json:"reject-reason,omitempty"`
}
// GetMempoolEntryResult models the data returned from the getmempoolentry's
// fee field
type MempoolFees struct {
Base float64 `json:"base"`
Modified float64 `json:"modified"`
Ancestor float64 `json:"ancestor"`
Descendant float64 `json:"descendant"`
}
// GetMempoolEntryResult models the data returned from the getmempoolentry
// command.
type GetMempoolEntryResult struct {
Size int32 `json:"size"`
Fee float64 `json:"fee"`
ModifiedFee float64 `json:"modifiedfee"`
Time int64 `json:"time"`
Height int64 `json:"height"`
StartingPriority float64 `json:"startingpriority"`
CurrentPriority float64 `json:"currentpriority"`
DescendantCount int64 `json:"descendantcount"`
DescendantSize int64 `json:"descendantsize"`
DescendantFees float64 `json:"descendantfees"`
AncestorCount int64 `json:"ancestorcount"`
AncestorSize int64 `json:"ancestorsize"`
AncestorFees float64 `json:"ancestorfees"`
Depends []string `json:"depends"`
VSize int32 `json:"vsize"`
Size int32 `json:"size"`
Weight int64 `json:"weight"`
Fee float64 `json:"fee"`
ModifiedFee float64 `json:"modifiedfee"`
Time int64 `json:"time"`
Height int64 `json:"height"`
DescendantCount int64 `json:"descendantcount"`
DescendantSize int64 `json:"descendantsize"`
DescendantFees float64 `json:"descendantfees"`
AncestorCount int64 `json:"ancestorcount"`
AncestorSize int64 `json:"ancestorsize"`
AncestorFees float64 `json:"ancestorfees"`
WTxId string `json:"wtxid"`
Fees MempoolFees `json:"fees"`
Depends []string `json:"depends"`
}
// GetMempoolInfoResult models the data returned from the getmempoolinfo
@@ -584,3 +675,58 @@ type ValidateAddressChainResult struct {
IsValid bool `json:"isvalid"`
Address string `json:"address,omitempty"`
}
// EstimateSmartFeeResult models the data returned buy the chain server
// estimatesmartfee command
type EstimateSmartFeeResult struct {
FeeRate *float64 `json:"feerate,omitempty"`
Errors []string `json:"errors,omitempty"`
Blocks int64 `json:"blocks"`
}
var _ json.Unmarshaler = &FundRawTransactionResult{}
type rawFundRawTransactionResult struct {
Transaction string `json:"hex"`
Fee float64 `json:"fee"`
ChangePosition int `json:"changepos"`
}
// FundRawTransactionResult is the result of the fundrawtransaction JSON-RPC call
type FundRawTransactionResult struct {
Transaction *wire.MsgTx
Fee btcutil.Amount
ChangePosition int // the position of the added change output, or -1
}
// UnmarshalJSON unmarshals the result of the fundrawtransaction JSON-RPC call
func (f *FundRawTransactionResult) UnmarshalJSON(data []byte) error {
var rawRes rawFundRawTransactionResult
if err := json.Unmarshal(data, &rawRes); err != nil {
return err
}
txBytes, err := hex.DecodeString(rawRes.Transaction)
if err != nil {
return err
}
var msgTx wire.MsgTx
witnessErr := msgTx.Deserialize(bytes.NewReader(txBytes))
if witnessErr != nil {
legacyErr := msgTx.DeserializeNoWitness(bytes.NewReader(txBytes))
if legacyErr != nil {
return legacyErr
}
}
fee, err := btcutil.NewAmount(rawRes.Fee)
if err != nil {
return err
}
f.Transaction = &msgTx
f.Fee = fee
f.ChangePosition = rawRes.ChangePosition
return nil
}

View File

@@ -80,7 +80,7 @@ func NewStopNotifyNewTransactionsCmd() *StopNotifyNewTransactionsCmd {
// NotifyReceivedCmd defines the notifyreceived JSON-RPC command.
//
// NOTE: Deprecated. Use LoadTxFilterCmd instead.
// Deprecated: Use LoadTxFilterCmd instead.
type NotifyReceivedCmd struct {
Addresses []string
}
@@ -88,7 +88,7 @@ type NotifyReceivedCmd struct {
// NewNotifyReceivedCmd returns a new instance which can be used to issue a
// notifyreceived JSON-RPC command.
//
// NOTE: Deprecated. Use NewLoadTxFilterCmd instead.
// Deprecated: Use NewLoadTxFilterCmd instead.
func NewNotifyReceivedCmd(addresses []string) *NotifyReceivedCmd {
return &NotifyReceivedCmd{
Addresses: addresses,
@@ -128,7 +128,7 @@ func NewLoadTxFilterCmd(reload bool, addresses []string, outPoints []OutPoint) *
// NotifySpentCmd defines the notifyspent JSON-RPC command.
//
// NOTE: Deprecated. Use LoadTxFilterCmd instead.
// Deprecated: Use LoadTxFilterCmd instead.
type NotifySpentCmd struct {
OutPoints []OutPoint
}
@@ -136,7 +136,7 @@ type NotifySpentCmd struct {
// NewNotifySpentCmd returns a new instance which can be used to issue a
// notifyspent JSON-RPC command.
//
// NOTE: Deprecated. Use NewLoadTxFilterCmd instead.
// Deprecated: Use NewLoadTxFilterCmd instead.
func NewNotifySpentCmd(outPoints []OutPoint) *NotifySpentCmd {
return &NotifySpentCmd{
OutPoints: outPoints,
@@ -145,7 +145,7 @@ func NewNotifySpentCmd(outPoints []OutPoint) *NotifySpentCmd {
// StopNotifyReceivedCmd defines the stopnotifyreceived JSON-RPC command.
//
// NOTE: Deprecated. Use LoadTxFilterCmd instead.
// Deprecated: Use LoadTxFilterCmd instead.
type StopNotifyReceivedCmd struct {
Addresses []string
}
@@ -153,7 +153,7 @@ type StopNotifyReceivedCmd struct {
// NewStopNotifyReceivedCmd returns a new instance which can be used to issue a
// stopnotifyreceived JSON-RPC command.
//
// NOTE: Deprecated. Use NewLoadTxFilterCmd instead.
// Deprecated: Use NewLoadTxFilterCmd instead.
func NewStopNotifyReceivedCmd(addresses []string) *StopNotifyReceivedCmd {
return &StopNotifyReceivedCmd{
Addresses: addresses,
@@ -162,7 +162,7 @@ func NewStopNotifyReceivedCmd(addresses []string) *StopNotifyReceivedCmd {
// StopNotifySpentCmd defines the stopnotifyspent JSON-RPC command.
//
// NOTE: Deprecated. Use LoadTxFilterCmd instead.
// Deprecated: Use LoadTxFilterCmd instead.
type StopNotifySpentCmd struct {
OutPoints []OutPoint
}
@@ -170,7 +170,7 @@ type StopNotifySpentCmd struct {
// NewStopNotifySpentCmd returns a new instance which can be used to issue a
// stopnotifyspent JSON-RPC command.
//
// NOTE: Deprecated. Use NewLoadTxFilterCmd instead.
// Deprecated: Use NewLoadTxFilterCmd instead.
func NewStopNotifySpentCmd(outPoints []OutPoint) *StopNotifySpentCmd {
return &StopNotifySpentCmd{
OutPoints: outPoints,
@@ -179,7 +179,7 @@ func NewStopNotifySpentCmd(outPoints []OutPoint) *StopNotifySpentCmd {
// RescanCmd defines the rescan JSON-RPC command.
//
// NOTE: Deprecated. Use RescanBlocksCmd instead.
// Deprecated: Use RescanBlocksCmd instead.
type RescanCmd struct {
BeginBlock string
Addresses []string
@@ -193,7 +193,7 @@ type RescanCmd struct {
// The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value.
//
// NOTE: Deprecated. Use NewRescanBlocksCmd instead.
// Deprecated: Use NewRescanBlocksCmd instead.
func NewRescanCmd(beginBlock string, addresses []string, outPoints []OutPoint, endBlock *string) *RescanCmd {
return &RescanCmd{
BeginBlock: beginBlock,

View File

@@ -12,14 +12,14 @@ const (
// BlockConnectedNtfnMethod is the legacy, deprecated method used for
// notifications from the chain server that a block has been connected.
//
// NOTE: Deprecated. Use FilteredBlockConnectedNtfnMethod instead.
// Deprecated: Use FilteredBlockConnectedNtfnMethod instead.
BlockConnectedNtfnMethod = "blockconnected"
// BlockDisconnectedNtfnMethod is the legacy, deprecated method used for
// notifications from the chain server that a block has been
// disconnected.
//
// NOTE: Deprecated. Use FilteredBlockDisconnectedNtfnMethod instead.
// Deprecated: Use FilteredBlockDisconnectedNtfnMethod instead.
BlockDisconnectedNtfnMethod = "blockdisconnected"
// FilteredBlockConnectedNtfnMethod is the new method used for
@@ -35,7 +35,7 @@ const (
// notifications from the chain server that a transaction which pays to
// a registered address has been processed.
//
// NOTE: Deprecated. Use RelevantTxAcceptedNtfnMethod and
// Deprecated: Use RelevantTxAcceptedNtfnMethod and
// FilteredBlockConnectedNtfnMethod instead.
RecvTxNtfnMethod = "recvtx"
@@ -43,7 +43,7 @@ const (
// notifications from the chain server that a transaction which spends a
// registered outpoint has been processed.
//
// NOTE: Deprecated. Use RelevantTxAcceptedNtfnMethod and
// Deprecated: Use RelevantTxAcceptedNtfnMethod and
// FilteredBlockConnectedNtfnMethod instead.
RedeemingTxNtfnMethod = "redeemingtx"
@@ -51,14 +51,14 @@ const (
// notifications from the chain server that a legacy, deprecated rescan
// operation has finished.
//
// NOTE: Deprecated. Not used with rescanblocks command.
// Deprecated: Not used with rescanblocks command.
RescanFinishedNtfnMethod = "rescanfinished"
// RescanProgressNtfnMethod is the legacy, deprecated method used for
// notifications from the chain server that a legacy, deprecated rescan
// operation this is underway has made progress.
//
// NOTE: Deprecated. Not used with rescanblocks command.
// Deprecated: Not used with rescanblocks command.
RescanProgressNtfnMethod = "rescanprogress"
// TxAcceptedNtfnMethod is the method used for notifications from the
@@ -79,7 +79,7 @@ const (
// BlockConnectedNtfn defines the blockconnected JSON-RPC notification.
//
// NOTE: Deprecated. Use FilteredBlockConnectedNtfn instead.
// Deprecated: Use FilteredBlockConnectedNtfn instead.
type BlockConnectedNtfn struct {
Hash string
Height int32
@@ -89,7 +89,7 @@ type BlockConnectedNtfn struct {
// NewBlockConnectedNtfn returns a new instance which can be used to issue a
// blockconnected JSON-RPC notification.
//
// NOTE: Deprecated. Use NewFilteredBlockConnectedNtfn instead.
// Deprecated: Use NewFilteredBlockConnectedNtfn instead.
func NewBlockConnectedNtfn(hash string, height int32, time int64) *BlockConnectedNtfn {
return &BlockConnectedNtfn{
Hash: hash,
@@ -100,7 +100,7 @@ func NewBlockConnectedNtfn(hash string, height int32, time int64) *BlockConnecte
// BlockDisconnectedNtfn defines the blockdisconnected JSON-RPC notification.
//
// NOTE: Deprecated. Use FilteredBlockDisconnectedNtfn instead.
// Deprecated: Use FilteredBlockDisconnectedNtfn instead.
type BlockDisconnectedNtfn struct {
Hash string
Height int32
@@ -110,7 +110,7 @@ type BlockDisconnectedNtfn struct {
// NewBlockDisconnectedNtfn returns a new instance which can be used to issue a
// blockdisconnected JSON-RPC notification.
//
// NOTE: Deprecated. Use NewFilteredBlockDisconnectedNtfn instead.
// Deprecated: Use NewFilteredBlockDisconnectedNtfn instead.
func NewBlockDisconnectedNtfn(hash string, height int32, time int64) *BlockDisconnectedNtfn {
return &BlockDisconnectedNtfn{
Hash: hash,
@@ -163,7 +163,7 @@ type BlockDetails struct {
// RecvTxNtfn defines the recvtx JSON-RPC notification.
//
// NOTE: Deprecated. Use RelevantTxAcceptedNtfn and FilteredBlockConnectedNtfn
// Deprecated: Use RelevantTxAcceptedNtfn and FilteredBlockConnectedNtfn
// instead.
type RecvTxNtfn struct {
HexTx string
@@ -173,7 +173,7 @@ type RecvTxNtfn struct {
// NewRecvTxNtfn returns a new instance which can be used to issue a recvtx
// JSON-RPC notification.
//
// NOTE: Deprecated. Use NewRelevantTxAcceptedNtfn and
// Deprecated: Use NewRelevantTxAcceptedNtfn and
// NewFilteredBlockConnectedNtfn instead.
func NewRecvTxNtfn(hexTx string, block *BlockDetails) *RecvTxNtfn {
return &RecvTxNtfn{
@@ -184,7 +184,7 @@ func NewRecvTxNtfn(hexTx string, block *BlockDetails) *RecvTxNtfn {
// RedeemingTxNtfn defines the redeemingtx JSON-RPC notification.
//
// NOTE: Deprecated. Use RelevantTxAcceptedNtfn and FilteredBlockConnectedNtfn
// Deprecated: Use RelevantTxAcceptedNtfn and FilteredBlockConnectedNtfn
// instead.
type RedeemingTxNtfn struct {
HexTx string
@@ -194,7 +194,7 @@ type RedeemingTxNtfn struct {
// NewRedeemingTxNtfn returns a new instance which can be used to issue a
// redeemingtx JSON-RPC notification.
//
// NOTE: Deprecated. Use NewRelevantTxAcceptedNtfn and
// Deprecated: Use NewRelevantTxAcceptedNtfn and
// NewFilteredBlockConnectedNtfn instead.
func NewRedeemingTxNtfn(hexTx string, block *BlockDetails) *RedeemingTxNtfn {
return &RedeemingTxNtfn{
@@ -205,7 +205,7 @@ func NewRedeemingTxNtfn(hexTx string, block *BlockDetails) *RedeemingTxNtfn {
// RescanFinishedNtfn defines the rescanfinished JSON-RPC notification.
//
// NOTE: Deprecated. Not used with rescanblocks command.
// Deprecated: Not used with rescanblocks command.
type RescanFinishedNtfn struct {
Hash string
Height int32
@@ -215,7 +215,7 @@ type RescanFinishedNtfn struct {
// NewRescanFinishedNtfn returns a new instance which can be used to issue a
// rescanfinished JSON-RPC notification.
//
// NOTE: Deprecated. Not used with rescanblocks command.
// Deprecated: Not used with rescanblocks command.
func NewRescanFinishedNtfn(hash string, height int32, time int64) *RescanFinishedNtfn {
return &RescanFinishedNtfn{
Hash: hash,
@@ -226,7 +226,7 @@ func NewRescanFinishedNtfn(hash string, height int32, time int64) *RescanFinishe
// RescanProgressNtfn defines the rescanprogress JSON-RPC notification.
//
// NOTE: Deprecated. Not used with rescanblocks command.
// Deprecated: Not used with rescanblocks command.
type RescanProgressNtfn struct {
Hash string
Height int32
@@ -236,7 +236,7 @@ type RescanProgressNtfn struct {
// NewRescanProgressNtfn returns a new instance which can be used to issue a
// rescanprogress JSON-RPC notification.
//
// NOTE: Deprecated. Not used with rescanblocks command.
// Deprecated: Not used with rescanblocks command.
func NewRescanProgressNtfn(hash string, height int32, time int64) *RescanProgressNtfn {
return &RescanProgressNtfn{
Hash: hash,

View File

@@ -22,10 +22,10 @@ type RPCError struct {
Message string `json:"message,omitempty"`
}
// Guarantee RPCError satisifies the builtin error interface.
// Guarantee RPCError satisfies the builtin error interface.
var _, _ error = RPCError{}, (*RPCError)(nil)
// Error returns a string describing the RPC error. This satisifies the
// Error returns a string describing the RPC error. This satisfies the
// builtin error interface.
func (e RPCError) Error() string {
return fmt.Sprintf("%d: %s", e.Code, e.Message)

View File

@@ -39,6 +39,7 @@ const (
ErrRPCDatabase RPCErrorCode = -20
ErrRPCDeserialization RPCErrorCode = -22
ErrRPCVerify RPCErrorCode = -25
ErrRPCInWarmup RPCErrorCode = -28
)
// Peer-to-peer client errors.

View File

@@ -287,6 +287,6 @@ func RegisteredCmdMethods() []string {
methods = append(methods, k)
}
sort.Sort(sort.StringSlice(methods))
sort.Strings(methods)
return methods
}

View File

@@ -81,6 +81,30 @@ func NewEncryptWalletCmd(passphrase string) *EncryptWalletCmd {
}
}
// EstimateSmartFeeMode defines the different fee estimation modes available
// for the estimatesmartfee JSON-RPC command.
type EstimateSmartFeeMode string
var (
EstimateModeUnset EstimateSmartFeeMode = "UNSET"
EstimateModeEconomical EstimateSmartFeeMode = "ECONOMICAL"
EstimateModeConservative EstimateSmartFeeMode = "CONSERVATIVE"
)
// EstimateSmartFeeCmd defines the estimatesmartfee JSON-RPC command.
type EstimateSmartFeeCmd struct {
ConfTarget int64
EstimateMode *EstimateSmartFeeMode `jsonrpcdefault:"\"CONSERVATIVE\""`
}
// NewEstimateSmartFeeCmd returns a new instance which can be used to issue a
// estimatesmartfee JSON-RPC command.
func NewEstimateSmartFeeCmd(confTarget int64, mode *EstimateSmartFeeMode) *EstimateSmartFeeCmd {
return &EstimateSmartFeeCmd{
ConfTarget: confTarget, EstimateMode: mode,
}
}
// EstimateFeeCmd defines the estimatefee JSON-RPC command.
type EstimateFeeCmd struct {
NumBlocks int64
@@ -164,6 +188,15 @@ func NewGetBalanceCmd(account *string, minConf *int) *GetBalanceCmd {
}
}
// GetBalancesCmd defines the getbalances JSON-RPC command.
type GetBalancesCmd struct{}
// NewGetBalancesCmd returns a new instance which can be used to issue a
// getbalances JSON-RPC command.
func NewGetBalancesCmd() *GetBalancesCmd {
return &GetBalancesCmd{}
}
// GetNewAddressCmd defines the getnewaddress JSON-RPC command.
type GetNewAddressCmd struct {
Account *string
@@ -662,12 +695,14 @@ func init() {
MustRegisterCmd("createmultisig", (*CreateMultisigCmd)(nil), flags)
MustRegisterCmd("dumpprivkey", (*DumpPrivKeyCmd)(nil), flags)
MustRegisterCmd("encryptwallet", (*EncryptWalletCmd)(nil), flags)
MustRegisterCmd("estimatesmartfee", (*EstimateSmartFeeCmd)(nil), flags)
MustRegisterCmd("estimatefee", (*EstimateFeeCmd)(nil), flags)
MustRegisterCmd("estimatepriority", (*EstimatePriorityCmd)(nil), flags)
MustRegisterCmd("getaccount", (*GetAccountCmd)(nil), flags)
MustRegisterCmd("getaccountaddress", (*GetAccountAddressCmd)(nil), flags)
MustRegisterCmd("getaddressesbyaccount", (*GetAddressesByAccountCmd)(nil), flags)
MustRegisterCmd("getbalance", (*GetBalanceCmd)(nil), flags)
MustRegisterCmd("getbalances", (*GetBalancesCmd)(nil), flags)
MustRegisterCmd("getnewaddress", (*GetNewAddressCmd)(nil), flags)
MustRegisterCmd("getrawchangeaddress", (*GetRawChangeAddressCmd)(nil), flags)
MustRegisterCmd("getreceivedbyaccount", (*GetReceivedByAccountCmd)(nil), flags)

View File

@@ -159,3 +159,17 @@ type GetBestBlockResult struct {
Hash string `json:"hash"`
Height int32 `json:"height"`
}
// BalanceDetailsResult models the details data from the `getbalances` command.
type BalanceDetailsResult struct {
Trusted float64 `json:"trusted"`
UntrustedPending float64 `json:"untrusted_pending"`
Immature float64 `json:"immature"`
Used *float64 `json:"used"`
}
// GetBalancesResult models the data returned from the getbalances command.
type GetBalancesResult struct {
Mine BalanceDetailsResult `json:"mine"`
WatchOnly *BalanceDetailsResult `json:"watchonly"`
}

View File

@@ -71,7 +71,7 @@ type DynamicBanScore struct {
func (s *DynamicBanScore) String() string {
s.mtx.Lock()
r := fmt.Sprintf("persistent %v + transient %v at %v = %v as of now",
s.persistent, s.transient, s.lastUnix, s.Int())
s.persistent, s.transient, s.lastUnix, s.int(time.Now()))
s.mtx.Unlock()
return r
}

View File

@@ -0,0 +1,30 @@
rpctest
=======
[![Build Status](http://img.shields.io/travis/btcsuite/btcd.svg)](https://travis-ci.org/btcsuite/btcd)
[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/btcsuite/btcd/integration/rpctest)
Package rpctest provides a btcd-specific RPC testing harness crafting and
executing integration tests by driving a `btcd` instance via the `RPC`
interface. Each instance of an active harness comes equipped with a simple
in-memory HD wallet capable of properly syncing to the generated chain,
creating new addresses, and crafting fully signed transactions paying to an
arbitrary set of outputs.
This package was designed specifically to act as an RPC testing harness for
`btcd`. However, the constructs presented are general enough to be adapted to
any project wishing to programmatically drive a `btcd` instance of its
systems/integration tests.
## Installation and Updating
```bash
$ go get -u github.com/btcsuite/btcd/integration/rpctest
```
## License
Package rpctest is licensed under the [copyfree](http://copyfree.org) ISC
License.

View File

@@ -0,0 +1,207 @@
// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package rpctest
import (
"errors"
"math"
"math/big"
"runtime"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
// solveBlock attempts to find a nonce which makes the passed block header hash
// to a value less than the target difficulty. When a successful solution is
// found true is returned and the nonce field of the passed header is updated
// with the solution. False is returned if no solution exists.
func solveBlock(header *wire.BlockHeader, targetDifficulty *big.Int) bool {
// sbResult is used by the solver goroutines to send results.
type sbResult struct {
found bool
nonce uint32
}
// solver accepts a block header and a nonce range to test. It is
// intended to be run as a goroutine.
quit := make(chan bool)
results := make(chan sbResult)
solver := func(hdr wire.BlockHeader, startNonce, stopNonce uint32) {
// We need to modify the nonce field of the header, so make sure
// we work with a copy of the original header.
for i := startNonce; i >= startNonce && i <= stopNonce; i++ {
select {
case <-quit:
return
default:
hdr.Nonce = i
hash := hdr.BlockHash()
if blockchain.HashToBig(&hash).Cmp(targetDifficulty) <= 0 {
select {
case results <- sbResult{true, i}:
return
case <-quit:
return
}
}
}
}
select {
case results <- sbResult{false, 0}:
case <-quit:
return
}
}
startNonce := uint32(0)
stopNonce := uint32(math.MaxUint32)
numCores := uint32(runtime.NumCPU())
noncesPerCore := (stopNonce - startNonce) / numCores
for i := uint32(0); i < numCores; i++ {
rangeStart := startNonce + (noncesPerCore * i)
rangeStop := startNonce + (noncesPerCore * (i + 1)) - 1
if i == numCores-1 {
rangeStop = stopNonce
}
go solver(*header, rangeStart, rangeStop)
}
for i := uint32(0); i < numCores; i++ {
result := <-results
if result.found {
close(quit)
header.Nonce = result.nonce
return true
}
}
return false
}
// standardCoinbaseScript returns a standard script suitable for use as the
// signature script of the coinbase transaction of a new block. In particular,
// it starts with the block height that is required by version 2 blocks.
func standardCoinbaseScript(nextBlockHeight int32, extraNonce uint64) ([]byte, error) {
return txscript.NewScriptBuilder().AddInt64(int64(nextBlockHeight)).
AddInt64(int64(extraNonce)).Script()
}
// createCoinbaseTx returns a coinbase transaction paying an appropriate
// subsidy based on the passed block height to the provided address.
func createCoinbaseTx(coinbaseScript []byte, nextBlockHeight int32,
addr btcutil.Address, mineTo []wire.TxOut,
net *chaincfg.Params) (*btcutil.Tx, error) {
// Create the script to pay to the provided payment address.
pkScript, err := txscript.PayToAddrScript(addr)
if err != nil {
return nil, err
}
tx := wire.NewMsgTx(wire.TxVersion)
tx.AddTxIn(&wire.TxIn{
// Coinbase transactions have no inputs, so previous outpoint is
// zero hash and max index.
PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{},
wire.MaxPrevOutIndex),
SignatureScript: coinbaseScript,
Sequence: wire.MaxTxInSequenceNum,
})
if len(mineTo) == 0 {
tx.AddTxOut(&wire.TxOut{
Value: blockchain.CalcBlockSubsidy(nextBlockHeight, net),
PkScript: pkScript,
})
} else {
for i := range mineTo {
tx.AddTxOut(&mineTo[i])
}
}
return btcutil.NewTx(tx), nil
}
// CreateBlock creates a new block building from the previous block with a
// specified blockversion and timestamp. If the timestamp passed is zero (not
// initialized), then the timestamp of the previous block will be used plus 1
// second is used. Passing nil for the previous block results in a block that
// builds off of the genesis block for the specified chain.
func CreateBlock(prevBlock *btcutil.Block, inclusionTxs []*btcutil.Tx,
blockVersion int32, blockTime time.Time, miningAddr btcutil.Address,
mineTo []wire.TxOut, net *chaincfg.Params) (*btcutil.Block, error) {
var (
prevHash *chainhash.Hash
blockHeight int32
prevBlockTime time.Time
)
// If the previous block isn't specified, then we'll construct a block
// that builds off of the genesis block for the chain.
if prevBlock == nil {
prevHash = net.GenesisHash
blockHeight = 1
prevBlockTime = net.GenesisBlock.Header.Timestamp.Add(time.Minute)
} else {
prevHash = prevBlock.Hash()
blockHeight = prevBlock.Height() + 1
prevBlockTime = prevBlock.MsgBlock().Header.Timestamp
}
// If a target block time was specified, then use that as the header's
// timestamp. Otherwise, add one second to the previous block unless
// it's the genesis block in which case use the current time.
var ts time.Time
switch {
case !blockTime.IsZero():
ts = blockTime
default:
ts = prevBlockTime.Add(time.Second)
}
extraNonce := uint64(0)
coinbaseScript, err := standardCoinbaseScript(blockHeight, extraNonce)
if err != nil {
return nil, err
}
coinbaseTx, err := createCoinbaseTx(coinbaseScript, blockHeight,
miningAddr, mineTo, net)
if err != nil {
return nil, err
}
// Create a new block ready to be solved.
blockTxns := []*btcutil.Tx{coinbaseTx}
if inclusionTxs != nil {
blockTxns = append(blockTxns, inclusionTxs...)
}
merkles := blockchain.BuildMerkleTreeStore(blockTxns, false)
var block wire.MsgBlock
block.Header = wire.BlockHeader{
Version: blockVersion,
PrevBlock: *prevHash,
MerkleRoot: *merkles[len(merkles)-1],
Timestamp: ts,
Bits: net.PowLimitBits,
}
for _, tx := range blockTxns {
if err := block.AddTransaction(tx.MsgTx()); err != nil {
return nil, err
}
}
found := solveBlock(&block.Header, net.PowLimit)
if !found {
return nil, errors.New("Unable to solve block")
}
utilBlock := btcutil.NewBlock(&block)
utilBlock.SetHeight(blockHeight)
return utilBlock, nil
}

View File

@@ -0,0 +1,62 @@
// Copyright (c) 2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package rpctest
import (
"fmt"
"os/exec"
"path/filepath"
"runtime"
"sync"
)
var (
// compileMtx guards access to the executable path so that the project is
// only compiled once.
compileMtx sync.Mutex
// executablePath is the path to the compiled executable. This is the empty
// string until btcd is compiled. This should not be accessed directly;
// instead use the function btcdExecutablePath().
executablePath string
)
// btcdExecutablePath returns a path to the btcd executable to be used by
// rpctests. To ensure the code tests against the most up-to-date version of
// btcd, this method compiles btcd the first time it is called. After that, the
// generated binary is used for subsequent test harnesses. The executable file
// is not cleaned up, but since it lives at a static path in a temp directory,
// it is not a big deal.
func btcdExecutablePath() (string, error) {
compileMtx.Lock()
defer compileMtx.Unlock()
// If btcd has already been compiled, just use that.
if len(executablePath) != 0 {
return executablePath, nil
}
testDir, err := baseDir()
if err != nil {
return "", err
}
// Build btcd and output an executable in a static temp path.
outputPath := filepath.Join(testDir, "btcd")
if runtime.GOOS == "windows" {
outputPath += ".exe"
}
cmd := exec.Command(
"go", "build", "-o", outputPath, "github.com/btcsuite/btcd",
)
err = cmd.Run()
if err != nil {
return "", fmt.Errorf("Failed to build btcd: %v", err)
}
// Save executable path so future calls do not recompile.
executablePath = outputPath
return executablePath, nil
}

View File

@@ -0,0 +1,12 @@
// Package rpctest provides a btcd-specific RPC testing harness crafting and
// executing integration tests by driving a `btcd` instance via the `RPC`
// interface. Each instance of an active harness comes equipped with a simple
// in-memory HD wallet capable of properly syncing to the generated chain,
// creating new addresses, and crafting fully signed transactions paying to an
// arbitrary set of outputs.
//
// This package was designed specifically to act as an RPC testing harness for
// `btcd`. However, the constructs presented are general enough to be adapted to
// any project wishing to programmatically drive a `btcd` instance of its
// systems/integration tests.
package rpctest

View File

@@ -0,0 +1,591 @@
// Copyright (c) 2016-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package rpctest
import (
"bytes"
"encoding/binary"
"fmt"
"sync"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
)
var (
// hdSeed is the BIP 32 seed used by the memWallet to initialize it's
// HD root key. This value is hard coded in order to ensure
// deterministic behavior across test runs.
hdSeed = [chainhash.HashSize]byte{
0x79, 0xa6, 0x1a, 0xdb, 0xc6, 0xe5, 0xa2, 0xe1,
0x39, 0xd2, 0x71, 0x3a, 0x54, 0x6e, 0xc7, 0xc8,
0x75, 0x63, 0x2e, 0x75, 0xf1, 0xdf, 0x9c, 0x3f,
0xa6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
)
// utxo represents an unspent output spendable by the memWallet. The maturity
// height of the transaction is recorded in order to properly observe the
// maturity period of direct coinbase outputs.
type utxo struct {
pkScript []byte
value btcutil.Amount
keyIndex uint32
maturityHeight int32
isLocked bool
}
// isMature returns true if the target utxo is considered "mature" at the
// passed block height. Otherwise, false is returned.
func (u *utxo) isMature(height int32) bool {
return height >= u.maturityHeight
}
// chainUpdate encapsulates an update to the current main chain. This struct is
// used to sync up the memWallet each time a new block is connected to the main
// chain.
type chainUpdate struct {
blockHeight int32
filteredTxns []*btcutil.Tx
isConnect bool // True if connect, false if disconnect
}
// undoEntry is functionally the opposite of a chainUpdate. An undoEntry is
// created for each new block received, then stored in a log in order to
// properly handle block re-orgs.
type undoEntry struct {
utxosDestroyed map[wire.OutPoint]*utxo
utxosCreated []wire.OutPoint
}
// memWallet is a simple in-memory wallet whose purpose is to provide basic
// wallet functionality to the harness. The wallet uses a hard-coded HD key
// hierarchy which promotes reproducibility between harness test runs.
type memWallet struct {
coinbaseKey *btcec.PrivateKey
coinbaseAddr btcutil.Address
// hdRoot is the root master private key for the wallet.
hdRoot *hdkeychain.ExtendedKey
// hdIndex is the next available key index offset from the hdRoot.
hdIndex uint32
// currentHeight is the latest height the wallet is known to be synced
// to.
currentHeight int32
// addrs tracks all addresses belonging to the wallet. The addresses
// are indexed by their keypath from the hdRoot.
addrs map[uint32]btcutil.Address
// utxos is the set of utxos spendable by the wallet.
utxos map[wire.OutPoint]*utxo
// reorgJournal is a map storing an undo entry for each new block
// received. Once a block is disconnected, the undo entry for the
// particular height is evaluated, thereby rewinding the effect of the
// disconnected block on the wallet's set of spendable utxos.
reorgJournal map[int32]*undoEntry
chainUpdates []*chainUpdate
chainUpdateSignal chan struct{}
chainMtx sync.Mutex
net *chaincfg.Params
rpc *rpcclient.Client
sync.RWMutex
}
// newMemWallet creates and returns a fully initialized instance of the
// memWallet given a particular blockchain's parameters.
func newMemWallet(net *chaincfg.Params, harnessID uint32) (*memWallet, error) {
// The wallet's final HD seed is: hdSeed || harnessID. This method
// ensures that each harness instance uses a deterministic root seed
// based on its harness ID.
var harnessHDSeed [chainhash.HashSize + 4]byte
copy(harnessHDSeed[:], hdSeed[:])
binary.BigEndian.PutUint32(harnessHDSeed[:chainhash.HashSize], harnessID)
hdRoot, err := hdkeychain.NewMaster(harnessHDSeed[:], net)
if err != nil {
return nil, nil
}
// The first child key from the hd root is reserved as the coinbase
// generation address.
coinbaseChild, err := hdRoot.Child(0)
if err != nil {
return nil, err
}
coinbaseKey, err := coinbaseChild.ECPrivKey()
if err != nil {
return nil, err
}
coinbaseAddr, err := keyToAddr(coinbaseKey, net)
if err != nil {
return nil, err
}
// Track the coinbase generation address to ensure we properly track
// newly generated bitcoin we can spend.
addrs := make(map[uint32]btcutil.Address)
addrs[0] = coinbaseAddr
return &memWallet{
net: net,
coinbaseKey: coinbaseKey,
coinbaseAddr: coinbaseAddr,
hdIndex: 1,
hdRoot: hdRoot,
addrs: addrs,
utxos: make(map[wire.OutPoint]*utxo),
chainUpdateSignal: make(chan struct{}),
reorgJournal: make(map[int32]*undoEntry),
}, nil
}
// Start launches all goroutines required for the wallet to function properly.
func (m *memWallet) Start() {
go m.chainSyncer()
}
// SyncedHeight returns the height the wallet is known to be synced to.
//
// This function is safe for concurrent access.
func (m *memWallet) SyncedHeight() int32 {
m.RLock()
defer m.RUnlock()
return m.currentHeight
}
// SetRPCClient saves the passed rpc connection to btcd as the wallet's
// personal rpc connection.
func (m *memWallet) SetRPCClient(rpcClient *rpcclient.Client) {
m.rpc = rpcClient
}
// IngestBlock is a call-back which is to be triggered each time a new block is
// connected to the main chain. It queues the update for the chain syncer,
// calling the private version in sequential order.
func (m *memWallet) IngestBlock(height int32, header *wire.BlockHeader, filteredTxns []*btcutil.Tx) {
// Append this new chain update to the end of the queue of new chain
// updates.
m.chainMtx.Lock()
m.chainUpdates = append(m.chainUpdates, &chainUpdate{height,
filteredTxns, true})
m.chainMtx.Unlock()
// Launch a goroutine to signal the chainSyncer that a new update is
// available. We do this in a new goroutine in order to avoid blocking
// the main loop of the rpc client.
go func() {
m.chainUpdateSignal <- struct{}{}
}()
}
// ingestBlock updates the wallet's internal utxo state based on the outputs
// created and destroyed within each block.
func (m *memWallet) ingestBlock(update *chainUpdate) {
// Update the latest synced height, then process each filtered
// transaction in the block creating and destroying utxos within
// the wallet as a result.
m.currentHeight = update.blockHeight
undo := &undoEntry{
utxosDestroyed: make(map[wire.OutPoint]*utxo),
}
for _, tx := range update.filteredTxns {
mtx := tx.MsgTx()
isCoinbase := blockchain.IsCoinBaseTx(mtx)
txHash := mtx.TxHash()
m.evalOutputs(mtx.TxOut, &txHash, isCoinbase, undo)
m.evalInputs(mtx.TxIn, undo)
}
// Finally, record the undo entry for this block so we can
// properly update our internal state in response to the block
// being re-org'd from the main chain.
m.reorgJournal[update.blockHeight] = undo
}
// chainSyncer is a goroutine dedicated to processing new blocks in order to
// keep the wallet's utxo state up to date.
//
// NOTE: This MUST be run as a goroutine.
func (m *memWallet) chainSyncer() {
var update *chainUpdate
for range m.chainUpdateSignal {
// A new update is available, so pop the new chain update from
// the front of the update queue.
m.chainMtx.Lock()
update = m.chainUpdates[0]
m.chainUpdates[0] = nil // Set to nil to prevent GC leak.
m.chainUpdates = m.chainUpdates[1:]
m.chainMtx.Unlock()
m.Lock()
if update.isConnect {
m.ingestBlock(update)
} else {
m.unwindBlock(update)
}
m.Unlock()
}
}
// evalOutputs evaluates each of the passed outputs, creating a new matching
// utxo within the wallet if we're able to spend the output.
func (m *memWallet) evalOutputs(outputs []*wire.TxOut, txHash *chainhash.Hash,
isCoinbase bool, undo *undoEntry) {
for i, output := range outputs {
pkScript := output.PkScript
// Scan all the addresses we currently control to see if the
// output is paying to us.
for keyIndex, addr := range m.addrs {
pkHash := addr.ScriptAddress()
if !bytes.Contains(pkScript, pkHash) {
continue
}
// If this is a coinbase output, then we mark the
// maturity height at the proper block height in the
// future.
var maturityHeight int32
if isCoinbase {
maturityHeight = m.currentHeight + int32(m.net.CoinbaseMaturity)
}
op := wire.OutPoint{Hash: *txHash, Index: uint32(i)}
m.utxos[op] = &utxo{
value: btcutil.Amount(output.Value),
keyIndex: keyIndex,
maturityHeight: maturityHeight,
pkScript: pkScript,
}
undo.utxosCreated = append(undo.utxosCreated, op)
}
}
}
// evalInputs scans all the passed inputs, destroying any utxos within the
// wallet which are spent by an input.
func (m *memWallet) evalInputs(inputs []*wire.TxIn, undo *undoEntry) {
for _, txIn := range inputs {
op := txIn.PreviousOutPoint
oldUtxo, ok := m.utxos[op]
if !ok {
continue
}
undo.utxosDestroyed[op] = oldUtxo
delete(m.utxos, op)
}
}
// UnwindBlock is a call-back which is to be executed each time a block is
// disconnected from the main chain. It queues the update for the chain syncer,
// calling the private version in sequential order.
func (m *memWallet) UnwindBlock(height int32, header *wire.BlockHeader) {
// Append this new chain update to the end of the queue of new chain
// updates.
m.chainMtx.Lock()
m.chainUpdates = append(m.chainUpdates, &chainUpdate{height,
nil, false})
m.chainMtx.Unlock()
// Launch a goroutine to signal the chainSyncer that a new update is
// available. We do this in a new goroutine in order to avoid blocking
// the main loop of the rpc client.
go func() {
m.chainUpdateSignal <- struct{}{}
}()
}
// unwindBlock undoes the effect that a particular block had on the wallet's
// internal utxo state.
func (m *memWallet) unwindBlock(update *chainUpdate) {
undo := m.reorgJournal[update.blockHeight]
for _, utxo := range undo.utxosCreated {
delete(m.utxos, utxo)
}
for outPoint, utxo := range undo.utxosDestroyed {
m.utxos[outPoint] = utxo
}
delete(m.reorgJournal, update.blockHeight)
}
// newAddress returns a new address from the wallet's hd key chain. It also
// loads the address into the RPC client's transaction filter to ensure any
// transactions that involve it are delivered via the notifications.
func (m *memWallet) newAddress() (btcutil.Address, error) {
index := m.hdIndex
childKey, err := m.hdRoot.Child(index)
if err != nil {
return nil, err
}
privKey, err := childKey.ECPrivKey()
if err != nil {
return nil, err
}
addr, err := keyToAddr(privKey, m.net)
if err != nil {
return nil, err
}
err = m.rpc.LoadTxFilter(false, []btcutil.Address{addr}, nil)
if err != nil {
return nil, err
}
m.addrs[index] = addr
m.hdIndex++
return addr, nil
}
// NewAddress returns a fresh address spendable by the wallet.
//
// This function is safe for concurrent access.
func (m *memWallet) NewAddress() (btcutil.Address, error) {
m.Lock()
defer m.Unlock()
return m.newAddress()
}
// fundTx attempts to fund a transaction sending amt bitcoin. The coins are
// selected such that the final amount spent pays enough fees as dictated by the
// passed fee rate. The passed fee rate should be expressed in
// satoshis-per-byte. The transaction being funded can optionally include a
// change output indicated by the change boolean.
//
// NOTE: The memWallet's mutex must be held when this function is called.
func (m *memWallet) fundTx(tx *wire.MsgTx, amt btcutil.Amount,
feeRate btcutil.Amount, change bool) error {
const (
// spendSize is the largest number of bytes of a sigScript
// which spends a p2pkh output: OP_DATA_73 <sig> OP_DATA_33 <pubkey>
spendSize = 1 + 73 + 1 + 33
)
var (
amtSelected btcutil.Amount
txSize int
)
for outPoint, utxo := range m.utxos {
// Skip any outputs that are still currently immature or are
// currently locked.
if !utxo.isMature(m.currentHeight) || utxo.isLocked {
continue
}
amtSelected += utxo.value
// Add the selected output to the transaction, updating the
// current tx size while accounting for the size of the future
// sigScript.
tx.AddTxIn(wire.NewTxIn(&outPoint, nil, nil))
txSize = tx.SerializeSize() + spendSize*len(tx.TxIn)
// Calculate the fee required for the txn at this point
// observing the specified fee rate. If we don't have enough
// coins from he current amount selected to pay the fee, then
// continue to grab more coins.
reqFee := btcutil.Amount(txSize * int(feeRate))
if amtSelected-reqFee < amt {
continue
}
// If we have any change left over and we should create a change
// output, then add an additional output to the transaction
// reserved for it.
changeVal := amtSelected - amt - reqFee
if changeVal > 0 && change {
addr, err := m.newAddress()
if err != nil {
return err
}
pkScript, err := txscript.PayToAddrScript(addr)
if err != nil {
return err
}
changeOutput := &wire.TxOut{
Value: int64(changeVal),
PkScript: pkScript,
}
tx.AddTxOut(changeOutput)
}
return nil
}
// If we've reached this point, then coin selection failed due to an
// insufficient amount of coins.
return fmt.Errorf("not enough funds for coin selection")
}
// SendOutputs creates, then sends a transaction paying to the specified output
// while observing the passed fee rate. The passed fee rate should be expressed
// in satoshis-per-byte.
func (m *memWallet) SendOutputs(outputs []*wire.TxOut,
feeRate btcutil.Amount) (*chainhash.Hash, error) {
tx, err := m.CreateTransaction(outputs, feeRate, true)
if err != nil {
return nil, err
}
return m.rpc.SendRawTransaction(tx, true)
}
// SendOutputsWithoutChange creates and sends a transaction that pays to the
// specified outputs while observing the passed fee rate and ignoring a change
// output. The passed fee rate should be expressed in sat/b.
func (m *memWallet) SendOutputsWithoutChange(outputs []*wire.TxOut,
feeRate btcutil.Amount) (*chainhash.Hash, error) {
tx, err := m.CreateTransaction(outputs, feeRate, false)
if err != nil {
return nil, err
}
return m.rpc.SendRawTransaction(tx, true)
}
// CreateTransaction returns a fully signed transaction paying to the specified
// outputs while observing the desired fee rate. The passed fee rate should be
// expressed in satoshis-per-byte. The transaction being created can optionally
// include a change output indicated by the change boolean.
//
// This function is safe for concurrent access.
func (m *memWallet) CreateTransaction(outputs []*wire.TxOut,
feeRate btcutil.Amount, change bool) (*wire.MsgTx, error) {
m.Lock()
defer m.Unlock()
tx := wire.NewMsgTx(wire.TxVersion)
// Tally up the total amount to be sent in order to perform coin
// selection shortly below.
var outputAmt btcutil.Amount
for _, output := range outputs {
outputAmt += btcutil.Amount(output.Value)
tx.AddTxOut(output)
}
// Attempt to fund the transaction with spendable utxos.
if err := m.fundTx(tx, outputAmt, feeRate, change); err != nil {
return nil, err
}
// Populate all the selected inputs with valid sigScript for spending.
// Along the way record all outputs being spent in order to avoid a
// potential double spend.
spentOutputs := make([]*utxo, 0, len(tx.TxIn))
for i, txIn := range tx.TxIn {
outPoint := txIn.PreviousOutPoint
utxo := m.utxos[outPoint]
extendedKey, err := m.hdRoot.Child(utxo.keyIndex)
if err != nil {
return nil, err
}
privKey, err := extendedKey.ECPrivKey()
if err != nil {
return nil, err
}
sigScript, err := txscript.SignatureScript(tx, i, utxo.pkScript,
txscript.SigHashAll, privKey, true)
if err != nil {
return nil, err
}
txIn.SignatureScript = sigScript
spentOutputs = append(spentOutputs, utxo)
}
// As these outputs are now being spent by this newly created
// transaction, mark the outputs are "locked". This action ensures
// these outputs won't be double spent by any subsequent transactions.
// These locked outputs can be freed via a call to UnlockOutputs.
for _, utxo := range spentOutputs {
utxo.isLocked = true
}
return tx, nil
}
// UnlockOutputs unlocks any outputs which were previously locked due to
// being selected to fund a transaction via the CreateTransaction method.
//
// This function is safe for concurrent access.
func (m *memWallet) UnlockOutputs(inputs []*wire.TxIn) {
m.Lock()
defer m.Unlock()
for _, input := range inputs {
utxo, ok := m.utxos[input.PreviousOutPoint]
if !ok {
continue
}
utxo.isLocked = false
}
}
// ConfirmedBalance returns the confirmed balance of the wallet.
//
// This function is safe for concurrent access.
func (m *memWallet) ConfirmedBalance() btcutil.Amount {
m.RLock()
defer m.RUnlock()
var balance btcutil.Amount
for _, utxo := range m.utxos {
// Prevent any immature or locked outputs from contributing to
// the wallet's total confirmed balance.
if !utxo.isMature(m.currentHeight) || utxo.isLocked {
continue
}
balance += utxo.value
}
return balance
}
// keyToAddr maps the passed private to corresponding p2pkh address.
func keyToAddr(key *btcec.PrivateKey, net *chaincfg.Params) (btcutil.Address, error) {
serializedKey := key.PubKey().SerializeCompressed()
pubKeyAddr, err := btcutil.NewAddressPubKey(serializedKey, net)
if err != nil {
return nil, err
}
return pubKeyAddr.AddressPubKeyHash(), nil
}

View File

@@ -0,0 +1,287 @@
// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package rpctest
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"time"
rpc "github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcutil"
)
// nodeConfig contains all the args, and data required to launch a btcd process
// and connect the rpc client to it.
type nodeConfig struct {
rpcUser string
rpcPass string
listen string
rpcListen string
rpcConnect string
dataDir string
logDir string
profile string
debugLevel string
extra []string
prefix string
exe string
endpoint string
certFile string
keyFile string
certificates []byte
}
// newConfig returns a newConfig with all default values.
func newConfig(prefix, certFile, keyFile string, extra []string) (*nodeConfig, error) {
btcdPath, err := btcdExecutablePath()
if err != nil {
btcdPath = "btcd"
}
a := &nodeConfig{
listen: "127.0.0.1:18555",
rpcListen: "127.0.0.1:18556",
rpcUser: "user",
rpcPass: "pass",
extra: extra,
prefix: prefix,
exe: btcdPath,
endpoint: "ws",
certFile: certFile,
keyFile: keyFile,
}
if err := a.setDefaults(); err != nil {
return nil, err
}
return a, nil
}
// setDefaults sets the default values of the config. It also creates the
// temporary data, and log directories which must be cleaned up with a call to
// cleanup().
func (n *nodeConfig) setDefaults() error {
datadir, err := ioutil.TempDir("", n.prefix+"-data")
if err != nil {
return err
}
n.dataDir = datadir
logdir, err := ioutil.TempDir("", n.prefix+"-logs")
if err != nil {
return err
}
n.logDir = logdir
cert, err := ioutil.ReadFile(n.certFile)
if err != nil {
return err
}
n.certificates = cert
return nil
}
// arguments returns an array of arguments that be used to launch the btcd
// process.
func (n *nodeConfig) arguments() []string {
args := []string{}
if n.rpcUser != "" {
// --rpcuser
args = append(args, fmt.Sprintf("--rpcuser=%s", n.rpcUser))
}
if n.rpcPass != "" {
// --rpcpass
args = append(args, fmt.Sprintf("--rpcpass=%s", n.rpcPass))
}
if n.listen != "" {
// --listen
args = append(args, fmt.Sprintf("--listen=%s", n.listen))
}
if n.rpcListen != "" {
// --rpclisten
args = append(args, fmt.Sprintf("--rpclisten=%s", n.rpcListen))
}
if n.rpcConnect != "" {
// --rpcconnect
args = append(args, fmt.Sprintf("--rpcconnect=%s", n.rpcConnect))
}
// --rpccert
args = append(args, fmt.Sprintf("--rpccert=%s", n.certFile))
// --rpckey
args = append(args, fmt.Sprintf("--rpckey=%s", n.keyFile))
if n.dataDir != "" {
// --datadir
args = append(args, fmt.Sprintf("--datadir=%s", n.dataDir))
}
if n.logDir != "" {
// --logdir
args = append(args, fmt.Sprintf("--logdir=%s", n.logDir))
}
if n.profile != "" {
// --profile
args = append(args, fmt.Sprintf("--profile=%s", n.profile))
}
if n.debugLevel != "" {
// --debuglevel
args = append(args, fmt.Sprintf("--debuglevel=%s", n.debugLevel))
}
args = append(args, n.extra...)
return args
}
// command returns the exec.Cmd which will be used to start the btcd process.
func (n *nodeConfig) command() *exec.Cmd {
return exec.Command(n.exe, n.arguments()...)
}
// rpcConnConfig returns the rpc connection config that can be used to connect
// to the btcd process that is launched via Start().
func (n *nodeConfig) rpcConnConfig() rpc.ConnConfig {
return rpc.ConnConfig{
Host: n.rpcListen,
Endpoint: n.endpoint,
User: n.rpcUser,
Pass: n.rpcPass,
Certificates: n.certificates,
DisableAutoReconnect: true,
}
}
// String returns the string representation of this nodeConfig.
func (n *nodeConfig) String() string {
return n.prefix
}
// cleanup removes the tmp data and log directories.
func (n *nodeConfig) cleanup() error {
dirs := []string{
n.logDir,
n.dataDir,
}
var err error
for _, dir := range dirs {
if err = os.RemoveAll(dir); err != nil {
log.Printf("Cannot remove dir %s: %v", dir, err)
}
}
return err
}
// node houses the necessary state required to configure, launch, and manage a
// btcd process.
type node struct {
config *nodeConfig
cmd *exec.Cmd
pidFile string
dataDir string
}
// newNode creates a new node instance according to the passed config. dataDir
// will be used to hold a file recording the pid of the launched process, and
// as the base for the log and data directories for btcd.
func newNode(config *nodeConfig, dataDir string) (*node, error) {
return &node{
config: config,
dataDir: dataDir,
cmd: config.command(),
}, nil
}
// start creates a new btcd process, and writes its pid in a file reserved for
// recording the pid of the launched process. This file can be used to
// terminate the process in case of a hang, or panic. In the case of a failing
// test case, or panic, it is important that the process be stopped via stop(),
// otherwise, it will persist unless explicitly killed.
func (n *node) start() error {
if err := n.cmd.Start(); err != nil {
return err
}
pid, err := os.Create(filepath.Join(n.dataDir,
fmt.Sprintf("%s.pid", n.config)))
if err != nil {
return err
}
n.pidFile = pid.Name()
if _, err = fmt.Fprintf(pid, "%d\n", n.cmd.Process.Pid); err != nil {
return err
}
if err := pid.Close(); err != nil {
return err
}
return nil
}
// stop interrupts the running btcd process process, and waits until it exits
// properly. On windows, interrupt is not supported, so a kill signal is used
// instead
func (n *node) stop() error {
if n.cmd == nil || n.cmd.Process == nil {
// return if not properly initialized
// or error starting the process
return nil
}
defer n.cmd.Wait()
if runtime.GOOS == "windows" {
return n.cmd.Process.Signal(os.Kill)
}
return n.cmd.Process.Signal(os.Interrupt)
}
// cleanup cleanups process and args files. The file housing the pid of the
// created process will be deleted, as well as any directories created by the
// process.
func (n *node) cleanup() error {
if n.pidFile != "" {
if err := os.Remove(n.pidFile); err != nil {
log.Printf("unable to remove file %s: %v", n.pidFile,
err)
}
}
return n.config.cleanup()
}
// shutdown terminates the running btcd process, and cleans up all
// file/directories created by node.
func (n *node) shutdown() error {
if err := n.stop(); err != nil {
return err
}
if err := n.cleanup(); err != nil {
return err
}
return nil
}
// genCertPair generates a key/cert pair to the paths provided.
func genCertPair(certFile, keyFile string) error {
org := "rpctest autogenerated cert"
validUntil := time.Now().Add(10 * 365 * 24 * time.Hour)
cert, key, err := btcutil.NewTLSCertPair(org, validUntil, nil)
if err != nil {
return err
}
// Write cert and key files.
if err = ioutil.WriteFile(certFile, cert, 0666); err != nil {
return err
}
if err = ioutil.WriteFile(keyFile, key, 0600); err != nil {
os.Remove(certFile)
return err
}
return nil
}

View File

@@ -0,0 +1,500 @@
// Copyright (c) 2016-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package rpctest
import (
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"strconv"
"sync"
"testing"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
const (
// These constants define the minimum and maximum p2p and rpc port
// numbers used by a test harness. The min port is inclusive while the
// max port is exclusive.
minPeerPort = 10000
maxPeerPort = 35000
minRPCPort = maxPeerPort
maxRPCPort = 60000
// BlockVersion is the default block version used when generating
// blocks.
BlockVersion = 4
)
var (
// current number of active test nodes.
numTestInstances = 0
// processID is the process ID of the current running process. It is
// used to calculate ports based upon it when launching an rpc
// harnesses. The intent is to allow multiple process to run in
// parallel without port collisions.
//
// It should be noted however that there is still some small probability
// that there will be port collisions either due to other processes
// running or simply due to the stars aligning on the process IDs.
processID = os.Getpid()
// testInstances is a private package-level slice used to keep track of
// all active test harnesses. This global can be used to perform
// various "joins", shutdown several active harnesses after a test,
// etc.
testInstances = make(map[string]*Harness)
// Used to protest concurrent access to above declared variables.
harnessStateMtx sync.RWMutex
)
// HarnessTestCase represents a test-case which utilizes an instance of the
// Harness to exercise functionality.
type HarnessTestCase func(r *Harness, t *testing.T)
// Harness fully encapsulates an active btcd process to provide a unified
// platform for creating rpc driven integration tests involving btcd. The
// active btcd node will typically be run in simnet mode in order to allow for
// easy generation of test blockchains. The active btcd process is fully
// managed by Harness, which handles the necessary initialization, and teardown
// of the process along with any temporary directories created as a result.
// Multiple Harness instances may be run concurrently, in order to allow for
// testing complex scenarios involving multiple nodes. The harness also
// includes an in-memory wallet to streamline various classes of tests.
type Harness struct {
// ActiveNet is the parameters of the blockchain the Harness belongs
// to.
ActiveNet *chaincfg.Params
Node *rpcclient.Client
node *node
handlers *rpcclient.NotificationHandlers
wallet *memWallet
testNodeDir string
maxConnRetries int
nodeNum int
sync.Mutex
}
// New creates and initializes new instance of the rpc test harness.
// Optionally, websocket handlers and a specified configuration may be passed.
// In the case that a nil config is passed, a default configuration will be
// used.
//
// NOTE: This function is safe for concurrent access.
func New(activeNet *chaincfg.Params, handlers *rpcclient.NotificationHandlers,
extraArgs []string) (*Harness, error) {
harnessStateMtx.Lock()
defer harnessStateMtx.Unlock()
// Add a flag for the appropriate network type based on the provided
// chain params.
switch activeNet.Net {
case wire.MainNet:
// No extra flags since mainnet is the default
case wire.TestNet3:
extraArgs = append(extraArgs, "--testnet")
case wire.TestNet:
extraArgs = append(extraArgs, "--regtest")
case wire.SimNet:
extraArgs = append(extraArgs, "--simnet")
default:
return nil, fmt.Errorf("rpctest.New must be called with one " +
"of the supported chain networks")
}
testDir, err := baseDir()
if err != nil {
return nil, err
}
harnessID := strconv.Itoa(numTestInstances)
nodeTestData, err := ioutil.TempDir(testDir, "harness-"+harnessID)
if err != nil {
return nil, err
}
certFile := filepath.Join(nodeTestData, "rpc.cert")
keyFile := filepath.Join(nodeTestData, "rpc.key")
if err := genCertPair(certFile, keyFile); err != nil {
return nil, err
}
wallet, err := newMemWallet(activeNet, uint32(numTestInstances))
if err != nil {
return nil, err
}
miningAddr := fmt.Sprintf("--miningaddr=%s", wallet.coinbaseAddr)
extraArgs = append(extraArgs, miningAddr)
config, err := newConfig("rpctest", certFile, keyFile, extraArgs)
if err != nil {
return nil, err
}
// Generate p2p+rpc listening addresses.
config.listen, config.rpcListen = generateListeningAddresses()
// Create the testing node bounded to the simnet.
node, err := newNode(config, nodeTestData)
if err != nil {
return nil, err
}
nodeNum := numTestInstances
numTestInstances++
if handlers == nil {
handlers = &rpcclient.NotificationHandlers{}
}
// If a handler for the OnFilteredBlock{Connected,Disconnected} callback
// callback has already been set, then create a wrapper callback which
// executes both the currently registered callback and the mem wallet's
// callback.
if handlers.OnFilteredBlockConnected != nil {
obc := handlers.OnFilteredBlockConnected
handlers.OnFilteredBlockConnected = func(height int32, header *wire.BlockHeader, filteredTxns []*btcutil.Tx) {
wallet.IngestBlock(height, header, filteredTxns)
obc(height, header, filteredTxns)
}
} else {
// Otherwise, we can claim the callback ourselves.
handlers.OnFilteredBlockConnected = wallet.IngestBlock
}
if handlers.OnFilteredBlockDisconnected != nil {
obd := handlers.OnFilteredBlockDisconnected
handlers.OnFilteredBlockDisconnected = func(height int32, header *wire.BlockHeader) {
wallet.UnwindBlock(height, header)
obd(height, header)
}
} else {
handlers.OnFilteredBlockDisconnected = wallet.UnwindBlock
}
h := &Harness{
handlers: handlers,
node: node,
maxConnRetries: 20,
testNodeDir: nodeTestData,
ActiveNet: activeNet,
nodeNum: nodeNum,
wallet: wallet,
}
// Track this newly created test instance within the package level
// global map of all active test instances.
testInstances[h.testNodeDir] = h
return h, nil
}
// SetUp initializes the rpc test state. Initialization includes: starting up a
// simnet node, creating a websockets client and connecting to the started
// node, and finally: optionally generating and submitting a testchain with a
// configurable number of mature coinbase outputs coinbase outputs.
//
// NOTE: This method and TearDown should always be called from the same
// goroutine as they are not concurrent safe.
func (h *Harness) SetUp(createTestChain bool, numMatureOutputs uint32) error {
// Start the btcd node itself. This spawns a new process which will be
// managed
if err := h.node.start(); err != nil {
return err
}
if err := h.connectRPCClient(); err != nil {
return err
}
h.wallet.Start()
// Filter transactions that pay to the coinbase associated with the
// wallet.
filterAddrs := []btcutil.Address{h.wallet.coinbaseAddr}
if err := h.Node.LoadTxFilter(true, filterAddrs, nil); err != nil {
return err
}
// Ensure btcd properly dispatches our registered call-back for each new
// block. Otherwise, the memWallet won't function properly.
if err := h.Node.NotifyBlocks(); err != nil {
return err
}
// Create a test chain with the desired number of mature coinbase
// outputs.
if createTestChain && numMatureOutputs != 0 {
numToGenerate := (uint32(h.ActiveNet.CoinbaseMaturity) +
numMatureOutputs)
_, err := h.Node.Generate(numToGenerate)
if err != nil {
return err
}
}
// Block until the wallet has fully synced up to the tip of the main
// chain.
_, height, err := h.Node.GetBestBlock()
if err != nil {
return err
}
ticker := time.NewTicker(time.Millisecond * 100)
for range ticker.C {
walletHeight := h.wallet.SyncedHeight()
if walletHeight == height {
break
}
}
ticker.Stop()
return nil
}
// tearDown stops the running rpc test instance. All created processes are
// killed, and temporary directories removed.
//
// This function MUST be called with the harness state mutex held (for writes).
func (h *Harness) tearDown() error {
if h.Node != nil {
h.Node.Shutdown()
}
if err := h.node.shutdown(); err != nil {
return err
}
if err := os.RemoveAll(h.testNodeDir); err != nil {
return err
}
delete(testInstances, h.testNodeDir)
return nil
}
// TearDown stops the running rpc test instance. All created processes are
// killed, and temporary directories removed.
//
// NOTE: This method and SetUp should always be called from the same goroutine
// as they are not concurrent safe.
func (h *Harness) TearDown() error {
harnessStateMtx.Lock()
defer harnessStateMtx.Unlock()
return h.tearDown()
}
// connectRPCClient attempts to establish an RPC connection to the created btcd
// process belonging to this Harness instance. If the initial connection
// attempt fails, this function will retry h.maxConnRetries times, backing off
// the time between subsequent attempts. If after h.maxConnRetries attempts,
// we're not able to establish a connection, this function returns with an
// error.
func (h *Harness) connectRPCClient() error {
var client *rpcclient.Client
var err error
rpcConf := h.node.config.rpcConnConfig()
for i := 0; i < h.maxConnRetries; i++ {
if client, err = rpcclient.New(&rpcConf, h.handlers); err != nil {
time.Sleep(time.Duration(i) * 50 * time.Millisecond)
continue
}
break
}
if client == nil {
return fmt.Errorf("connection timeout")
}
h.Node = client
h.wallet.SetRPCClient(client)
return nil
}
// NewAddress returns a fresh address spendable by the Harness' internal
// wallet.
//
// This function is safe for concurrent access.
func (h *Harness) NewAddress() (btcutil.Address, error) {
return h.wallet.NewAddress()
}
// ConfirmedBalance returns the confirmed balance of the Harness' internal
// wallet.
//
// This function is safe for concurrent access.
func (h *Harness) ConfirmedBalance() btcutil.Amount {
return h.wallet.ConfirmedBalance()
}
// SendOutputs creates, signs, and finally broadcasts a transaction spending
// the harness' available mature coinbase outputs creating new outputs
// according to targetOutputs.
//
// This function is safe for concurrent access.
func (h *Harness) SendOutputs(targetOutputs []*wire.TxOut,
feeRate btcutil.Amount) (*chainhash.Hash, error) {
return h.wallet.SendOutputs(targetOutputs, feeRate)
}
// SendOutputsWithoutChange creates and sends a transaction that pays to the
// specified outputs while observing the passed fee rate and ignoring a change
// output. The passed fee rate should be expressed in sat/b.
//
// This function is safe for concurrent access.
func (h *Harness) SendOutputsWithoutChange(targetOutputs []*wire.TxOut,
feeRate btcutil.Amount) (*chainhash.Hash, error) {
return h.wallet.SendOutputsWithoutChange(targetOutputs, feeRate)
}
// CreateTransaction returns a fully signed transaction paying to the specified
// outputs while observing the desired fee rate. The passed fee rate should be
// expressed in satoshis-per-byte. The transaction being created can optionally
// include a change output indicated by the change boolean. Any unspent outputs
// selected as inputs for the crafted transaction are marked as unspendable in
// order to avoid potential double-spends by future calls to this method. If the
// created transaction is cancelled for any reason then the selected inputs MUST
// be freed via a call to UnlockOutputs. Otherwise, the locked inputs won't be
// returned to the pool of spendable outputs.
//
// This function is safe for concurrent access.
func (h *Harness) CreateTransaction(targetOutputs []*wire.TxOut,
feeRate btcutil.Amount, change bool) (*wire.MsgTx, error) {
return h.wallet.CreateTransaction(targetOutputs, feeRate, change)
}
// UnlockOutputs unlocks any outputs which were previously marked as
// unspendabe due to being selected to fund a transaction via the
// CreateTransaction method.
//
// This function is safe for concurrent access.
func (h *Harness) UnlockOutputs(inputs []*wire.TxIn) {
h.wallet.UnlockOutputs(inputs)
}
// RPCConfig returns the harnesses current rpc configuration. This allows other
// potential RPC clients created within tests to connect to a given test
// harness instance.
func (h *Harness) RPCConfig() rpcclient.ConnConfig {
return h.node.config.rpcConnConfig()
}
// P2PAddress returns the harness' P2P listening address. This allows potential
// peers (such as SPV peers) created within tests to connect to a given test
// harness instance.
func (h *Harness) P2PAddress() string {
return h.node.config.listen
}
// GenerateAndSubmitBlock creates a block whose contents include the passed
// transactions and submits it to the running simnet node. For generating
// blocks with only a coinbase tx, callers can simply pass nil instead of
// transactions to be mined. Additionally, a custom block version can be set by
// the caller. A blockVersion of -1 indicates that the current default block
// version should be used. An uninitialized time.Time should be used for the
// blockTime parameter if one doesn't wish to set a custom time.
//
// This function is safe for concurrent access.
func (h *Harness) GenerateAndSubmitBlock(txns []*btcutil.Tx, blockVersion int32,
blockTime time.Time) (*btcutil.Block, error) {
return h.GenerateAndSubmitBlockWithCustomCoinbaseOutputs(txns,
blockVersion, blockTime, []wire.TxOut{})
}
// GenerateAndSubmitBlockWithCustomCoinbaseOutputs creates a block whose
// contents include the passed coinbase outputs and transactions and submits
// it to the running simnet node. For generating blocks with only a coinbase tx,
// callers can simply pass nil instead of transactions to be mined.
// Additionally, a custom block version can be set by the caller. A blockVersion
// of -1 indicates that the current default block version should be used. An
// uninitialized time.Time should be used for the blockTime parameter if one
// doesn't wish to set a custom time. The mineTo list of outputs will be added
// to the coinbase; this is not checked for correctness until the block is
// submitted; thus, it is the caller's responsibility to ensure that the outputs
// are correct. If the list is empty, the coinbase reward goes to the wallet
// managed by the Harness.
//
// This function is safe for concurrent access.
func (h *Harness) GenerateAndSubmitBlockWithCustomCoinbaseOutputs(
txns []*btcutil.Tx, blockVersion int32, blockTime time.Time,
mineTo []wire.TxOut) (*btcutil.Block, error) {
h.Lock()
defer h.Unlock()
if blockVersion == -1 {
blockVersion = BlockVersion
}
prevBlockHash, prevBlockHeight, err := h.Node.GetBestBlock()
if err != nil {
return nil, err
}
mBlock, err := h.Node.GetBlock(prevBlockHash)
if err != nil {
return nil, err
}
prevBlock := btcutil.NewBlock(mBlock)
prevBlock.SetHeight(prevBlockHeight)
// Create a new block including the specified transactions
newBlock, err := CreateBlock(prevBlock, txns, blockVersion,
blockTime, h.wallet.coinbaseAddr, mineTo, h.ActiveNet)
if err != nil {
return nil, err
}
// Submit the block to the simnet node.
if err := h.Node.SubmitBlock(newBlock, nil); err != nil {
return nil, err
}
return newBlock, nil
}
// generateListeningAddresses returns two strings representing listening
// addresses designated for the current rpc test. If there haven't been any
// test instances created, the default ports are used. Otherwise, in order to
// support multiple test nodes running at once, the p2p and rpc port are
// incremented after each initialization.
func generateListeningAddresses() (string, string) {
localhost := "127.0.0.1"
portString := func(minPort, maxPort int) string {
port := minPort + numTestInstances + ((20 * processID) %
(maxPort - minPort))
return strconv.Itoa(port)
}
p2p := net.JoinHostPort(localhost, portString(minPeerPort, maxPeerPort))
rpc := net.JoinHostPort(localhost, portString(minRPCPort, maxRPCPort))
return p2p, rpc
}
// baseDir is the directory path of the temp directory for all rpctest files.
func baseDir() (string, error) {
dirPath := filepath.Join(os.TempDir(), "btcd", "rpctest")
err := os.MkdirAll(dirPath, 0755)
return dirPath, err
}

View File

@@ -0,0 +1,164 @@
// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package rpctest
import (
"reflect"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
)
// JoinType is an enum representing a particular type of "node join". A node
// join is a synchronization tool used to wait until a subset of nodes have a
// consistent state with respect to an attribute.
type JoinType uint8
const (
// Blocks is a JoinType which waits until all nodes share the same
// block height.
Blocks JoinType = iota
// Mempools is a JoinType which blocks until all nodes have identical
// mempool.
Mempools
)
// JoinNodes is a synchronization tool used to block until all passed nodes are
// fully synced with respect to an attribute. This function will block for a
// period of time, finally returning once all nodes are synced according to the
// passed JoinType. This function be used to to ensure all active test
// harnesses are at a consistent state before proceeding to an assertion or
// check within rpc tests.
func JoinNodes(nodes []*Harness, joinType JoinType) error {
switch joinType {
case Blocks:
return syncBlocks(nodes)
case Mempools:
return syncMempools(nodes)
}
return nil
}
// syncMempools blocks until all nodes have identical mempools.
func syncMempools(nodes []*Harness) error {
poolsMatch := false
retry:
for !poolsMatch {
firstPool, err := nodes[0].Node.GetRawMempool()
if err != nil {
return err
}
// If all nodes have an identical mempool with respect to the
// first node, then we're done. Otherwise, drop back to the top
// of the loop and retry after a short wait period.
for _, node := range nodes[1:] {
nodePool, err := node.Node.GetRawMempool()
if err != nil {
return err
}
if !reflect.DeepEqual(firstPool, nodePool) {
time.Sleep(time.Millisecond * 100)
continue retry
}
}
poolsMatch = true
}
return nil
}
// syncBlocks blocks until all nodes report the same best chain.
func syncBlocks(nodes []*Harness) error {
blocksMatch := false
retry:
for !blocksMatch {
var prevHash *chainhash.Hash
var prevHeight int32
for _, node := range nodes {
blockHash, blockHeight, err := node.Node.GetBestBlock()
if err != nil {
return err
}
if prevHash != nil && (*blockHash != *prevHash ||
blockHeight != prevHeight) {
time.Sleep(time.Millisecond * 100)
continue retry
}
prevHash, prevHeight = blockHash, blockHeight
}
blocksMatch = true
}
return nil
}
// ConnectNode establishes a new peer-to-peer connection between the "from"
// harness and the "to" harness. The connection made is flagged as persistent,
// therefore in the case of disconnects, "from" will attempt to reestablish a
// connection to the "to" harness.
func ConnectNode(from *Harness, to *Harness) error {
peerInfo, err := from.Node.GetPeerInfo()
if err != nil {
return err
}
numPeers := len(peerInfo)
targetAddr := to.node.config.listen
if err := from.Node.AddNode(targetAddr, rpcclient.ANAdd); err != nil {
return err
}
// Block until a new connection has been established.
peerInfo, err = from.Node.GetPeerInfo()
if err != nil {
return err
}
for len(peerInfo) <= numPeers {
peerInfo, err = from.Node.GetPeerInfo()
if err != nil {
return err
}
}
return nil
}
// TearDownAll tears down all active test harnesses.
func TearDownAll() error {
harnessStateMtx.Lock()
defer harnessStateMtx.Unlock()
for _, harness := range testInstances {
if err := harness.tearDown(); err != nil {
return err
}
}
return nil
}
// ActiveHarnesses returns a slice of all currently active test harnesses. A
// test harness if considered "active" if it has been created, but not yet torn
// down.
func ActiveHarnesses() []*Harness {
harnessStateMtx.RLock()
defer harnessStateMtx.RUnlock()
activeNodes := make([]*Harness, 0, len(testInstances))
for _, harness := range testInstances {
activeNodes = append(activeNodes, harness)
}
return activeNodes
}

View File

@@ -1,127 +0,0 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package peer
import (
"bytes"
"container/list"
"fmt"
"sync"
"github.com/btcsuite/btcd/wire"
)
// mruInventoryMap provides a concurrency safe map that is limited to a maximum
// number of items with eviction for the oldest entry when the limit is
// exceeded.
type mruInventoryMap struct {
invMtx sync.Mutex
invMap map[wire.InvVect]*list.Element // nearly O(1) lookups
invList *list.List // O(1) insert, update, delete
limit uint
}
// String returns the map as a human-readable string.
//
// This function is safe for concurrent access.
func (m *mruInventoryMap) String() string {
m.invMtx.Lock()
defer m.invMtx.Unlock()
lastEntryNum := len(m.invMap) - 1
curEntry := 0
buf := bytes.NewBufferString("[")
for iv := range m.invMap {
buf.WriteString(fmt.Sprintf("%v", iv))
if curEntry < lastEntryNum {
buf.WriteString(", ")
}
curEntry++
}
buf.WriteString("]")
return fmt.Sprintf("<%d>%s", m.limit, buf.String())
}
// Exists returns whether or not the passed inventory item is in the map.
//
// This function is safe for concurrent access.
func (m *mruInventoryMap) Exists(iv *wire.InvVect) bool {
m.invMtx.Lock()
_, exists := m.invMap[*iv]
m.invMtx.Unlock()
return exists
}
// Add adds the passed inventory to the map and handles eviction of the oldest
// item if adding the new item would exceed the max limit. Adding an existing
// item makes it the most recently used item.
//
// This function is safe for concurrent access.
func (m *mruInventoryMap) Add(iv *wire.InvVect) {
m.invMtx.Lock()
defer m.invMtx.Unlock()
// When the limit is zero, nothing can be added to the map, so just
// return.
if m.limit == 0 {
return
}
// When the entry already exists move it to the front of the list
// thereby marking it most recently used.
if node, exists := m.invMap[*iv]; exists {
m.invList.MoveToFront(node)
return
}
// Evict the least recently used entry (back of the list) if the the new
// entry would exceed the size limit for the map. Also reuse the list
// node so a new one doesn't have to be allocated.
if uint(len(m.invMap))+1 > m.limit {
node := m.invList.Back()
lru := node.Value.(*wire.InvVect)
// Evict least recently used item.
delete(m.invMap, *lru)
// Reuse the list node of the item that was just evicted for the
// new item.
node.Value = iv
m.invList.MoveToFront(node)
m.invMap[*iv] = node
return
}
// The limit hasn't been reached yet, so just add the new item.
node := m.invList.PushFront(iv)
m.invMap[*iv] = node
}
// Delete deletes the passed inventory item from the map (if it exists).
//
// This function is safe for concurrent access.
func (m *mruInventoryMap) Delete(iv *wire.InvVect) {
m.invMtx.Lock()
if node, exists := m.invMap[*iv]; exists {
m.invList.Remove(node)
delete(m.invMap, *iv)
}
m.invMtx.Unlock()
}
// newMruInventoryMap returns a new inventory map that is limited to the number
// of entries specified by limit. When the number of entries exceeds the limit,
// the oldest (least recently used) entry will be removed to make room for the
// new entry.
func newMruInventoryMap(limit uint) *mruInventoryMap {
m := mruInventoryMap{
invMap: make(map[wire.InvVect]*list.Element),
invList: list.New(),
limit: limit,
}
return &m
}

View File

@@ -1,125 +0,0 @@
// Copyright (c) 2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package peer
import (
"bytes"
"container/list"
"fmt"
"sync"
)
// mruNonceMap provides a concurrency safe map that is limited to a maximum
// number of items with eviction for the oldest entry when the limit is
// exceeded.
type mruNonceMap struct {
mtx sync.Mutex
nonceMap map[uint64]*list.Element // nearly O(1) lookups
nonceList *list.List // O(1) insert, update, delete
limit uint
}
// String returns the map as a human-readable string.
//
// This function is safe for concurrent access.
func (m *mruNonceMap) String() string {
m.mtx.Lock()
defer m.mtx.Unlock()
lastEntryNum := len(m.nonceMap) - 1
curEntry := 0
buf := bytes.NewBufferString("[")
for nonce := range m.nonceMap {
buf.WriteString(fmt.Sprintf("%d", nonce))
if curEntry < lastEntryNum {
buf.WriteString(", ")
}
curEntry++
}
buf.WriteString("]")
return fmt.Sprintf("<%d>%s", m.limit, buf.String())
}
// Exists returns whether or not the passed nonce is in the map.
//
// This function is safe for concurrent access.
func (m *mruNonceMap) Exists(nonce uint64) bool {
m.mtx.Lock()
_, exists := m.nonceMap[nonce]
m.mtx.Unlock()
return exists
}
// Add adds the passed nonce to the map and handles eviction of the oldest item
// if adding the new item would exceed the max limit. Adding an existing item
// makes it the most recently used item.
//
// This function is safe for concurrent access.
func (m *mruNonceMap) Add(nonce uint64) {
m.mtx.Lock()
defer m.mtx.Unlock()
// When the limit is zero, nothing can be added to the map, so just
// return.
if m.limit == 0 {
return
}
// When the entry already exists move it to the front of the list
// thereby marking it most recently used.
if node, exists := m.nonceMap[nonce]; exists {
m.nonceList.MoveToFront(node)
return
}
// Evict the least recently used entry (back of the list) if the the new
// entry would exceed the size limit for the map. Also reuse the list
// node so a new one doesn't have to be allocated.
if uint(len(m.nonceMap))+1 > m.limit {
node := m.nonceList.Back()
lru := node.Value.(uint64)
// Evict least recently used item.
delete(m.nonceMap, lru)
// Reuse the list node of the item that was just evicted for the
// new item.
node.Value = nonce
m.nonceList.MoveToFront(node)
m.nonceMap[nonce] = node
return
}
// The limit hasn't been reached yet, so just add the new item.
node := m.nonceList.PushFront(nonce)
m.nonceMap[nonce] = node
}
// Delete deletes the passed nonce from the map (if it exists).
//
// This function is safe for concurrent access.
func (m *mruNonceMap) Delete(nonce uint64) {
m.mtx.Lock()
if node, exists := m.nonceMap[nonce]; exists {
m.nonceList.Remove(node)
delete(m.nonceMap, nonce)
}
m.mtx.Unlock()
}
// newMruNonceMap returns a new nonce map that is limited to the number of
// entries specified by limit. When the number of entries exceeds the limit,
// the oldest (least recently used) entry will be removed to make room for the
// new entry.
func newMruNonceMap(limit uint) *mruNonceMap {
m := mruNonceMap{
nonceMap: make(map[uint64]*list.Element),
nonceList: list.New(),
limit: limit,
}
return &m
}

View File

@@ -24,6 +24,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/go-socks/socks"
"github.com/davecgh/go-spew/spew"
"github.com/decred/dcrd/lru"
)
const (
@@ -82,7 +83,7 @@ var (
// sentNonces houses the unique nonces that are generated when pushing
// version messages that are used to detect self connections.
sentNonces = newMruNonceMap(50)
sentNonces = lru.NewCache(50)
// allowSelfConns is only used to allow the tests to bypass the self
// connection detecting and disconnect logic since they intentionally
@@ -450,7 +451,7 @@ type Peer struct {
wireEncoding wire.MessageEncoding
knownInventory *mruInventoryMap
knownInventory lru.Cache
prevGetBlocksMtx sync.Mutex
prevGetBlocksBegin *chainhash.Hash
prevGetBlocksStop *chainhash.Hash
@@ -1626,7 +1627,7 @@ out:
// Don't send inventory that became known after
// the initial check.
if p.knownInventory.Exists(iv) {
if p.knownInventory.Contains(iv) {
continue
}
@@ -1832,7 +1833,7 @@ func (p *Peer) QueueMessageWithEncoding(msg wire.Message, doneChan chan<- struct
func (p *Peer) QueueInventory(invVect *wire.InvVect) {
// Don't add the inventory to the send queue if the peer is already
// known to have it.
if p.knownInventory.Exists(invVect) {
if p.knownInventory.Contains(invVect) {
return
}
@@ -1891,7 +1892,7 @@ func (p *Peer) readRemoteVersionMsg() error {
}
// Detect self connections.
if !allowSelfConns && sentNonces.Exists(msg.Nonce) {
if !allowSelfConns && sentNonces.Contains(msg.Nonce) {
return errors.New("disconnecting peer connected to self")
}
@@ -2097,7 +2098,7 @@ func (p *Peer) negotiateInboundProtocol() error {
return p.readRemoteVerAckMsg()
}
// negotiateOutoundProtocol performs the negotiation protocol for an outbound
// negotiateOutboundProtocol performs the negotiation protocol for an outbound
// peer. The events should occur in the following order, otherwise an error is
// returned:
//
@@ -2224,7 +2225,7 @@ func newPeerBase(origCfg *Config, inbound bool) *Peer {
p := Peer{
inbound: inbound,
wireEncoding: wire.BaseEncoding,
knownInventory: newMruInventoryMap(maxKnownInventory),
knownInventory: lru.NewCache(maxKnownInventory),
stallControl: make(chan stallControlMsg, 1), // nonblocking sync
outputQueue: make(chan outMsg, outputBufferSize),
sendQueue: make(chan outMsg, 1), // nonblocking sync

View File

@@ -52,14 +52,61 @@ func (c *Client) GetBestBlockHash() (*chainhash.Hash, error) {
return c.GetBestBlockHashAsync().Receive()
}
// legacyGetBlockRequest constructs and sends a legacy getblock request which
// contains two separate bools to denote verbosity, in contract to a single int
// parameter.
func (c *Client) legacyGetBlockRequest(hash string, verbose,
verboseTx bool) ([]byte, error) {
hashJSON, err := json.Marshal(hash)
if err != nil {
return nil, err
}
verboseJSON, err := json.Marshal(btcjson.Bool(verbose))
if err != nil {
return nil, err
}
verboseTxJSON, err := json.Marshal(btcjson.Bool(verboseTx))
if err != nil {
return nil, err
}
return c.RawRequest("getblock", []json.RawMessage{
hashJSON, verboseJSON, verboseTxJSON,
})
}
// waitForGetBlockRes waits for the response of a getblock request. If the
// response indicates an invalid parameter was provided, a legacy style of the
// request is resent and its response is returned instead.
func (c *Client) waitForGetBlockRes(respChan chan *response, hash string,
verbose, verboseTx bool) ([]byte, error) {
res, err := receiveFuture(respChan)
// If we receive an invalid parameter error, then we may be
// communicating with a btcd node which only understands the legacy
// request, so we'll try that.
if err, ok := err.(*btcjson.RPCError); ok &&
err.Code == btcjson.ErrRPCInvalidParams.Code {
return c.legacyGetBlockRequest(hash, verbose, verboseTx)
}
// Otherwise, we can return the response as is.
return res, err
}
// FutureGetBlockResult is a future promise to deliver the result of a
// GetBlockAsync RPC invocation (or an applicable error).
type FutureGetBlockResult chan *response
type FutureGetBlockResult struct {
client *Client
hash string
Response chan *response
}
// Receive waits for the response promised by the future and returns the raw
// block requested from the server given its hash.
func (r FutureGetBlockResult) Receive() (*wire.MsgBlock, error) {
res, err := receiveFuture(r)
res, err := r.client.waitForGetBlockRes(r.Response, r.hash, false, false)
if err != nil {
return nil, err
}
@@ -97,8 +144,12 @@ func (c *Client) GetBlockAsync(blockHash *chainhash.Hash) FutureGetBlockResult {
hash = blockHash.String()
}
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(false), nil)
return c.sendCmd(cmd)
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Int(0))
return FutureGetBlockResult{
client: c,
hash: hash,
Response: c.sendCmd(cmd),
}
}
// GetBlock returns a raw block from the server given its hash.
@@ -111,12 +162,16 @@ func (c *Client) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
// FutureGetBlockVerboseResult is a future promise to deliver the result of a
// GetBlockVerboseAsync RPC invocation (or an applicable error).
type FutureGetBlockVerboseResult chan *response
type FutureGetBlockVerboseResult struct {
client *Client
hash string
Response chan *response
}
// Receive waits for the response promised by the future and returns the data
// structure from the server with information about the requested block.
func (r FutureGetBlockVerboseResult) Receive() (*btcjson.GetBlockVerboseResult, error) {
res, err := receiveFuture(r)
res, err := r.client.waitForGetBlockRes(r.Response, r.hash, true, false)
if err != nil {
return nil, err
}
@@ -140,9 +195,14 @@ func (c *Client) GetBlockVerboseAsync(blockHash *chainhash.Hash) FutureGetBlockV
if blockHash != nil {
hash = blockHash.String()
}
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(true), nil)
return c.sendCmd(cmd)
// From the bitcoin-cli getblock documentation:
// "If verbosity is 1, returns an Object with information about block ."
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Int(1))
return FutureGetBlockVerboseResult{
client: c,
hash: hash,
Response: c.sendCmd(cmd),
}
}
// GetBlockVerbose returns a data structure from the server with information
@@ -154,19 +214,52 @@ func (c *Client) GetBlockVerbose(blockHash *chainhash.Hash) (*btcjson.GetBlockVe
return c.GetBlockVerboseAsync(blockHash).Receive()
}
// FutureGetBlockVerboseTxResult is a future promise to deliver the result of a
// GetBlockVerboseTxResult RPC invocation (or an applicable error).
type FutureGetBlockVerboseTxResult struct {
client *Client
hash string
Response chan *response
}
// Receive waits for the response promised by the future and returns a verbose
// version of the block including detailed information about its transactions.
func (r FutureGetBlockVerboseTxResult) Receive() (*btcjson.GetBlockVerboseTxResult, error) {
res, err := r.client.waitForGetBlockRes(r.Response, r.hash, true, true)
if err != nil {
return nil, err
}
var blockResult btcjson.GetBlockVerboseTxResult
err = json.Unmarshal(res, &blockResult)
if err != nil {
return nil, err
}
return &blockResult, nil
}
// GetBlockVerboseTxAsync 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 GetBlockVerboseTx or the blocking version and more details.
func (c *Client) GetBlockVerboseTxAsync(blockHash *chainhash.Hash) FutureGetBlockVerboseResult {
func (c *Client) GetBlockVerboseTxAsync(blockHash *chainhash.Hash) FutureGetBlockVerboseTxResult {
hash := ""
if blockHash != nil {
hash = blockHash.String()
}
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(true), btcjson.Bool(true))
return c.sendCmd(cmd)
// From the bitcoin-cli getblock documentation:
//
// If verbosity is 2, returns an Object with information about block
// and information about each transaction.
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Int(2))
return FutureGetBlockVerboseTxResult{
client: c,
hash: hash,
Response: c.sendCmd(cmd),
}
}
// GetBlockVerboseTx returns a data structure from the server with information
@@ -174,7 +267,7 @@ func (c *Client) GetBlockVerboseTxAsync(blockHash *chainhash.Hash) FutureGetBloc
//
// See GetBlockVerbose if only transaction hashes are preferred.
// See GetBlock to retrieve a raw block instead.
func (c *Client) GetBlockVerboseTx(blockHash *chainhash.Hash) (*btcjson.GetBlockVerboseResult, error) {
func (c *Client) GetBlockVerboseTx(blockHash *chainhash.Hash) (*btcjson.GetBlockVerboseTxResult, error) {
return c.GetBlockVerboseTxAsync(blockHash).Receive()
}
@@ -214,6 +307,79 @@ func (c *Client) GetBlockCount() (int64, error) {
return c.GetBlockCountAsync().Receive()
}
// FutureGetChainTxStatsResult is a future promise to deliver the result of a
// GetChainTxStatsAsync RPC invocation (or an applicable error).
type FutureGetChainTxStatsResult chan *response
// Receive waits for the response promised by the future and returns transaction statistics
func (r FutureGetChainTxStatsResult) Receive() (*btcjson.GetChainTxStatsResult, error) {
res, err := receiveFuture(r)
if err != nil {
return nil, err
}
var chainTxStats btcjson.GetChainTxStatsResult
err = json.Unmarshal(res, &chainTxStats)
if err != nil {
return nil, err
}
return &chainTxStats, nil
}
// GetChainTxStatsAsync 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 GetChainTxStats for the blocking version and more details.
func (c *Client) GetChainTxStatsAsync() FutureGetChainTxStatsResult {
cmd := btcjson.NewGetChainTxStatsCmd(nil, nil)
return c.sendCmd(cmd)
}
// GetChainTxStatsNBlocksAsync 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 GetChainTxStatsNBlocks for the blocking version and more details.
func (c *Client) GetChainTxStatsNBlocksAsync(nBlocks int32) FutureGetChainTxStatsResult {
cmd := btcjson.NewGetChainTxStatsCmd(&nBlocks, nil)
return c.sendCmd(cmd)
}
// GetChainTxStatsNBlocksBlockHashAsync 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 GetChainTxStatsNBlocksBlockHash for the blocking version and more details.
func (c *Client) GetChainTxStatsNBlocksBlockHashAsync(nBlocks int32, blockHash chainhash.Hash) FutureGetChainTxStatsResult {
hash := blockHash.String()
cmd := btcjson.NewGetChainTxStatsCmd(&nBlocks, &hash)
return c.sendCmd(cmd)
}
// GetChainTxStats returns statistics about the total number and rate of transactions in the chain.
//
// Size of the window is one month and it ends at chain tip.
func (c *Client) GetChainTxStats() (*btcjson.GetChainTxStatsResult, error) {
return c.GetChainTxStatsAsync().Receive()
}
// GetChainTxStatsNBlocks returns statistics about the total number and rate of transactions in the chain.
//
// The argument specifies size of the window in number of blocks. The window ends at chain tip.
func (c *Client) GetChainTxStatsNBlocks(nBlocks int32) (*btcjson.GetChainTxStatsResult, error) {
return c.GetChainTxStatsNBlocksAsync(nBlocks).Receive()
}
// GetChainTxStatsNBlocksBlockHash returns statistics about the total number and rate of transactions in the chain.
//
// First argument specifies size of the window in number of blocks.
// Second argument is the hash of the block that ends the window.
func (c *Client) GetChainTxStatsNBlocksBlockHash(nBlocks int32, blockHash chainhash.Hash) (*btcjson.GetChainTxStatsResult, error) {
return c.GetChainTxStatsNBlocksBlockHashAsync(nBlocks, blockHash).Receive()
}
// FutureGetDifficultyResult is a future promise to deliver the result of a
// GetDifficultyAsync RPC invocation (or an applicable error).
type FutureGetDifficultyResult chan *response
@@ -649,6 +815,41 @@ func (c *Client) EstimateFee(numBlocks int64) (float64, error) {
return c.EstimateFeeAsync(numBlocks).Receive()
}
// FutureEstimateFeeResult is a future promise to deliver the result of a
// EstimateSmartFeeAsync RPC invocation (or an applicable error).
type FutureEstimateSmartFeeResult chan *response
// Receive waits for the response promised by the future and returns the
// estimated fee.
func (r FutureEstimateSmartFeeResult) Receive() (*btcjson.EstimateSmartFeeResult, error) {
res, err := receiveFuture(r)
if err != nil {
return nil, err
}
var verified btcjson.EstimateSmartFeeResult
err = json.Unmarshal(res, &verified)
if err != nil {
return nil, err
}
return &verified, nil
}
// EstimateSmartFeeAsync 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 EstimateSmartFee for the blocking version and more details.
func (c *Client) EstimateSmartFeeAsync(confTarget int64, mode *btcjson.EstimateSmartFeeMode) FutureEstimateSmartFeeResult {
cmd := btcjson.NewEstimateSmartFeeCmd(confTarget, mode)
return c.sendCmd(cmd)
}
// EstimateSmartFee requests the server to estimate a fee level based on the given parameters.
func (c *Client) EstimateSmartFee(confTarget int64, mode *btcjson.EstimateSmartFeeMode) (*btcjson.EstimateSmartFeeResult, error) {
return c.EstimateSmartFeeAsync(confTarget, mode).Receive()
}
// FutureVerifyChainResult is a future promise to deliver the result of a
// VerifyChainAsync, VerifyChainLevelAsyncRPC, or VerifyChainBlocksAsync
// invocation (or an applicable error).
@@ -982,3 +1183,44 @@ func (c *Client) GetCFilterHeader(blockHash *chainhash.Hash,
filterType wire.FilterType) (*wire.MsgCFHeaders, error) {
return c.GetCFilterHeaderAsync(blockHash, filterType).Receive()
}
// FutureGetBlockStatsResult is a future promise to deliver the result of a
// GetBlockStatsAsync RPC invocation (or an applicable error).
type FutureGetBlockStatsResult chan *response
// Receive waits for the response promised by the future and returns statistics
// of a block at a certain height.
func (r FutureGetBlockStatsResult) Receive() (*btcjson.GetBlockStatsResult, error) {
res, err := receiveFuture(r)
if err != nil {
return nil, err
}
var blockStats btcjson.GetBlockStatsResult
err = json.Unmarshal(res, &blockStats)
if err != nil {
return nil, err
}
return &blockStats, nil
}
// GetBlockStatsAsync 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 GetBlockStats or the blocking version and more details.
func (c *Client) GetBlockStatsAsync(hashOrHeight interface{}, stats *[]string) FutureGetBlockStatsResult {
if hash, ok := hashOrHeight.(*chainhash.Hash); ok {
hashOrHeight = hash.String()
}
cmd := btcjson.NewGetBlockStatsCmd(btcjson.HashOrHeight{Value: hashOrHeight}, stats)
return c.sendCmd(cmd)
}
// GetBlockStats returns block statistics. First argument specifies height or hash of the target block.
// Second argument allows to select certain stats to return.
func (c *Client) GetBlockStats(hashOrHeight interface{}, stats *[]string) (*btcjson.GetBlockStatsResult, error) {
return c.GetBlockStatsAsync(hashOrHeight, stats).Receive()
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2017 The Namecoin developers
// Copyright (c) 2019 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package rpcclient
import (
"bufio"
"fmt"
"os"
"strings"
)
func readCookieFile(path string) (username, password string, err error) {
f, err := os.Open(path)
if err != nil {
return
}
defer f.Close()
scanner := bufio.NewScanner(f)
scanner.Scan()
err = scanner.Err()
if err != nil {
return
}
s := scanner.Text()
parts := strings.SplitN(s, ":", 2)
if len(parts) != 2 {
err = fmt.Errorf("malformed cookie file")
return
}
username, password = parts[0], parts[1]
return
}

View File

@@ -106,7 +106,7 @@ Some of the commands are extensions specific to a particular RPC server. For
example, the DebugLevel call is an extension only provided by btcd (and
btcwallet passthrough). Therefore if you call one of these commands against
an RPC server that doesn't provide them, you will get an unimplemented error
from the server. An effort has been made to call out which commmands are
from the server. An effort has been made to call out which commands are
extensions in their documentation.
Also, it is important to realize that btcd intentionally separates the wallet

View File

@@ -19,12 +19,14 @@ import (
"net"
"net/http"
"net/url"
"os"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/go-socks/socks"
"github.com/btcsuite/websocket"
)
@@ -138,6 +140,10 @@ type Client struct {
// config holds the connection configuration assoiated with this client.
config *ConnConfig
// chainParams holds the params for the chain that this client is using,
// and is used for many wallet methods.
chainParams *chaincfg.Params
// wsConn is the underlying websocket connection when not in HTTP POST
// mode.
wsConn *websocket.Conn
@@ -283,31 +289,29 @@ func (c *Client) trackRegisteredNtfns(cmd interface{}) {
}
}
type (
// inMessage is the first type that an incoming message is unmarshaled
// into. It supports both requests (for notification support) and
// responses. The partially-unmarshaled message is a notification if
// the embedded ID (from the response) is nil. Otherwise, it is a
// response.
inMessage struct {
ID *float64 `json:"id"`
*rawNotification
*rawResponse
}
// inMessage is the first type that an incoming message is unmarshaled
// into. It supports both requests (for notification support) and
// responses. The partially-unmarshaled message is a notification if
// the embedded ID (from the response) is nil. Otherwise, it is a
// response.
type inMessage struct {
ID *float64 `json:"id"`
*rawNotification
*rawResponse
}
// rawNotification is a partially-unmarshaled JSON-RPC notification.
rawNotification struct {
Method string `json:"method"`
Params []json.RawMessage `json:"params"`
}
// rawNotification is a partially-unmarshaled JSON-RPC notification.
type rawNotification struct {
Method string `json:"method"`
Params []json.RawMessage `json:"params"`
}
// rawResponse is a partially-unmarshaled JSON-RPC response. For this
// to be valid (according to JSON-RPC 1.0 spec), ID may not be nil.
rawResponse struct {
Result json.RawMessage `json:"result"`
Error *btcjson.RPCError `json:"error"`
}
)
// rawResponse is a partially-unmarshaled JSON-RPC response. For this
// to be valid (according to JSON-RPC 1.0 spec), ID may not be nil.
type rawResponse struct {
Result json.RawMessage `json:"result"`
Error *btcjson.RPCError `json:"error"`
}
// response is the raw bytes of a JSON-RPC result, or the error if the response
// error object was non-null.
@@ -848,7 +852,12 @@ func (c *Client) sendPost(jReq *jsonRequest) {
httpReq.Header.Set("Content-Type", "application/json")
// Configure basic access authorization.
httpReq.SetBasicAuth(c.config.User, c.config.Pass)
user, pass, err := c.config.getAuth()
if err != nil {
jReq.responseChan <- &response{result: nil, err: err}
return
}
httpReq.SetBasicAuth(user, pass)
log.Tracef("Sending command [%s] with id %d", jReq.method, jReq.id)
c.sendPostRequest(httpReq, jReq)
@@ -1093,6 +1102,22 @@ type ConnConfig struct {
// Pass is the passphrase to use to authenticate to the RPC server.
Pass string
// CookiePath is the path to a cookie file containing the username and
// passphrase to use to authenticate to the RPC server. It is used
// instead of User and Pass if non-empty.
CookiePath string
cookieLastCheckTime time.Time
cookieLastModTime time.Time
cookieLastUser string
cookieLastPass string
cookieLastErr error
// Params is the string representing the network that the server
// is running. If there is no parameter set in the config, then
// mainnet will be used by default.
Params 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
@@ -1141,6 +1166,43 @@ type ConnConfig struct {
EnableBCInfoHacks bool
}
// getAuth returns the username and passphrase that will actually be used for
// this connection. This will be the result of checking the cookie if a cookie
// path is configured; if not, it will be the user-configured username and
// passphrase.
func (config *ConnConfig) getAuth() (username, passphrase string, err error) {
// Try username+passphrase auth first.
if config.Pass != "" {
return config.User, config.Pass, nil
}
// If no username or passphrase is set, try cookie auth.
return config.retrieveCookie()
}
// retrieveCookie returns the cookie username and passphrase.
func (config *ConnConfig) retrieveCookie() (username, passphrase string, err error) {
if !config.cookieLastCheckTime.IsZero() && time.Now().Before(config.cookieLastCheckTime.Add(30*time.Second)) {
return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr
}
config.cookieLastCheckTime = time.Now()
st, err := os.Stat(config.CookiePath)
if err != nil {
config.cookieLastErr = err
return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr
}
modTime := st.ModTime()
if !modTime.Equal(config.cookieLastModTime) {
config.cookieLastModTime = modTime
config.cookieLastUser, config.cookieLastPass, config.cookieLastErr = readCookieFile(config.CookiePath)
}
return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr
}
// 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) {
@@ -1210,7 +1272,11 @@ func dial(config *ConnConfig) (*websocket.Conn, error) {
// The RPC server requires basic authorization, so create a custom
// request header with the Authorization header set.
login := config.User + ":" + config.Pass
user, pass, err := config.getAuth()
if err != nil {
return nil, err
}
login := user + ":" + pass
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
requestHeader := make(http.Header)
requestHeader.Add("Authorization", auth)
@@ -1290,6 +1356,23 @@ func New(config *ConnConfig, ntfnHandlers *NotificationHandlers) (*Client, error
shutdown: make(chan struct{}),
}
// Default network is mainnet, no parameters are necessary but if mainnet
// is specified it will be the param
switch config.Params {
case "":
fallthrough
case chaincfg.MainNetParams.Name:
client.chainParams = &chaincfg.MainNetParams
case chaincfg.TestNet3Params.Name:
client.chainParams = &chaincfg.TestNet3Params
case chaincfg.RegressionNetParams.Name:
client.chainParams = &chaincfg.RegressionNetParams
case chaincfg.SimNetParams.Name:
client.chainParams = &chaincfg.SimNetParams
default:
return nil, fmt.Errorf("rpcclient.New: Unknown chain %s", config.Params)
}
if start {
log.Infof("Established connection to RPC server %s",
config.Host)

View File

@@ -61,6 +61,53 @@ func (c *Client) Generate(numBlocks uint32) ([]*chainhash.Hash, error) {
return c.GenerateAsync(numBlocks).Receive()
}
// FutureGenerateToAddressResult is a future promise to deliver the result of a
// GenerateToAddressResult RPC invocation (or an applicable error).
type FutureGenerateToAddressResult chan *response
// Receive waits for the response promised by the future and returns the hashes of
// of the generated blocks.
func (f FutureGenerateToAddressResult) Receive() ([]*chainhash.Hash, error) {
res, err := receiveFuture(f)
if err != nil {
return nil, err
}
// Unmarshal result as a list of strings.
var result []string
err = json.Unmarshal(res, &result)
if err != nil {
return nil, err
}
// Convert each block hash to a chainhash.Hash and store a pointer to
// each.
convertedResult := make([]*chainhash.Hash, len(result))
for i, hashString := range result {
convertedResult[i], err = chainhash.NewHashFromStr(hashString)
if err != nil {
return nil, err
}
}
return convertedResult, nil
}
// GenerateToAddressAsync 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 GenerateToAddress for the blocking version and more details.
func (c *Client) GenerateToAddressAsync(numBlocks int64, address btcutil.Address, maxTries *int64) FutureGenerateToAddressResult {
cmd := btcjson.NewGenerateToAddressCmd(numBlocks, address.EncodeAddress(), maxTries)
return c.sendCmd(cmd)
}
// GenerateToAddress generates numBlocks blocks to the given address and returns their hashes.
func (c *Client) GenerateToAddress(numBlocks int64, address btcutil.Address, maxTries *int64) ([]*chainhash.Hash, error) {
return c.GenerateToAddressAsync(numBlocks, address, maxTries).Receive()
}
// FutureGetGenerateResult is a future promise to deliver the result of a
// GetGenerateAsync RPC invocation (or an applicable error).
type FutureGetGenerateResult chan *response

View File

@@ -95,7 +95,7 @@ type NotificationHandlers struct {
// NotifyBlocks has been made to register for the notification and the
// function is non-nil.
//
// NOTE: Deprecated. Use OnFilteredBlockConnected instead.
// Deprecated: Use OnFilteredBlockConnected instead.
OnBlockConnected func(hash *chainhash.Hash, height int32, t time.Time)
// OnFilteredBlockConnected is invoked when a block is connected to the
@@ -111,7 +111,7 @@ type NotificationHandlers struct {
// NotifyBlocks has been made to register for the notification and the
// function is non-nil.
//
// NOTE: Deprecated. Use OnFilteredBlockDisconnected instead.
// Deprecated: Use OnFilteredBlockDisconnected instead.
OnBlockDisconnected func(hash *chainhash.Hash, height int32, t time.Time)
// OnFilteredBlockDisconnected is invoked when a block is disconnected
@@ -127,7 +127,7 @@ type NotificationHandlers struct {
// preceding call to NotifyReceived, Rescan, or RescanEndHeight has been
// made to register for the notification and the function is non-nil.
//
// NOTE: Deprecated. Use OnRelevantTxAccepted instead.
// Deprecated: Use OnRelevantTxAccepted instead.
OnRecvTx func(transaction *btcutil.Tx, details *btcjson.BlockDetails)
// OnRedeemingTx is invoked when a transaction that spends a registered
@@ -141,7 +141,7 @@ type NotificationHandlers struct {
// funds to the registered addresses. This means it is possible for
// this to invoked indirectly as the result of a NotifyReceived call.
//
// NOTE: Deprecated. Use OnRelevantTxAccepted instead.
// Deprecated: Use OnRelevantTxAccepted instead.
OnRedeemingTx func(transaction *btcutil.Tx, details *btcjson.BlockDetails)
// OnRelevantTxAccepted is invoked when an unmined transaction passes
@@ -157,14 +157,14 @@ type NotificationHandlers struct {
// result of a rescan request, due to how btcd may send various rescan
// notifications after the rescan request has already returned.
//
// NOTE: Deprecated. Not used with RescanBlocks.
// Deprecated: Not used with RescanBlocks.
OnRescanFinished func(hash *chainhash.Hash, height int32, blkTime time.Time)
// 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.
//
// NOTE: Deprecated. Not used with RescanBlocks.
// Deprecated: Not used with RescanBlocks.
OnRescanProgress func(hash *chainhash.Hash, height int32, blkTime time.Time)
// OnTxAccepted is invoked when a transaction is accepted into the
@@ -905,7 +905,7 @@ func (c *Client) NotifyBlocks() error {
// FutureNotifySpentResult is a future promise to deliver the result of a
// NotifySpentAsync RPC invocation (or an applicable error).
//
// NOTE: Deprecated. Use FutureLoadTxFilterResult instead.
// Deprecated: Use FutureLoadTxFilterResult instead.
type FutureNotifySpentResult chan *response
// Receive waits for the response promised by the future and returns an error
@@ -951,7 +951,7 @@ func newOutPointFromWire(op *wire.OutPoint) btcjson.OutPoint {
//
// NOTE: This is a btcd extension and requires a websocket connection.
//
// NOTE: Deprecated. Use LoadTxFilterAsync instead.
// Deprecated: Use LoadTxFilterAsync instead.
func (c *Client) NotifySpentAsync(outpoints []*wire.OutPoint) FutureNotifySpentResult {
// Not supported in HTTP POST mode.
if c.config.HTTPPostMode {
@@ -983,7 +983,7 @@ func (c *Client) NotifySpentAsync(outpoints []*wire.OutPoint) FutureNotifySpentR
//
// NOTE: This is a btcd extension and requires a websocket connection.
//
// NOTE: Deprecated. Use LoadTxFilter instead.
// Deprecated: Use LoadTxFilter instead.
func (c *Client) NotifySpent(outpoints []*wire.OutPoint) error {
return c.NotifySpentAsync(outpoints).Receive()
}
@@ -1040,7 +1040,7 @@ func (c *Client) NotifyNewTransactions(verbose bool) error {
// FutureNotifyReceivedResult is a future promise to deliver the result of a
// NotifyReceivedAsync RPC invocation (or an applicable error).
//
// NOTE: Deprecated. Use FutureLoadTxFilterResult instead.
// Deprecated: Use FutureLoadTxFilterResult instead.
type FutureNotifyReceivedResult chan *response
// Receive waits for the response promised by the future and returns an error
@@ -1078,7 +1078,7 @@ func (c *Client) notifyReceivedInternal(addresses []string) FutureNotifyReceived
//
// NOTE: This is a btcd extension and requires a websocket connection.
//
// NOTE: Deprecated. Use LoadTxFilterAsync instead.
// Deprecated: Use LoadTxFilterAsync instead.
func (c *Client) NotifyReceivedAsync(addresses []btcutil.Address) FutureNotifyReceivedResult {
// Not supported in HTTP POST mode.
if c.config.HTTPPostMode {
@@ -1118,7 +1118,7 @@ func (c *Client) NotifyReceivedAsync(addresses []btcutil.Address) FutureNotifyRe
//
// NOTE: This is a btcd extension and requires a websocket connection.
//
// NOTE: Deprecated. Use LoadTxFilter instead.
// Deprecated: Use LoadTxFilter instead.
func (c *Client) NotifyReceived(addresses []btcutil.Address) error {
return c.NotifyReceivedAsync(addresses).Receive()
}
@@ -1126,7 +1126,7 @@ func (c *Client) NotifyReceived(addresses []btcutil.Address) error {
// FutureRescanResult is a future promise to deliver the result of a RescanAsync
// or RescanEndHeightAsync RPC invocation (or an applicable error).
//
// NOTE: Deprecated. Use FutureRescanBlocksResult instead.
// Deprecated: Use FutureRescanBlocksResult instead.
type FutureRescanResult chan *response
// Receive waits for the response promised by the future and returns an error
@@ -1150,7 +1150,7 @@ func (r FutureRescanResult) Receive() error {
//
// NOTE: This is a btcd extension and requires a websocket connection.
//
// NOTE: Deprecated. Use RescanBlocksAsync instead.
// Deprecated: Use RescanBlocksAsync instead.
func (c *Client) RescanAsync(startBlock *chainhash.Hash,
addresses []btcutil.Address,
outpoints []*wire.OutPoint) FutureRescanResult {
@@ -1215,7 +1215,7 @@ func (c *Client) RescanAsync(startBlock *chainhash.Hash,
//
// NOTE: This is a btcd extension and requires a websocket connection.
//
// NOTE: Deprecated. Use RescanBlocks instead.
// Deprecated: Use RescanBlocks instead.
func (c *Client) Rescan(startBlock *chainhash.Hash,
addresses []btcutil.Address,
outpoints []*wire.OutPoint) error {
@@ -1231,7 +1231,7 @@ func (c *Client) Rescan(startBlock *chainhash.Hash,
//
// NOTE: This is a btcd extension and requires a websocket connection.
//
// NOTE: Deprecated. Use RescanBlocksAsync instead.
// Deprecated: Use RescanBlocksAsync instead.
func (c *Client) RescanEndBlockAsync(startBlock *chainhash.Hash,
addresses []btcutil.Address, outpoints []*wire.OutPoint,
endBlock *chainhash.Hash) FutureRescanResult {
@@ -1293,7 +1293,7 @@ func (c *Client) RescanEndBlockAsync(startBlock *chainhash.Hash,
//
// NOTE: This is a btcd extension and requires a websocket connection.
//
// NOTE: Deprecated. Use RescanBlocks instead.
// Deprecated: Use RescanBlocks instead.
func (c *Client) RescanEndHeight(startBlock *chainhash.Hash,
addresses []btcutil.Address, outpoints []*wire.OutPoint,
endBlock *chainhash.Hash) error {

View File

@@ -205,6 +205,47 @@ func (c *Client) DecodeRawTransaction(serializedTx []byte) (*btcjson.TxRawResult
return c.DecodeRawTransactionAsync(serializedTx).Receive()
}
// FutureFundRawTransactionResult is a future promise to deliver the result
// of a FutureFundRawTransactionAsync RPC invocation (or an applicable error).
type FutureFundRawTransactionResult chan *response
// Receive waits for the response promised by the future and returns information
// about a funding attempt
func (r FutureFundRawTransactionResult) Receive() (*btcjson.FundRawTransactionResult, error) {
res, err := receiveFuture(r)
if err != nil {
return nil, err
}
var marshalled btcjson.FundRawTransactionResult
if err := json.Unmarshal(res, &marshalled); err != nil {
return nil, err
}
return &marshalled, nil
}
// FundRawTransactionAsync 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 FundRawTransaction for the blocking version and more details.
func (c *Client) FundRawTransactionAsync(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) FutureFundRawTransactionResult {
var txBuf bytes.Buffer
if err := tx.Serialize(&txBuf); err != nil {
return newFutureError(err)
}
cmd := btcjson.NewFundRawTransactionCmd(txBuf.Bytes(), opts, isWitness)
return c.sendCmd(cmd)
}
// FundRawTransaction returns the result of trying to fund the given transaction with
// funds from the node wallet
func (c *Client) FundRawTransaction(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) (*btcjson.FundRawTransactionResult, error) {
return c.FundRawTransactionAsync(tx, opts, isWitness).Receive()
}
// FutureCreateRawTransactionResult is a future promise to deliver the result
// of a CreateRawTransactionAsync RPC invocation (or an applicable error).
type FutureCreateRawTransactionResult chan *response
@@ -233,8 +274,13 @@ func (r FutureCreateRawTransactionResult) Receive() (*wire.MsgTx, error) {
// Deserialize the transaction and return it.
var msgTx wire.MsgTx
if err := msgTx.Deserialize(bytes.NewReader(serializedTx)); err != nil {
return nil, err
// we try both the new and old encoding format
witnessErr := msgTx.Deserialize(bytes.NewReader(serializedTx))
if witnessErr != nil {
legacyErr := msgTx.DeserializeNoWitness(bytes.NewReader(serializedTx))
if legacyErr != nil {
return nil, legacyErr
}
}
return &msgTx, nil
}
@@ -256,7 +302,8 @@ func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput,
}
// CreateRawTransaction returns a new transaction spending the provided inputs
// and sending to the provided addresses.
// and sending to the provided addresses. If the inputs are either nil or an
// empty slice, it is interpreted as an empty slice.
func (c *Client) CreateRawTransaction(inputs []btcjson.TransactionInput,
amounts map[btcutil.Address]btcutil.Amount, lockTime *int64) (*wire.MsgTx, error) {

View File

@@ -20,7 +20,8 @@ import (
// *****************************
// FutureGetTransactionResult is a future promise to deliver the result
// of a GetTransactionAsync RPC invocation (or an applicable error).
// of a GetTransactionAsync or GetTransactionWatchOnlyAsync RPC invocation
// (or an applicable error).
type FutureGetTransactionResult chan *response
// Receive waits for the response promised by the future and returns detailed
@@ -63,6 +64,28 @@ func (c *Client) GetTransaction(txHash *chainhash.Hash) (*btcjson.GetTransaction
return c.GetTransactionAsync(txHash).Receive()
}
// GetTransactionWatchOnlyAsync 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 GetTransactionWatchOnly for the blocking version and more details.
func (c *Client) GetTransactionWatchOnlyAsync(txHash *chainhash.Hash, watchOnly bool) FutureGetTransactionResult {
hash := ""
if txHash != nil {
hash = txHash.String()
}
cmd := btcjson.NewGetTransactionCmd(hash, &watchOnly)
return c.sendCmd(cmd)
}
// GetTransactionWatchOnly returns detailed information about a wallet
// transaction, and allow including watch-only addresses in balance
// calculation and details.
func (c *Client) GetTransactionWatchOnly(txHash *chainhash.Hash, watchOnly bool) (*btcjson.GetTransactionResult, error) {
return c.GetTransactionWatchOnlyAsync(txHash, watchOnly).Receive()
}
// FutureListTransactionsResult is a future promise to deliver the result of a
// ListTransactionsAsync, ListTransactionsCountAsync, or
// ListTransactionsCountFromAsync RPC invocation (or an applicable error).
@@ -753,13 +776,16 @@ func (c *Client) SendManyComment(fromAccount string,
// FutureAddMultisigAddressResult is a future promise to deliver the result of a
// AddMultisigAddressAsync RPC invocation (or an applicable error).
type FutureAddMultisigAddressResult chan *response
type FutureAddMultisigAddressResult struct {
responseChannel chan *response
network *chaincfg.Params
}
// Receive waits for the response promised by the future and returns the
// multisignature address that requires the specified number of signatures for
// the provided addresses.
func (r FutureAddMultisigAddressResult) Receive() (btcutil.Address, error) {
res, err := receiveFuture(r)
res, err := receiveFuture(r.responseChannel)
if err != nil {
return nil, err
}
@@ -771,7 +797,7 @@ func (r FutureAddMultisigAddressResult) Receive() (btcutil.Address, error) {
return nil, err
}
return btcutil.DecodeAddress(addr, &chaincfg.MainNetParams)
return btcutil.DecodeAddress(addr, r.network)
}
// AddMultisigAddressAsync returns an instance of a type that can be used to get
@@ -786,14 +812,17 @@ func (c *Client) AddMultisigAddressAsync(requiredSigs int, addresses []btcutil.A
}
cmd := btcjson.NewAddMultisigAddressCmd(requiredSigs, addrs, &account)
return c.sendCmd(cmd)
result := FutureAddMultisigAddressResult{
network: c.chainParams,
responseChannel: c.sendCmd(cmd),
}
return result
}
// AddMultisigAddress adds a multisignature address that requires the specified
// number of signatures for the provided addresses to the wallet.
func (c *Client) AddMultisigAddress(requiredSigs int, addresses []btcutil.Address, account string) (btcutil.Address, error) {
return c.AddMultisigAddressAsync(requiredSigs, addresses,
account).Receive()
return c.AddMultisigAddressAsync(requiredSigs, addresses, account).Receive()
}
// FutureCreateMultisigResult is a future promise to deliver the result of a
@@ -868,12 +897,15 @@ func (c *Client) CreateNewAccount(account string) error {
// FutureGetNewAddressResult is a future promise to deliver the result of a
// GetNewAddressAsync RPC invocation (or an applicable error).
type FutureGetNewAddressResult chan *response
type FutureGetNewAddressResult struct {
responseChannel chan *response
network *chaincfg.Params
}
// Receive waits for the response promised by the future and returns a new
// address.
func (r FutureGetNewAddressResult) Receive() (btcutil.Address, error) {
res, err := receiveFuture(r)
res, err := receiveFuture(r.responseChannel)
if err != nil {
return nil, err
}
@@ -885,7 +917,7 @@ func (r FutureGetNewAddressResult) Receive() (btcutil.Address, error) {
return nil, err
}
return btcutil.DecodeAddress(addr, &chaincfg.MainNetParams)
return btcutil.DecodeAddress(addr, r.network)
}
// GetNewAddressAsync returns an instance of a type that can be used to get the
@@ -895,23 +927,31 @@ func (r FutureGetNewAddressResult) Receive() (btcutil.Address, error) {
// See GetNewAddress for the blocking version and more details.
func (c *Client) GetNewAddressAsync(account string) FutureGetNewAddressResult {
cmd := btcjson.NewGetNewAddressCmd(&account)
return c.sendCmd(cmd)
result := FutureGetNewAddressResult{
network: c.chainParams,
responseChannel: c.sendCmd(cmd),
}
return result
}
// GetNewAddress returns a new address.
// GetNewAddress returns a new address, and decodes based on the client's
// chain params.
func (c *Client) GetNewAddress(account string) (btcutil.Address, error) {
return c.GetNewAddressAsync(account).Receive()
}
// FutureGetRawChangeAddressResult is a future promise to deliver the result of
// a GetRawChangeAddressAsync RPC invocation (or an applicable error).
type FutureGetRawChangeAddressResult chan *response
type FutureGetRawChangeAddressResult struct {
responseChannel chan *response
network *chaincfg.Params
}
// Receive waits for the response promised by the future and returns a new
// address for receiving change that will be associated with the provided
// account. Note that this is only for raw transactions and NOT for normal use.
func (r FutureGetRawChangeAddressResult) Receive() (btcutil.Address, error) {
res, err := receiveFuture(r)
res, err := receiveFuture(r.responseChannel)
if err != nil {
return nil, err
}
@@ -923,7 +963,7 @@ func (r FutureGetRawChangeAddressResult) Receive() (btcutil.Address, error) {
return nil, err
}
return btcutil.DecodeAddress(addr, &chaincfg.MainNetParams)
return btcutil.DecodeAddress(addr, r.network)
}
// GetRawChangeAddressAsync returns an instance of a type that can be used to
@@ -933,7 +973,11 @@ func (r FutureGetRawChangeAddressResult) Receive() (btcutil.Address, error) {
// See GetRawChangeAddress for the blocking version and more details.
func (c *Client) GetRawChangeAddressAsync(account string) FutureGetRawChangeAddressResult {
cmd := btcjson.NewGetRawChangeAddressCmd(&account)
return c.sendCmd(cmd)
result := FutureGetRawChangeAddressResult{
network: c.chainParams,
responseChannel: c.sendCmd(cmd),
}
return result
}
// GetRawChangeAddress returns a new address for receiving change that will be
@@ -945,12 +989,15 @@ func (c *Client) GetRawChangeAddress(account string) (btcutil.Address, error) {
// FutureAddWitnessAddressResult is a future promise to deliver the result of
// a AddWitnessAddressAsync RPC invocation (or an applicable error).
type FutureAddWitnessAddressResult chan *response
type FutureAddWitnessAddressResult struct {
responseChannel chan *response
network *chaincfg.Params
}
// Receive waits for the response promised by the future and returns the new
// address.
func (r FutureAddWitnessAddressResult) Receive() (btcutil.Address, error) {
res, err := receiveFuture(r)
res, err := receiveFuture(r.responseChannel)
if err != nil {
return nil, err
}
@@ -962,7 +1009,7 @@ func (r FutureAddWitnessAddressResult) Receive() (btcutil.Address, error) {
return nil, err
}
return btcutil.DecodeAddress(addr, &chaincfg.MainNetParams)
return btcutil.DecodeAddress(addr, r.network)
}
// AddWitnessAddressAsync returns an instance of a type that can be used to get
@@ -972,7 +1019,11 @@ func (r FutureAddWitnessAddressResult) Receive() (btcutil.Address, error) {
// See AddWitnessAddress for the blocking version and more details.
func (c *Client) AddWitnessAddressAsync(address string) FutureAddWitnessAddressResult {
cmd := btcjson.NewAddWitnessAddressCmd(address)
return c.sendCmd(cmd)
response := FutureAddWitnessAddressResult{
network: c.chainParams,
responseChannel: c.sendCmd(cmd),
}
return response
}
// AddWitnessAddress adds a witness address for a script and returns the new
@@ -983,12 +1034,15 @@ func (c *Client) AddWitnessAddress(address string) (btcutil.Address, error) {
// FutureGetAccountAddressResult is a future promise to deliver the result of a
// GetAccountAddressAsync RPC invocation (or an applicable error).
type FutureGetAccountAddressResult chan *response
type FutureGetAccountAddressResult struct {
responseChannel chan *response
network *chaincfg.Params
}
// Receive waits for the response promised by the future and returns the current
// Bitcoin address for receiving payments to the specified account.
func (r FutureGetAccountAddressResult) Receive() (btcutil.Address, error) {
res, err := receiveFuture(r)
res, err := receiveFuture(r.responseChannel)
if err != nil {
return nil, err
}
@@ -1000,7 +1054,7 @@ func (r FutureGetAccountAddressResult) Receive() (btcutil.Address, error) {
return nil, err
}
return btcutil.DecodeAddress(addr, &chaincfg.MainNetParams)
return btcutil.DecodeAddress(addr, r.network)
}
// GetAccountAddressAsync returns an instance of a type that can be used to get
@@ -1010,7 +1064,11 @@ func (r FutureGetAccountAddressResult) Receive() (btcutil.Address, error) {
// See GetAccountAddress for the blocking version and more details.
func (c *Client) GetAccountAddressAsync(account string) FutureGetAccountAddressResult {
cmd := btcjson.NewGetAccountAddressCmd(account)
return c.sendCmd(cmd)
result := FutureGetAccountAddressResult{
network: c.chainParams,
responseChannel: c.sendCmd(cmd),
}
return result
}
// GetAccountAddress returns the current Bitcoin address for receiving payments
@@ -1086,12 +1144,15 @@ func (c *Client) SetAccount(address btcutil.Address, account string) error {
// FutureGetAddressesByAccountResult is a future promise to deliver the result
// of a GetAddressesByAccountAsync RPC invocation (or an applicable error).
type FutureGetAddressesByAccountResult chan *response
type FutureGetAddressesByAccountResult struct {
responseChannel chan *response
network *chaincfg.Params
}
// Receive waits for the response promised by the future and returns the list of
// addresses associated with the passed account.
func (r FutureGetAddressesByAccountResult) Receive() ([]btcutil.Address, error) {
res, err := receiveFuture(r)
res, err := receiveFuture(r.responseChannel)
if err != nil {
return nil, err
}
@@ -1103,17 +1164,15 @@ func (r FutureGetAddressesByAccountResult) Receive() ([]btcutil.Address, error)
return nil, err
}
addrs := make([]btcutil.Address, 0, len(addrStrings))
for _, addrStr := range addrStrings {
addr, err := btcutil.DecodeAddress(addrStr,
&chaincfg.MainNetParams)
addresses := make([]btcutil.Address, len(addrStrings))
for i, addrString := range addrStrings {
addresses[i], err = btcutil.DecodeAddress(addrString, r.network)
if err != nil {
return nil, err
}
addrs = append(addrs, addr)
}
return addrs, nil
return addresses, nil
}
// GetAddressesByAccountAsync returns an instance of a type that can be used to
@@ -1123,7 +1182,11 @@ func (r FutureGetAddressesByAccountResult) Receive() ([]btcutil.Address, error)
// See GetAddressesByAccount for the blocking version and more details.
func (c *Client) GetAddressesByAccountAsync(account string) FutureGetAddressesByAccountResult {
cmd := btcjson.NewGetAddressesByAccountCmd(account)
return c.sendCmd(cmd)
result := FutureGetAddressesByAccountResult{
network: c.chainParams,
responseChannel: c.sendCmd(cmd),
}
return result
}
// GetAddressesByAccount returns the list of addresses associated with the
@@ -1507,6 +1570,43 @@ func (c *Client) GetBalanceMinConf(account string, minConfirms int) (btcutil.Amo
return c.GetBalanceMinConfAsync(account, minConfirms).Receive()
}
// FutureGetBalancesResult is a future promise to deliver the result of a
// GetBalancesAsync RPC invocation (or an applicable error).
type FutureGetBalancesResult chan *response
// Receive waits for the response promised by the future and returns the
// available balances from the server.
func (r FutureGetBalancesResult) Receive() (*btcjson.GetBalancesResult, error) {
res, err := receiveFuture(r)
if err != nil {
return nil, err
}
// Unmarshal result as a floating point number.
var balances btcjson.GetBalancesResult
err = json.Unmarshal(res, &balances)
if err != nil {
return nil, err
}
return &balances, nil
}
// GetBalancesAsync 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 GetBalances for the blocking version and more details.
func (c *Client) GetBalancesAsync() FutureGetBalancesResult {
cmd := btcjson.NewGetBalancesCmd()
return c.sendCmd(cmd)
}
// GetBalances returns the available balances from the server.
func (c *Client) GetBalances() (*btcjson.GetBalancesResult, error) {
return c.GetBalancesAsync().Receive()
}
// FutureGetReceivedByAccountResult is a future promise to deliver the result of
// a GetReceivedByAccountAsync or GetReceivedByAccountMinConfAsync RPC
// invocation (or an applicable error).

View File

@@ -586,7 +586,7 @@ func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error {
if vm.hasFlag(ScriptVerifyWitnessPubKeyType) &&
vm.isWitnessVersionActive(0) && !btcec.IsCompressedPubKey(pubKey) {
str := "only uncompressed keys are accepted post-segwit"
str := "only compressed keys are accepted post-segwit"
return scriptError(ErrWitnessPubKeyType, str)
}