From 280648ccf3777d56ac8de8d7cd8fd4283c9be4a6 Mon Sep 17 00:00:00 2001 From: Anirudha Bose Date: Mon, 28 Sep 2020 14:42:18 +0200 Subject: [PATCH] rpcclient: implement gettxoutsetinfo command --- btcjson/chainsvrresults.go | 52 +++++++++++++++++++++++++++ btcjson/walletsvrresults_test.go | 62 ++++++++++++++++++++++++++++++++ rpcclient/chain.go | 38 ++++++++++++++++++++ rpcclient/example_test.go | 19 ++++++++++ 4 files changed, 171 insertions(+) diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 9cafe33494..5b6cbdd57d 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -9,6 +9,8 @@ import ( "encoding/hex" "encoding/json" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" ) @@ -436,6 +438,56 @@ type GetTxOutResult struct { Coinbase bool `json:"coinbase"` } +// GetTxOutSetInfoResult models the data from the gettxoutsetinfo command. +type GetTxOutSetInfoResult struct { + Height int64 `json:"height"` + BestBlock chainhash.Hash `json:"bestblock"` + Transactions int64 `json:"transactions"` + TxOuts int64 `json:"txouts"` + BogoSize int64 `json:"bogosize"` + HashSerialized string `json:"hash_serialized_2"` + DiskSize int64 `json:"disk_size"` + TotalAmount btcutil.Amount `json:"total_amount"` +} + +// UnmarshalJSON unmarshals the result of the gettxoutsetinfo JSON-RPC call +func (g *GetTxOutSetInfoResult) UnmarshalJSON(data []byte) error { + // Step 1: Create type aliases of the original struct. + type Alias GetTxOutSetInfoResult + + // Step 2: Create an anonymous struct with raw replacements for the special + // fields. + aux := &struct { + BestBlock string `json:"bestblock"` + TotalAmount float64 `json:"total_amount"` + *Alias + }{ + Alias: (*Alias)(g), + } + + // 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 + hash, err := chainhash.NewHashFromStr(aux.BestBlock) + if err != nil { + return err + } + + g.BestBlock = *hash + + amount, err := btcutil.NewAmount(aux.TotalAmount) + if err != nil { + return err + } + + g.TotalAmount = amount + + return nil +} + // GetNetTotalsResult models the data returned from the getnettotals command. type GetNetTotalsResult struct { TotalBytesRecv uint64 `json:"totalbytesrecv"` diff --git a/btcjson/walletsvrresults_test.go b/btcjson/walletsvrresults_test.go index fd44b066b8..d5b5b99daa 100644 --- a/btcjson/walletsvrresults_test.go +++ b/btcjson/walletsvrresults_test.go @@ -10,6 +10,9 @@ import ( "reflect" "testing" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/txscript" "github.com/davecgh/go-spew/spew" ) @@ -125,3 +128,62 @@ func TestGetWalletInfoResult(t *testing.T) { } } } + +// TestGetTxOutSetInfoResult ensures that custom unmarshalling of +// GetTxOutSetInfoResult works as intended. +func TestGetTxOutSetInfoResult(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + result string + want GetTxOutSetInfoResult + }{ + { + name: "GetWalletInfoResult - not scanning", + result: `{"height":123,"bestblock":"000000000000005f94116250e2407310463c0a7cf950f1af9ebe935b1c0687ab","transactions":1,"txouts":1,"bogosize":1,"hash_serialized_2":"0000","disk_size":1,"total_amount":0.2}`, + want: GetTxOutSetInfoResult{ + Height: 123, + BestBlock: func() chainhash.Hash { + h, err := chainhash.NewHashFromStr("000000000000005f94116250e2407310463c0a7cf950f1af9ebe935b1c0687ab") + if err != nil { + panic(err) + } + + return *h + }(), + Transactions: 1, + TxOuts: 1, + BogoSize: 1, + HashSerialized: "0000", + DiskSize: 1, + TotalAmount: func() btcutil.Amount { + a, err := btcutil.NewAmount(0.2) + if err != nil { + panic(err) + } + + return a + }(), + }, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + var out GetTxOutSetInfoResult + 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/chain.go b/rpcclient/chain.go index 35adbc1000..d478da7a00 100644 --- a/rpcclient/chain.go +++ b/rpcclient/chain.go @@ -1026,6 +1026,44 @@ func (c *Client) GetTxOut(txHash *chainhash.Hash, index uint32, mempool bool) (* return c.GetTxOutAsync(txHash, index, mempool).Receive() } +// FutureGetTxOutSetInfoResult is a future promise to deliver the result of a +// GetTxOutSetInfoAsync RPC invocation (or an applicable error). +type FutureGetTxOutSetInfoResult chan *response + +// Receive waits for the response promised by the future and returns the +// results of GetTxOutSetInfoAsync RPC invocation. +func (r FutureGetTxOutSetInfoResult) Receive() (*btcjson.GetTxOutSetInfoResult, error) { + res, err := receiveFuture(r) + if err != nil { + return nil, err + } + + // Unmarshal result as an gettxoutsetinfo result object. + var txOutSetInfo *btcjson.GetTxOutSetInfoResult + err = json.Unmarshal(res, &txOutSetInfo) + if err != nil { + return nil, err + } + + return txOutSetInfo, nil +} + +// GetTxOutSetInfoAsync 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 GetTxOutSetInfo for the blocking version and more details. +func (c *Client) GetTxOutSetInfoAsync() FutureGetTxOutSetInfoResult { + cmd := btcjson.NewGetTxOutSetInfoCmd() + return c.sendCmd(cmd) +} + +// GetTxOutSetInfo returns the statistics about the unspent transaction output +// set. +func (c *Client) GetTxOutSetInfo() (*btcjson.GetTxOutSetInfoResult, error) { + return c.GetTxOutSetInfoAsync().Receive() +} + // FutureRescanBlocksResult is a future promise to deliver the result of a // RescanBlocksAsync RPC invocation (or an applicable error). // diff --git a/rpcclient/example_test.go b/rpcclient/example_test.go index ba8bb5ecb5..35159317b5 100644 --- a/rpcclient/example_test.go +++ b/rpcclient/example_test.go @@ -116,3 +116,22 @@ func ExampleClient_GetWalletInfo() { fmt.Println(*info.HDSeedID) // eb44e4e9b864ef17e7ba947da746375b000f5d94 fmt.Println(info.Scanning.Value) // false } + +func ExampleClient_GetTxOutSetInfo() { + client, err := New(connCfg, nil) + if err != nil { + panic(err) + } + defer client.Shutdown() + + r, err := client.GetTxOutSetInfo() + if err != nil { + panic(err) + } + + fmt.Println(r.TotalAmount.String()) // 20947654.56996054 BTC + fmt.Println(r.BestBlock.String()) // 000000000000005f94116250e2407310463c0a7cf950f1af9ebe935b1c0687ab + fmt.Println(r.TxOuts) // 24280607 + fmt.Println(r.Transactions) // 9285603 + fmt.Println(r.DiskSize) // 1320871611 +}