From e9a51e8dcd673c563a549676ebd4384b0318a9b7 Mon Sep 17 00:00:00 2001 From: Anirudha Bose Date: Sun, 20 Sep 2020 15:46:32 +0200 Subject: [PATCH] rpcclient: implement getwalletinfo command --- btcjson/walletsvrresults.go | 54 ++++++++++++++++++++++++++++++++ btcjson/walletsvrresults_test.go | 47 +++++++++++++++++++++++++++ rpcclient/example_test.go | 26 ++++++++++----- rpcclient/wallet.go | 36 ++++++++++++++++++++- 4 files changed, 154 insertions(+), 9 deletions(-) diff --git a/btcjson/walletsvrresults.go b/btcjson/walletsvrresults.go index 1b9393ab..2569dde4 100644 --- a/btcjson/walletsvrresults.go +++ b/btcjson/walletsvrresults.go @@ -6,6 +6,8 @@ package btcjson import ( "encoding/json" + "fmt" + "github.com/btcsuite/btcd/txscript" ) @@ -150,6 +152,58 @@ type GetTransactionResult struct { Hex string `json:"hex"` } +type ScanningOrFalse struct { + Value interface{} +} + +type ScanProgress struct { + Duration int `json:"duration"` + Progress float64 `json:"progress"` +} + +// MarshalJSON implements the json.Marshaler interface +func (h ScanningOrFalse) MarshalJSON() ([]byte, error) { + return json.Marshal(h.Value) +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (h *ScanningOrFalse) UnmarshalJSON(data []byte) error { + var unmarshalled interface{} + if err := json.Unmarshal(data, &unmarshalled); err != nil { + return err + } + + switch v := unmarshalled.(type) { + case bool: + h.Value = v + case map[string]interface{}: + h.Value = ScanProgress{ + Duration: int(v["duration"].(float64)), + Progress: v["progress"].(float64), + } + default: + return fmt.Errorf("invalid scanning value: %v", unmarshalled) + } + + return nil +} + +// GetWalletInfoResult models the result of the getwalletinfo command. +type GetWalletInfoResult struct { + WalletName string `json:"walletname"` + WalletVersion int `json:"walletversion"` + TransactionCount int `json:"txcount"` + KeyPoolOldest int `json:"keypoololdest"` + KeyPoolSize int `json:"keypoolsize"` + KeyPoolSizeHDInternal *int `json:"keypoolsize_hd_internal,omitempty"` + UnlockedUntil *int `json:"unlocked_until,omitempty"` + PayTransactionFee float64 `json:"paytxfee"` + HDSeedID *string `json:"hdseedid,omitempty"` + PrivateKeysEnabled bool `json:"private_keys_enabled"` + AvoidReuse bool `json:"avoid_reuse"` + Scanning ScanningOrFalse `json:"scanning"` +} + // InfoWalletResult models the data returned by the wallet server getinfo // command. type InfoWalletResult struct { diff --git a/btcjson/walletsvrresults_test.go b/btcjson/walletsvrresults_test.go index 173226b8..fd44b066 100644 --- a/btcjson/walletsvrresults_test.go +++ b/btcjson/walletsvrresults_test.go @@ -78,3 +78,50 @@ func TestGetAddressInfoResult(t *testing.T) { } } } + +// TestGetWalletInfoResult ensures that custom unmarshalling of +// GetWalletInfoResult works as intended. +func TestGetWalletInfoResult(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + result string + want GetWalletInfoResult + }{ + { + name: "GetWalletInfoResult - not scanning", + result: `{"scanning":false}`, + want: GetWalletInfoResult{ + Scanning: ScanningOrFalse{Value: false}, + }, + }, + { + name: "GetWalletInfoResult - scanning", + result: `{"scanning":{"duration":10,"progress":1.0}}`, + want: GetWalletInfoResult{ + Scanning: ScanningOrFalse{ + Value: ScanProgress{Duration: 10, Progress: 1.0}, + }, + }, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + var out GetWalletInfoResult + err := json.Unmarshal([]byte(test.result), &out) + if err != nil { + t.Errorf("Test #%d (%s) unexpected error: %v", i, + test.name, err) + 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 + } + } +} diff --git a/rpcclient/example_test.go b/rpcclient/example_test.go index e930778a..ba8bb5ec 100644 --- a/rpcclient/example_test.go +++ b/rpcclient/example_test.go @@ -82,14 +82,6 @@ func ExampleClient_DeriveAddresses() { } 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) @@ -106,3 +98,21 @@ func ExampleClient_GetAddressInfo() { fmt.Println(*info.HDKeyPath) // m/49'/1'/0'/0/4 fmt.Println(info.Embedded.Address) // tb1q3x2h2kh57wzg7jz00jhwn0ycvqtdk2ane37j27 } + +func ExampleClient_GetWalletInfo() { + client, err := New(connCfg, nil) + if err != nil { + panic(err) + } + defer client.Shutdown() + + info, err := client.GetWalletInfo() + if err != nil { + panic(err) + } + + fmt.Println(info.WalletVersion) // 169900 + fmt.Println(info.TransactionCount) // 22 + fmt.Println(*info.HDSeedID) // eb44e4e9b864ef17e7ba947da746375b000f5d94 + fmt.Println(info.Scanning.Value) // false +} diff --git a/rpcclient/wallet.go b/rpcclient/wallet.go index 10bdfca6..3824de99 100644 --- a/rpcclient/wallet.go +++ b/rpcclient/wallet.go @@ -2573,10 +2573,44 @@ func (c *Client) WalletProcessPsbt( return c.WalletProcessPsbtAsync(psbt, sign, sighashType, bip32Derivs).Receive() } +// FutureGetWalletInfoResult is a future promise to deliver the result of an +// GetWalletInfoAsync RPC invocation (or an applicable error). +type FutureGetWalletInfoResult chan *response + +// Receive waits for the response promised by the future and returns the result +// of wallet state info. +func (r FutureGetWalletInfoResult) Receive() (*btcjson.GetWalletInfoResult, error) { + res, err := receiveFuture(r) + if err != nil { + return nil, err + } + + var getWalletInfoResult btcjson.GetWalletInfoResult + err = json.Unmarshal(res, &getWalletInfoResult) + if err != nil { + return nil, err + } + return &getWalletInfoResult, nil +} + +// GetWalletInfoAsync 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 GetWalletInfo for the blocking version and more details. +func (c *Client) GetWalletInfoAsync() FutureGetWalletInfoResult { + cmd := btcjson.NewGetWalletInfoCmd() + return c.sendCmd(cmd) +} + +// GetWalletInfo returns various wallet state info. +func (c *Client) GetWalletInfo() (*btcjson.GetWalletInfoResult, error) { + return c.GetWalletInfoAsync().Receive() +} + // TODO(davec): Implement // backupwallet (NYI in btcwallet) // encryptwallet (Won't be supported by btcwallet since it's always encrypted) -// getwalletinfo (NYI in btcwallet or btcjson) // listaddressgroupings (NYI in btcwallet) // listreceivedbyaccount (NYI in btcwallet)