Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rpcclient: implement getaddressinfo command #1633

Merged
merged 1 commit into from
Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion btcjson/walletsvrcmds.go
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
15 changes: 14 additions & 1 deletion btcjson/walletsvrcmds_test.go
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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) {
Expand Down
117 changes: 116 additions & 1 deletion btcjson/walletsvrresults.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down
80 changes: 80 additions & 0 deletions btcjson/walletsvrresults_test.go
Original file line number Diff line number Diff line change
@@ -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
}
}
}
31 changes: 30 additions & 1 deletion rpcclient/example_test.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand Down Expand Up @@ -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
}
37 changes: 36 additions & 1 deletion rpcclient/wallet.go
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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 {
Expand Down
Loading