rpcclient: implement getaddressinfo command
Fields such as label, and labelspurpose are not included, since they are deprecated, and will be removed in Bitcoin Core 0.21.
This commit is contained in:
parent
6daaf73544
commit
ac3f235eb9
8 changed files with 349 additions and 7 deletions
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Copyright (c) 2014-2020 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -178,6 +178,19 @@ func NewGetAddressesByAccountCmd(account string) *GetAddressesByAccountCmd {
|
|||
}
|
||||
}
|
||||
|
||||
// GetAddressInfoCmd defines the getaddressinfo JSON-RPC command.
|
||||
type GetAddressInfoCmd struct {
|
||||
Address string
|
||||
}
|
||||
|
||||
// NewGetAddressInfoCmd returns a new instance which can be used to issue a
|
||||
// getaddressinfo JSON-RPC command.
|
||||
func NewGetAddressInfoCmd(address string) *GetAddressInfoCmd {
|
||||
return &GetAddressInfoCmd{
|
||||
Address: address,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBalanceCmd defines the getbalance JSON-RPC command.
|
||||
type GetBalanceCmd struct {
|
||||
Account *string
|
||||
|
@ -993,6 +1006,7 @@ func init() {
|
|||
MustRegisterCmd("getaccount", (*GetAccountCmd)(nil), flags)
|
||||
MustRegisterCmd("getaccountaddress", (*GetAccountAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("getaddressesbyaccount", (*GetAddressesByAccountCmd)(nil), flags)
|
||||
MustRegisterCmd("getaddressinfo", (*GetAddressInfoCmd)(nil), flags)
|
||||
MustRegisterCmd("getbalance", (*GetBalanceCmd)(nil), flags)
|
||||
MustRegisterCmd("getbalances", (*GetBalancesCmd)(nil), flags)
|
||||
MustRegisterCmd("getnewaddress", (*GetNewAddressCmd)(nil), flags)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Copyright (c) 2014-2020 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -209,6 +209,19 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
Account: "acct",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "getaddressinfo",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("getaddressinfo", "1234")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGetAddressInfoCmd("1234")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getaddressinfo","params":["1234"],"id":1}`,
|
||||
unmarshalled: &btcjson.GetAddressInfoCmd{
|
||||
Address: "1234",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "getbalance",
|
||||
newCmd: func() (interface{}, error) {
|
||||
|
|
|
@ -1,9 +1,124 @@
|
|||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Copyright (c) 2014-2020 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
)
|
||||
|
||||
// embeddedAddressInfo includes all getaddressinfo output fields, excluding
|
||||
// metadata and relation to the wallet.
|
||||
//
|
||||
// It represents the non-metadata/non-wallet fields for GetAddressInfo, as well
|
||||
// as the precise fields for an embedded P2SH or P2WSH address.
|
||||
type embeddedAddressInfo struct {
|
||||
Address string `json:"address"`
|
||||
ScriptPubKey string `json:"scriptPubKey"`
|
||||
Solvable bool `json:"solvable"`
|
||||
Descriptor *string `json:"desc,omitempty"`
|
||||
IsScript bool `json:"isscript"`
|
||||
IsChange bool `json:"ischange"`
|
||||
IsWitness bool `json:"iswitness"`
|
||||
WitnessVersion int `json:"witness_version,omitempty"`
|
||||
WitnessProgram *string `json:"witness_program,omitempty"`
|
||||
ScriptType *txscript.ScriptClass `json:"script,omitempty"`
|
||||
Hex *string `json:"hex,omitempty"`
|
||||
PubKeys *[]string `json:"pubkeys,omitempty"`
|
||||
SignaturesRequired *int `json:"sigsrequired,omitempty"`
|
||||
PubKey *string `json:"pubkey,omitempty"`
|
||||
IsCompressed *bool `json:"iscompressed,omitempty"`
|
||||
HDMasterFingerprint *string `json:"hdmasterfingerprint,omitempty"`
|
||||
Labels []string `json:"labels"`
|
||||
}
|
||||
|
||||
// GetAddressInfoResult models the result of the getaddressinfo command. It
|
||||
// contains information about a bitcoin address.
|
||||
//
|
||||
// Reference: https://bitcoincore.org/en/doc/0.20.0/rpc/wallet/getaddressinfo
|
||||
//
|
||||
// The GetAddressInfoResult has three segments:
|
||||
// 1. General information about the address.
|
||||
// 2. Metadata (Timestamp, HDKeyPath, HDSeedID) and wallet fields
|
||||
// (IsMine, IsWatchOnly).
|
||||
// 3. Information about the embedded address in case of P2SH or P2WSH.
|
||||
// Same structure as (1).
|
||||
type GetAddressInfoResult struct {
|
||||
embeddedAddressInfo
|
||||
IsMine bool `json:"ismine"`
|
||||
IsWatchOnly bool `json:"iswatchonly"`
|
||||
Timestamp *int `json:"timestamp,omitempty"`
|
||||
HDKeyPath *string `json:"hdkeypath,omitempty"`
|
||||
HDSeedID *string `json:"hdseedid,omitempty"`
|
||||
Embedded *embeddedAddressInfo `json:"embedded,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON provides a custom unmarshaller for GetAddressInfoResult.
|
||||
// It is adapted to avoid creating a duplicate raw struct for unmarshalling
|
||||
// the JSON bytes into.
|
||||
//
|
||||
// Reference: http://choly.ca/post/go-json-marshalling
|
||||
func (e *GetAddressInfoResult) UnmarshalJSON(data []byte) error {
|
||||
// Step 1: Create type aliases of the original struct, including the
|
||||
// embedded one.
|
||||
type Alias GetAddressInfoResult
|
||||
type EmbeddedAlias embeddedAddressInfo
|
||||
|
||||
// Step 2: Create an anonymous struct with raw replacements for the special
|
||||
// fields.
|
||||
aux := &struct {
|
||||
ScriptType *string `json:"script,omitempty"`
|
||||
Embedded *struct {
|
||||
ScriptType *string `json:"script,omitempty"`
|
||||
*EmbeddedAlias
|
||||
} `json:"embedded,omitempty"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(e),
|
||||
}
|
||||
|
||||
// Step 3: Unmarshal the data into the anonymous struct.
|
||||
if err := json.Unmarshal(data, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Step 4: Convert the raw fields to the desired types
|
||||
var (
|
||||
sc *txscript.ScriptClass
|
||||
err error
|
||||
)
|
||||
|
||||
if aux.ScriptType != nil {
|
||||
sc, err = txscript.NewScriptClass(*aux.ScriptType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
e.ScriptType = sc
|
||||
|
||||
if aux.Embedded != nil {
|
||||
var (
|
||||
embeddedSc *txscript.ScriptClass
|
||||
err error
|
||||
)
|
||||
|
||||
if aux.Embedded.ScriptType != nil {
|
||||
embeddedSc, err = txscript.NewScriptClass(*aux.Embedded.ScriptType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
e.Embedded = (*embeddedAddressInfo)(aux.Embedded.EmbeddedAlias)
|
||||
e.Embedded.ScriptType = embeddedSc
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTransactionDetailsResult models the details data from the gettransaction command.
|
||||
//
|
||||
// This models the "short" version of the ListTransactionsResult type, which
|
||||
|
|
80
btcjson/walletsvrresults_test.go
Normal file
80
btcjson/walletsvrresults_test.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright (c) 2020 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// TestGetAddressInfoResult ensures that custom unmarshalling of
|
||||
// GetAddressInfoResult works as intended.
|
||||
func TestGetAddressInfoResult(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// arbitrary script class to use in tests
|
||||
nonStandard, _ := txscript.NewScriptClass("nonstandard")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
result string
|
||||
want GetAddressInfoResult
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "GetAddressInfoResult - no ScriptType",
|
||||
result: `{}`,
|
||||
want: GetAddressInfoResult{},
|
||||
},
|
||||
{
|
||||
name: "GetAddressInfoResult - ScriptType",
|
||||
result: `{"script":"nonstandard","address":"1abc"}`,
|
||||
want: GetAddressInfoResult{
|
||||
embeddedAddressInfo: embeddedAddressInfo{
|
||||
Address: "1abc",
|
||||
ScriptType: nonStandard,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GetAddressInfoResult - embedded ScriptType",
|
||||
result: `{"embedded": {"script":"nonstandard","address":"121313"}}`,
|
||||
want: GetAddressInfoResult{
|
||||
Embedded: &embeddedAddressInfo{
|
||||
Address: "121313",
|
||||
ScriptType: nonStandard,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GetAddressInfoResult - invalid ScriptType",
|
||||
result: `{"embedded": {"script":"foo","address":"121313"}}`,
|
||||
wantErr: txscript.ErrUnsupportedScriptType,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
var out GetAddressInfoResult
|
||||
err := json.Unmarshal([]byte(test.result), &out)
|
||||
if err != nil && !errors.Is(err, test.wantErr) {
|
||||
t.Errorf("Test #%d (%s) unexpected error: %v, want: %v", i,
|
||||
test.name, err, test.wantErr)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(out, test.want) {
|
||||
t.Errorf("Test #%d (%s) unexpected unmarshalled data - "+
|
||||
"got %v, want %v", i, test.name, spew.Sdump(out),
|
||||
spew.Sdump(test.want))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
// Copyright (c) 2020 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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
|
@ -77,3 +80,29 @@ func ExampleClient_DeriveAddresses() {
|
|||
fmt.Printf("%+v\n", addrs)
|
||||
// &[14NjenDKkGGq1McUgoSkeUHJpW3rrKLbPW 1Pn6i3cvdGhqbdgNjXHfbaYfiuviPiymXj 181x1NbgGYKLeMXkDdXEAqepG75EgU8XtG]
|
||||
}
|
||||
|
||||
func ExampleClient_GetAddressInfo() {
|
||||
connCfg = &ConnConfig{
|
||||
Host: "localhost:18332",
|
||||
User: "user",
|
||||
Pass: "pass",
|
||||
HTTPPostMode: true,
|
||||
DisableTLS: true,
|
||||
}
|
||||
|
||||
client, err := New(connCfg, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer client.Shutdown()
|
||||
|
||||
info, err := client.GetAddressInfo("2NF1FbxtUAsvdU4uW1UC2xkBVatp6cYQuJ3")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(info.Address) // 2NF1FbxtUAsvdU4uW1UC2xkBVatp6cYQuJ3
|
||||
fmt.Println(info.ScriptType.String()) // witness_v0_keyhash
|
||||
fmt.Println(*info.HDKeyPath) // m/49'/1'/0'/0/4
|
||||
fmt.Println(info.Embedded.Address) // tb1q3x2h2kh57wzg7jz00jhwn0ycvqtdk2ane37j27
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2014-2017 The btcsuite developers
|
||||
// Copyright (c) 2014-2020 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -939,6 +939,41 @@ func (c *Client) CreateNewAccount(account string) error {
|
|||
return c.CreateNewAccountAsync(account).Receive()
|
||||
}
|
||||
|
||||
// FutureGetAddressInfoResult is a future promise to deliver the result of an
|
||||
// GetAddressInfoAsync RPC invocation (or an applicable error).
|
||||
type FutureGetAddressInfoResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns the information
|
||||
// about the given bitcoin address.
|
||||
func (r FutureGetAddressInfoResult) Receive() (*btcjson.GetAddressInfoResult, error) {
|
||||
res, err := receiveFuture(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var getAddressInfoResult btcjson.GetAddressInfoResult
|
||||
err = json.Unmarshal(res, &getAddressInfoResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &getAddressInfoResult, nil
|
||||
}
|
||||
|
||||
// GetAddressInfoAsync 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 GetAddressInfo for the blocking version and more details.
|
||||
func (c *Client) GetAddressInfoAsync(address string) FutureGetAddressInfoResult {
|
||||
cmd := btcjson.NewGetAddressInfoCmd(address)
|
||||
return c.sendCmd(cmd)
|
||||
}
|
||||
|
||||
// GetAddressInfo returns information about the given bitcoin address.
|
||||
func (c *Client) GetAddressInfo(address string) (*btcjson.GetAddressInfoResult, error) {
|
||||
return c.GetAddressInfoAsync(address).Receive()
|
||||
}
|
||||
|
||||
// FutureGetNewAddressResult is a future promise to deliver the result of a
|
||||
// GetNewAddressAsync RPC invocation (or an applicable error).
|
||||
type FutureGetNewAddressResult struct {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2013-2020 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -58,6 +58,7 @@ const (
|
|||
WitnessV0ScriptHashTy // Pay to witness script hash.
|
||||
MultiSigTy // Multi signature.
|
||||
NullDataTy // Empty data-only (provably prunable).
|
||||
WitnessUnknownTy // Witness unknown
|
||||
)
|
||||
|
||||
// scriptClassToName houses the human-readable strings which describe each
|
||||
|
@ -71,6 +72,7 @@ var scriptClassToName = []string{
|
|||
WitnessV0ScriptHashTy: "witness_v0_scripthash",
|
||||
MultiSigTy: "multisig",
|
||||
NullDataTy: "nulldata",
|
||||
WitnessUnknownTy: "witness_unknown",
|
||||
}
|
||||
|
||||
// String implements the Stringer interface by returning the name of
|
||||
|
@ -188,6 +190,22 @@ func GetScriptClass(script []byte) ScriptClass {
|
|||
return typeOfScript(pops)
|
||||
}
|
||||
|
||||
// NewScriptClass returns the ScriptClass corresponding to the string name
|
||||
// provided as argument. ErrUnsupportedScriptType error is returned if the
|
||||
// name doesn't correspond to any known ScriptClass.
|
||||
//
|
||||
// Not to be confused with GetScriptClass.
|
||||
func NewScriptClass(name string) (*ScriptClass, error) {
|
||||
for i, n := range scriptClassToName {
|
||||
if n == name {
|
||||
value := ScriptClass(i)
|
||||
return &value, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: %s", ErrUnsupportedScriptType, name)
|
||||
}
|
||||
|
||||
// expectedInputs returns the number of arguments required by a script.
|
||||
// If the script is of unknown type such that the number can not be determined
|
||||
// then -1 is returned. We are an internal function and thus assume that class
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2013-2020 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -7,6 +7,7 @@ package txscript
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
@ -1213,3 +1214,40 @@ func TestNullDataScript(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewScriptClass tests whether NewScriptClass returns a valid ScriptClass.
|
||||
func TestNewScriptClass(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
scriptName string
|
||||
want *ScriptClass
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "NewScriptClass - ok",
|
||||
scriptName: NullDataTy.String(),
|
||||
want: func() *ScriptClass {
|
||||
s := NullDataTy
|
||||
return &s
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "NewScriptClass - invalid",
|
||||
scriptName: "foo",
|
||||
wantErr: ErrUnsupportedScriptType,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := NewScriptClass(tt.scriptName)
|
||||
if err != nil && !errors.Is(err, tt.wantErr) {
|
||||
t.Errorf("NewScriptClass() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewScriptClass() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue