Skip to content

Commit

Permalink
rpcclient: implement gettxoutsetinfo command
Browse files Browse the repository at this point in the history
  • Loading branch information
onyb committed Sep 28, 2020
1 parent e9a51e8 commit 7b5f3fe
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 0 deletions.
60 changes: 60 additions & 0 deletions btcjson/chainsvrresults.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"encoding/hex"
"encoding/json"

"github.com/btcsuite/btcd/chaincfg/chainhash"

"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
Expand Down Expand Up @@ -436,6 +438,64 @@ 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 chainhash.Hash `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"`
HashSerialized string `json:"hash_serialized_2"`
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
blockHash, err := chainhash.NewHashFromStr(aux.BestBlock)
if err != nil {
return err
}

g.BestBlock = *blockHash

serializedHash, err := chainhash.NewHashFromStr(aux.HashSerialized)
if err != nil {
return err
}

g.HashSerialized = *serializedHash

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"`
Expand Down
71 changes: 71 additions & 0 deletions btcjson/chainsvrresults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ package btcjson_test

import (
"encoding/json"
"reflect"
"testing"

"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew"

"github.com/btcsuite/btcd/btcjson"
)

Expand Down Expand Up @@ -86,3 +91,69 @@ func TestChainSvrCustomResults(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 btcjson.GetTxOutSetInfoResult
}{
{
name: "GetTxOutSetInfoResult - not scanning",
result: `{"height":123,"bestblock":"000000000000005f94116250e2407310463c0a7cf950f1af9ebe935b1c0687ab","transactions":1,"txouts":1,"bogosize":1,"hash_serialized_2":"9a0a561203ff052182993bc5d0cb2c620880bfafdbd80331f65fd9546c3e5c3e","disk_size":1,"total_amount":0.2}`,
want: btcjson.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: func() chainhash.Hash {
h, err := chainhash.NewHashFromStr("9a0a561203ff052182993bc5d0cb2c620880bfafdbd80331f65fd9546c3e5c3e")
if err != nil {
panic(err)
}

return *h
}(),
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 btcjson.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
}
}
}
38 changes: 38 additions & 0 deletions rpcclient/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
//
Expand Down
19 changes: 19 additions & 0 deletions rpcclient/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 7b5f3fe

Please sign in to comment.