Skip to content

Commit

Permalink
Refactor GetBlockVerboseTx to reflect correct getblock RPC call… (#1529)
Browse files Browse the repository at this point in the history
Refactor GetBlockVerboseResult into two separate types: one type for getblock "hash" verbosity=1,
and a second type for getblock "hash" verbosity=2. This is necessary due to how getblock returns
a block's transaction data based on the provided verbosity parameter.

If verbosity=1, then getblock.Tx is an array of a block's transaction ids (txids) as strings.
If verbosity=2, then getblock.Tx is an array of raw transaction data.

Due to differences in how getblock returns data based on the provided verbosity parameter, it's necessary
to have two separate return types based on verbosity. This necessitates a separate unmarshalling function
(represented throughout rpcclient/chain.go as Result.Receive()) to ensure that data is correctly unmarshalled
and returned to the user.
  • Loading branch information
jakesylvestre authored Mar 9, 2020
2 parents fd0921b + a310aa6 commit c4f3999
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 47 deletions.
8 changes: 3 additions & 5 deletions btcjson/chainsvrcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,20 +130,18 @@ func NewGetBestBlockHashCmd() *GetBestBlockHashCmd {
// GetBlockCmd defines the getblock JSON-RPC command.
type GetBlockCmd struct {
Hash string
Verbose *bool `jsonrpcdefault:"true"`
VerboseTx *bool `jsonrpcdefault:"false"`
Verbosity *int `jsonrpcdefault:"0"`
}

// NewGetBlockCmd returns a new instance which can be used to issue a getblock
// JSON-RPC command.
//
// The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value.
func NewGetBlockCmd(hash string, verbose, verboseTx *bool) *GetBlockCmd {
func NewGetBlockCmd(hash string, verbosity *int) *GetBlockCmd {
return &GetBlockCmd{
Hash: hash,
Verbose: verbose,
VerboseTx: verboseTx,
Verbosity: verbosity,
}
}

Expand Down
31 changes: 12 additions & 19 deletions btcjson/chainsvrcmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,50 +142,43 @@ func TestChainSvrCmds(t *testing.T) {
{
name: "getblock",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getblock", "123")
return btcjson.NewCmd("getblock", "123", btcjson.Int(0))
},
staticCmd: func() interface{} {
return btcjson.NewGetBlockCmd("123", nil, nil)
return btcjson.NewGetBlockCmd("123", btcjson.Int(0))
},
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123"],"id":1}`,
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",0],"id":1}`,
unmarshalled: &btcjson.GetBlockCmd{
Hash: "123",
Verbose: btcjson.Bool(true),
VerboseTx: btcjson.Bool(false),
Verbosity: btcjson.Int(0),
},
},
{
name: "getblock required optional1",
newCmd: func() (interface{}, error) {
// Intentionally use a source param that is
// more pointers than the destination to
// exercise that path.
verbosePtr := btcjson.Bool(true)
return btcjson.NewCmd("getblock", "123", &verbosePtr)
return btcjson.NewCmd("getblock", "123", btcjson.Int(1))
},
staticCmd: func() interface{} {
return btcjson.NewGetBlockCmd("123", btcjson.Bool(true), nil)
return btcjson.NewGetBlockCmd("123", btcjson.Int(1))
},
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",true],"id":1}`,
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",1],"id":1}`,
unmarshalled: &btcjson.GetBlockCmd{
Hash: "123",
Verbose: btcjson.Bool(true),
VerboseTx: btcjson.Bool(false),
Verbosity: btcjson.Int(1),
},
},
{
name: "getblock required optional2",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getblock", "123", true, true)
return btcjson.NewCmd("getblock", "123", btcjson.Int(2))
},
staticCmd: func() interface{} {
return btcjson.NewGetBlockCmd("123", btcjson.Bool(true), btcjson.Bool(true))
return btcjson.NewGetBlockCmd("123", btcjson.Int(2))
},
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",true,true],"id":1}`,
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",2],"id":1}`,
unmarshalled: &btcjson.GetBlockCmd{
Hash: "123",
Verbose: btcjson.Bool(true),
VerboseTx: btcjson.Bool(true),
Verbosity: btcjson.Int(2),
},
},
{
Expand Down
34 changes: 31 additions & 3 deletions btcjson/chainsvrresults.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ type GetBlockHeaderVerboseResult struct {
}

// GetBlockVerboseResult models the data from the getblock command when the
// verbose flag is set. When the verbose flag is not set, getblock returns a
// hex-encoded string.
// 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
// whose tx field is an array of transaction hashes. When the verbose flag is set to 2,
// getblock returns an object whose tx field is an array of raw transactions.
// Use GetBlockVerboseTxResult to unmarshal data received from passing verbose=2 to getblock.
type GetBlockVerboseResult struct {
Hash string `json:"hash"`
Confirmations int64 `json:"confirmations"`
Expand All @@ -38,7 +41,32 @@ type GetBlockVerboseResult struct {
VersionHex string `json:"versionHex"`
MerkleRoot string `json:"merkleroot"`
Tx []string `json:"tx,omitempty"`
RawTx []TxRawResult `json:"rawtx,omitempty"`
RawTx []TxRawResult `json:"rawtx,omitempty"` // Note: this field is always empty when verbose != 2.
Time int64 `json:"time"`
Nonce uint32 `json:"nonce"`
Bits string `json:"bits"`
Difficulty float64 `json:"difficulty"`
PreviousHash string `json:"previousblockhash"`
NextHash string `json:"nextblockhash,omitempty"`
}

// GetBlockVerboseTxResult models the data from the getblock command when the
// verbose flag is set to 2. 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
// whose tx field is an array of transaction hashes. When the verbose flag is set to 2,
// getblock returns an object whose tx field is an array of raw transactions.
// Use GetBlockVerboseResult to unmarshal data received from passing verbose=1 to getblock.
type GetBlockVerboseTxResult struct {
Hash string `json:"hash"`
Confirmations int64 `json:"confirmations"`
StrippedSize int32 `json:"strippedsize"`
Size int32 `json:"size"`
Weight int32 `json:"weight"`
Height int64 `json:"height"`
Version int32 `json:"version"`
VersionHex string `json:"versionHex"`
MerkleRoot string `json:"merkleroot"`
Tx []TxRawResult `json:"tx,omitempty"`
Time int64 `json:"time"`
Nonce uint32 `json:"nonce"`
Bits string `json:"bits"`
Expand Down
2 changes: 1 addition & 1 deletion btcjson/cmdinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func TestMethodUsageText(t *testing.T) {
{
name: "getblock",
method: "getblock",
expected: `getblock "hash" (verbose=true verbosetx=false)`,
expected: `getblock "hash" (verbosity=0)`,
},
}

Expand Down
12 changes: 5 additions & 7 deletions btcjson/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func ExampleMarshalCmd() {
// convenience function for creating a pointer out of a primitive for
// optional parameters.
blockHash := "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
gbCmd := btcjson.NewGetBlockCmd(blockHash, btcjson.Bool(false), nil)
gbCmd := btcjson.NewGetBlockCmd(blockHash, btcjson.Int(0))

// Marshal the command to the format suitable for sending to the RPC
// server. Typically the client would increment the id here which is
Expand All @@ -38,15 +38,15 @@ func ExampleMarshalCmd() {
fmt.Printf("%s\n", marshalledBytes)

// Output:
// {"jsonrpc":"1.0","method":"getblock","params":["000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",false],"id":1}
// {"jsonrpc":"1.0","method":"getblock","params":["000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",0],"id":1}
}

// This example demonstrates how to unmarshal a JSON-RPC request and then
// unmarshal the concrete request into a concrete command.
func ExampleUnmarshalCmd() {
// Ordinarily this would be read from the wire, but for this example,
// it is hard coded here for clarity.
data := []byte(`{"jsonrpc":"1.0","method":"getblock","params":["000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",false],"id":1}`)
data := []byte(`{"jsonrpc":"1.0","method":"getblock","params":["000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",0],"id":1}`)

// Unmarshal the raw bytes from the wire into a JSON-RPC request.
var request btcjson.Request
Expand Down Expand Up @@ -84,13 +84,11 @@ func ExampleUnmarshalCmd() {

// Display the fields in the concrete command.
fmt.Println("Hash:", gbCmd.Hash)
fmt.Println("Verbose:", *gbCmd.Verbose)
fmt.Println("VerboseTx:", *gbCmd.VerboseTx)
fmt.Println("Verbosity:", *gbCmd.Verbosity)

// Output:
// Hash: 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
// Verbose: false
// VerboseTx: false
// Verbosity: 0
}

// This example demonstrates how to marshal a JSON-RPC response.
Expand Down
32 changes: 26 additions & 6 deletions rpcclient/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func (c *Client) GetBlockAsync(blockHash *chainhash.Hash) FutureGetBlockResult {
hash = blockHash.String()
}

cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(false), nil)
cmd := btcjson.NewGetBlockCmd(hash, nil)
return c.sendCmd(cmd)
}

Expand Down Expand Up @@ -140,8 +140,9 @@ func (c *Client) GetBlockVerboseAsync(blockHash *chainhash.Hash) FutureGetBlockV
if blockHash != nil {
hash = blockHash.String()
}

cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(true), nil)
// From the bitcoin-cli getblock documentation:
// "If verbosity is 1, returns an Object with information about block ."
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Int(1))
return c.sendCmd(cmd)
}

Expand All @@ -154,18 +155,37 @@ func (c *Client) GetBlockVerbose(blockHash *chainhash.Hash) (*btcjson.GetBlockVe
return c.GetBlockVerboseAsync(blockHash).Receive()
}

type FutureGetBlockVerboseTxResult chan *response

func (r FutureGetBlockVerboseTxResult) Receive() (*btcjson.GetBlockVerboseTxResult, error) {
res, err := receiveFuture(r)
if err != nil {
return nil, err
}

var blockResult btcjson.GetBlockVerboseTxResult
err = json.Unmarshal(res, &blockResult)
if err != nil {
return nil, err
}

return &blockResult, nil
}

// GetBlockVerboseTxAsync 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 GetBlockVerboseTx or the blocking version and more details.
func (c *Client) GetBlockVerboseTxAsync(blockHash *chainhash.Hash) FutureGetBlockVerboseResult {
func (c *Client) GetBlockVerboseTxAsync(blockHash *chainhash.Hash) FutureGetBlockVerboseTxResult {
hash := ""
if blockHash != nil {
hash = blockHash.String()
}
// From the bitcoin-cli getblock documentation:
// "If verbosity is 2, returns an Object with information about block and information about each transaction."
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Int(2))

cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(true), btcjson.Bool(true))
return c.sendCmd(cmd)
}

Expand All @@ -174,7 +194,7 @@ func (c *Client) GetBlockVerboseTxAsync(blockHash *chainhash.Hash) FutureGetBloc
//
// See GetBlockVerbose if only transaction hashes are preferred.
// See GetBlock to retrieve a raw block instead.
func (c *Client) GetBlockVerboseTx(blockHash *chainhash.Hash) (*btcjson.GetBlockVerboseResult, error) {
func (c *Client) GetBlockVerboseTx(blockHash *chainhash.Hash) (*btcjson.GetBlockVerboseTxResult, error) {
return c.GetBlockVerboseTxAsync(blockHash).Receive()
}

Expand Down
4 changes: 2 additions & 2 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1084,7 +1084,7 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i

// When the verbose flag isn't set, simply return the serialized block
// as a hex-encoded string.
if c.Verbose != nil && !*c.Verbose {
if c.Verbosity != nil && *c.Verbosity == 0 {
return hex.EncodeToString(blkBytes), nil
}

Expand Down Expand Up @@ -1137,7 +1137,7 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i
NextHash: nextHashString,
}

if c.VerboseTx == nil || !*c.VerboseTx {
if *c.Verbosity == 1 {
transactions := blk.Transactions()
txNames := make([]string, len(transactions))
for i, tx := range transactions {
Expand Down
7 changes: 3 additions & 4 deletions rpcserverhelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,9 @@ var helpDescsEnUS = map[string]string{
// GetBlockCmd help.
"getblock--synopsis": "Returns information about a block given its hash.",
"getblock-hash": "The hash of the block",
"getblock-verbose": "Specifies the block is returned as a JSON object instead of hex-encoded string",
"getblock-verbosetx": "Specifies that each transaction is returned as a JSON object and only applies if the verbose flag is true (btcd extension)",
"getblock--condition0": "verbose=false",
"getblock--condition1": "verbose=true",
"getblock-verbosity": "Specifies whether the block data should be returned as a hex-encoded string (0), as parsed data with a slice of TXIDs (1), or as parsed data with parsed transaction data (2) ",
"getblock--condition0": "verbosity=0",
"getblock--condition1": "verbosity=1",
"getblock--result0": "Hex-encoded bytes of the serialized block",

// GetBlockChainInfoCmd help.
Expand Down

0 comments on commit c4f3999

Please sign in to comment.