diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go index 031eafd6..90ab70ec 100644 --- a/btcjson/chainsvrcmds.go +++ b/btcjson/chainsvrcmds.go @@ -191,6 +191,50 @@ func NewGetBlockHeaderCmd(hash string, verbose *bool) *GetBlockHeaderCmd { } } +// HashOrHeight defines a type that can be used as hash_or_height value in JSON-RPC commands. +type HashOrHeight struct { + Value interface{} +} + +// MarshalJSON implements the json.Marshaler interface +func (h HashOrHeight) MarshalJSON() ([]byte, error) { + return json.Marshal(h.Value) +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (h *HashOrHeight) UnmarshalJSON(data []byte) error { + var unmarshalled interface{} + if err := json.Unmarshal(data, &unmarshalled); err != nil { + return err + } + + switch v := unmarshalled.(type) { + case float64: + h.Value = int(v) + case string: + h.Value = v + default: + return fmt.Errorf("invalid hash_or_height value: %v", unmarshalled) + } + + return nil +} + +// GetBlockStatsCmd defines the getblockstats JSON-RPC command. +type GetBlockStatsCmd struct { + HashOrHeight HashOrHeight + Stats *[]string +} + +// NewGetBlockStatsCmd returns a new instance which can be used to issue a +// getblockstats JSON-RPC command. Either height or hash must be specified. +func NewGetBlockStatsCmd(hashOrHeight HashOrHeight, stats *[]string) *GetBlockStatsCmd { + return &GetBlockStatsCmd{ + HashOrHeight: hashOrHeight, + Stats: stats, + } +} + // TemplateRequest is a request object as defined in BIP22 // (https://en.bitcoin.it/wiki/BIP_0022), it is optionally provided as an // pointer argument to GetBlockTemplateCmd. @@ -798,6 +842,7 @@ func init() { MustRegisterCmd("getblockcount", (*GetBlockCountCmd)(nil), flags) MustRegisterCmd("getblockhash", (*GetBlockHashCmd)(nil), flags) MustRegisterCmd("getblockheader", (*GetBlockHeaderCmd)(nil), flags) + MustRegisterCmd("getblockstats", (*GetBlockStatsCmd)(nil), flags) MustRegisterCmd("getblocktemplate", (*GetBlockTemplateCmd)(nil), flags) MustRegisterCmd("getcfilter", (*GetCFilterCmd)(nil), flags) MustRegisterCmd("getcfilterheader", (*GetCFilterHeaderCmd)(nil), flags) diff --git a/btcjson/chainsvrcmds_test.go b/btcjson/chainsvrcmds_test.go index a861af1a..dca23328 100644 --- a/btcjson/chainsvrcmds_test.go +++ b/btcjson/chainsvrcmds_test.go @@ -228,6 +228,60 @@ func TestChainSvrCmds(t *testing.T) { Verbose: btcjson.Bool(true), }, }, + { + name: "getblockstats height", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getblockstats", btcjson.HashOrHeight{Value: 123}) + }, + staticCmd: func() interface{} { + return btcjson.NewGetBlockStatsCmd(btcjson.HashOrHeight{Value: 123}, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"getblockstats","params":[123],"id":1}`, + unmarshalled: &btcjson.GetBlockStatsCmd{ + HashOrHeight: btcjson.HashOrHeight{Value: 123}, + }, + }, + { + name: "getblockstats hash", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getblockstats", btcjson.HashOrHeight{Value: "deadbeef"}) + }, + staticCmd: func() interface{} { + return btcjson.NewGetBlockStatsCmd(btcjson.HashOrHeight{Value: "deadbeef"}, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"getblockstats","params":["deadbeef"],"id":1}`, + unmarshalled: &btcjson.GetBlockStatsCmd{ + HashOrHeight: btcjson.HashOrHeight{Value: "deadbeef"}, + }, + }, + { + name: "getblockstats height optional stats", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getblockstats", btcjson.HashOrHeight{Value: 123}, []string{"avgfee", "maxfee"}) + }, + staticCmd: func() interface{} { + return btcjson.NewGetBlockStatsCmd(btcjson.HashOrHeight{Value: 123}, &[]string{"avgfee", "maxfee"}) + }, + marshalled: `{"jsonrpc":"1.0","method":"getblockstats","params":[123,["avgfee","maxfee"]],"id":1}`, + unmarshalled: &btcjson.GetBlockStatsCmd{ + HashOrHeight: btcjson.HashOrHeight{Value: 123}, + Stats: &[]string{"avgfee", "maxfee"}, + }, + }, + { + name: "getblockstats hash optional stats", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getblockstats", btcjson.HashOrHeight{Value: "deadbeef"}, []string{"avgfee", "maxfee"}) + }, + staticCmd: func() interface{} { + return btcjson.NewGetBlockStatsCmd(btcjson.HashOrHeight{Value: "deadbeef"}, &[]string{"avgfee", "maxfee"}) + }, + marshalled: `{"jsonrpc":"1.0","method":"getblockstats","params":["deadbeef",["avgfee","maxfee"]],"id":1}`, + unmarshalled: &btcjson.GetBlockStatsCmd{ + HashOrHeight: btcjson.HashOrHeight{Value: "deadbeef"}, + Stats: &[]string{"avgfee", "maxfee"}, + }, + }, { name: "getblocktemplate", newCmd: func() (interface{}, error) { diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index a3a26c89..8cd716d4 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -24,6 +24,38 @@ type GetBlockHeaderVerboseResult struct { NextHash string `json:"nextblockhash,omitempty"` } +// GetBlockStatsResult models the data from the getblockstats command. +type GetBlockStatsResult struct { + AverageFee int64 `json:"avgfee"` + AverageFeeRate int64 `json:"avgfeerate"` + AverageTxSize int64 `json:"avgtxsize"` + FeeratePercentiles []int64 `json:"feerate_percentiles"` + Hash string `json:"blockhash"` + Height int64 `json:"height"` + Ins int64 `json:"ins"` + MaxFee int64 `json:"maxfee"` + MaxFeeRate int64 `json:"maxfeerate"` + MaxTxSize int64 `json:"maxtxsize"` + MedianFee int64 `json:"medianfee"` + MedianTime int64 `json:"mediantime"` + MedianTxSize int64 `json:"mediantxsize"` + MinFee int64 `json:"minfee"` + MinFeeRate int64 `json:"minfeerate"` + MinTxSize int64 `json:"mintxsize"` + Outs int64 `json:"outs"` + SegWitTotalSize int64 `json:"swtotal_size"` + SegWitTotalWeight int64 `json:"swtotal_weight"` + SegWitTxs int64 `json:"swtxs"` + Subsidy int64 `json:"subsidy"` + Time int64 `json:"time"` + TotalOut int64 `json:"total_out"` + TotalSize int64 `json:"total_size"` + TotalWeight int64 `json:"total_weight"` + Txs int64 `json:"txs"` + UTXOIncrease int64 `json:"utxo_increase"` + UTXOSizeIncrease int64 `json:"utxo_size_inc"` +} + // GetBlockVerboseResult models the data from the getblock command when the // verbose flag is set to 1. When the verbose flag is set to 0, getblock returns a // hex-encoded string. When the verbose flag is set to 1, getblock returns an object diff --git a/rpcclient/chain.go b/rpcclient/chain.go index c656a19a..707978ca 100644 --- a/rpcclient/chain.go +++ b/rpcclient/chain.go @@ -1037,3 +1037,44 @@ func (c *Client) GetCFilterHeader(blockHash *chainhash.Hash, filterType wire.FilterType) (*wire.MsgCFHeaders, error) { return c.GetCFilterHeaderAsync(blockHash, filterType).Receive() } + +// FutureGetBlockStatsResult is a future promise to deliver the result of a +// GetBlockStatsAsync RPC invocation (or an applicable error). +type FutureGetBlockStatsResult chan *response + +// Receive waits for the response promised by the future and returns statistics +// of a block at a certain height. +func (r FutureGetBlockStatsResult) Receive() (*btcjson.GetBlockStatsResult, error) { + res, err := receiveFuture(r) + if err != nil { + return nil, err + } + + var blockStats btcjson.GetBlockStatsResult + err = json.Unmarshal(res, &blockStats) + if err != nil { + return nil, err + } + + return &blockStats, nil +} + +// GetBlockStatsAsync 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 GetBlockStats or the blocking version and more details. +func (c *Client) GetBlockStatsAsync(hashOrHeight interface{}, stats *[]string) FutureGetBlockStatsResult { + if hash, ok := hashOrHeight.(*chainhash.Hash); ok { + hashOrHeight = hash.String() + } + + cmd := btcjson.NewGetBlockStatsCmd(btcjson.HashOrHeight{Value: hashOrHeight}, stats) + return c.sendCmd(cmd) +} + +// GetBlockStats returns block statistics. First argument specifies height or hash of the target block. +// Second argument allows to select certain stats to return. +func (c *Client) GetBlockStats(hashOrHeight interface{}, stats *[]string) (*btcjson.GetBlockStatsResult, error) { + return c.GetBlockStatsAsync(hashOrHeight, stats).Receive() +}