Refactor GetBlockVerboseTx to reflect correct getblock RPC call… (#1529)

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.
This commit is contained in:
Jake Sylvestre 2020-03-09 14:47:11 -04:00 committed by GitHub
commit c4f39996ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 47 deletions

View file

@ -130,8 +130,7 @@ func NewGetBestBlockHashCmd() *GetBestBlockHashCmd {
// GetBlockCmd defines the getblock JSON-RPC command. // GetBlockCmd defines the getblock JSON-RPC command.
type GetBlockCmd struct { type GetBlockCmd struct {
Hash string Hash string
Verbose *bool `jsonrpcdefault:"true"` Verbosity *int `jsonrpcdefault:"0"`
VerboseTx *bool `jsonrpcdefault:"false"`
} }
// NewGetBlockCmd returns a new instance which can be used to issue a getblock // NewGetBlockCmd returns a new instance which can be used to issue a getblock
@ -139,11 +138,10 @@ type GetBlockCmd struct {
// //
// The parameters which are pointers indicate they are optional. Passing nil // The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value. // 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{ return &GetBlockCmd{
Hash: hash, Hash: hash,
Verbose: verbose, Verbosity: verbosity,
VerboseTx: verboseTx,
} }
} }

View file

@ -142,50 +142,43 @@ func TestChainSvrCmds(t *testing.T) {
{ {
name: "getblock", name: "getblock",
newCmd: func() (interface{}, error) { newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getblock", "123") return btcjson.NewCmd("getblock", "123", btcjson.Int(0))
}, },
staticCmd: func() interface{} { 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{ unmarshalled: &btcjson.GetBlockCmd{
Hash: "123", Hash: "123",
Verbose: btcjson.Bool(true), Verbosity: btcjson.Int(0),
VerboseTx: btcjson.Bool(false),
}, },
}, },
{ {
name: "getblock required optional1", name: "getblock required optional1",
newCmd: func() (interface{}, error) { newCmd: func() (interface{}, error) {
// Intentionally use a source param that is return btcjson.NewCmd("getblock", "123", btcjson.Int(1))
// more pointers than the destination to
// exercise that path.
verbosePtr := btcjson.Bool(true)
return btcjson.NewCmd("getblock", "123", &verbosePtr)
}, },
staticCmd: func() interface{} { 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{ unmarshalled: &btcjson.GetBlockCmd{
Hash: "123", Hash: "123",
Verbose: btcjson.Bool(true), Verbosity: btcjson.Int(1),
VerboseTx: btcjson.Bool(false),
}, },
}, },
{ {
name: "getblock required optional2", name: "getblock required optional2",
newCmd: func() (interface{}, error) { newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getblock", "123", true, true) return btcjson.NewCmd("getblock", "123", btcjson.Int(2))
}, },
staticCmd: func() interface{} { 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{ unmarshalled: &btcjson.GetBlockCmd{
Hash: "123", Hash: "123",
Verbose: btcjson.Bool(true), Verbosity: btcjson.Int(2),
VerboseTx: btcjson.Bool(true),
}, },
}, },
{ {

View file

@ -25,8 +25,11 @@ type GetBlockHeaderVerboseResult struct {
} }
// GetBlockVerboseResult models the data from the getblock command when the // GetBlockVerboseResult models the data from the getblock command when the
// verbose flag is set. When the verbose flag is not set, getblock returns a // verbose flag is set to 1. When the verbose flag is set to 0, getblock returns a
// hex-encoded string. // 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 { type GetBlockVerboseResult struct {
Hash string `json:"hash"` Hash string `json:"hash"`
Confirmations int64 `json:"confirmations"` Confirmations int64 `json:"confirmations"`
@ -38,7 +41,32 @@ type GetBlockVerboseResult struct {
VersionHex string `json:"versionHex"` VersionHex string `json:"versionHex"`
MerkleRoot string `json:"merkleroot"` MerkleRoot string `json:"merkleroot"`
Tx []string `json:"tx,omitempty"` 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"` Time int64 `json:"time"`
Nonce uint32 `json:"nonce"` Nonce uint32 `json:"nonce"`
Bits string `json:"bits"` Bits string `json:"bits"`

View file

@ -151,7 +151,7 @@ func TestMethodUsageText(t *testing.T) {
{ {
name: "getblock", name: "getblock",
method: "getblock", method: "getblock",
expected: `getblock "hash" (verbose=true verbosetx=false)`, expected: `getblock "hash" (verbosity=0)`,
}, },
} }

View file

@ -21,7 +21,7 @@ func ExampleMarshalCmd() {
// convenience function for creating a pointer out of a primitive for // convenience function for creating a pointer out of a primitive for
// optional parameters. // optional parameters.
blockHash := "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" 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 // Marshal the command to the format suitable for sending to the RPC
// server. Typically the client would increment the id here which is // server. Typically the client would increment the id here which is
@ -38,7 +38,7 @@ func ExampleMarshalCmd() {
fmt.Printf("%s\n", marshalledBytes) fmt.Printf("%s\n", marshalledBytes)
// Output: // 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 // This example demonstrates how to unmarshal a JSON-RPC request and then
@ -46,7 +46,7 @@ func ExampleMarshalCmd() {
func ExampleUnmarshalCmd() { func ExampleUnmarshalCmd() {
// Ordinarily this would be read from the wire, but for this example, // Ordinarily this would be read from the wire, but for this example,
// it is hard coded here for clarity. // 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. // Unmarshal the raw bytes from the wire into a JSON-RPC request.
var request btcjson.Request var request btcjson.Request
@ -84,13 +84,11 @@ func ExampleUnmarshalCmd() {
// Display the fields in the concrete command. // Display the fields in the concrete command.
fmt.Println("Hash:", gbCmd.Hash) fmt.Println("Hash:", gbCmd.Hash)
fmt.Println("Verbose:", *gbCmd.Verbose) fmt.Println("Verbosity:", *gbCmd.Verbosity)
fmt.Println("VerboseTx:", *gbCmd.VerboseTx)
// Output: // Output:
// Hash: 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f // Hash: 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
// Verbose: false // Verbosity: 0
// VerboseTx: false
} }
// This example demonstrates how to marshal a JSON-RPC response. // This example demonstrates how to marshal a JSON-RPC response.

View file

@ -97,7 +97,7 @@ func (c *Client) GetBlockAsync(blockHash *chainhash.Hash) FutureGetBlockResult {
hash = blockHash.String() hash = blockHash.String()
} }
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(false), nil) cmd := btcjson.NewGetBlockCmd(hash, nil)
return c.sendCmd(cmd) return c.sendCmd(cmd)
} }
@ -140,8 +140,9 @@ func (c *Client) GetBlockVerboseAsync(blockHash *chainhash.Hash) FutureGetBlockV
if blockHash != nil { if blockHash != nil {
hash = blockHash.String() hash = blockHash.String()
} }
// From the bitcoin-cli getblock documentation:
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(true), nil) // "If verbosity is 1, returns an Object with information about block ."
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Int(1))
return c.sendCmd(cmd) return c.sendCmd(cmd)
} }
@ -154,18 +155,37 @@ func (c *Client) GetBlockVerbose(blockHash *chainhash.Hash) (*btcjson.GetBlockVe
return c.GetBlockVerboseAsync(blockHash).Receive() 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 // 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 result of the RPC at some future time by invoking the Receive function on
// the returned instance. // the returned instance.
// //
// See GetBlockVerboseTx or the blocking version and more details. // 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 := "" hash := ""
if blockHash != nil { if blockHash != nil {
hash = blockHash.String() 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) return c.sendCmd(cmd)
} }
@ -174,7 +194,7 @@ func (c *Client) GetBlockVerboseTxAsync(blockHash *chainhash.Hash) FutureGetBloc
// //
// See GetBlockVerbose if only transaction hashes are preferred. // See GetBlockVerbose if only transaction hashes are preferred.
// See GetBlock to retrieve a raw block instead. // 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() return c.GetBlockVerboseTxAsync(blockHash).Receive()
} }

View file

@ -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 // When the verbose flag isn't set, simply return the serialized block
// as a hex-encoded string. // as a hex-encoded string.
if c.Verbose != nil && !*c.Verbose { if c.Verbosity != nil && *c.Verbosity == 0 {
return hex.EncodeToString(blkBytes), nil return hex.EncodeToString(blkBytes), nil
} }
@ -1137,7 +1137,7 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i
NextHash: nextHashString, NextHash: nextHashString,
} }
if c.VerboseTx == nil || !*c.VerboseTx { if *c.Verbosity == 1 {
transactions := blk.Transactions() transactions := blk.Transactions()
txNames := make([]string, len(transactions)) txNames := make([]string, len(transactions))
for i, tx := range transactions { for i, tx := range transactions {

View file

@ -162,10 +162,9 @@ var helpDescsEnUS = map[string]string{
// GetBlockCmd help. // GetBlockCmd help.
"getblock--synopsis": "Returns information about a block given its hash.", "getblock--synopsis": "Returns information about a block given its hash.",
"getblock-hash": "The hash of the block", "getblock-hash": "The hash of the block",
"getblock-verbose": "Specifies the block is returned as a JSON object instead of hex-encoded string", "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-verbosetx": "Specifies that each transaction is returned as a JSON object and only applies if the verbose flag is true (btcd extension)", "getblock--condition0": "verbosity=0",
"getblock--condition0": "verbose=false", "getblock--condition1": "verbosity=1",
"getblock--condition1": "verbose=true",
"getblock--result0": "Hex-encoded bytes of the serialized block", "getblock--result0": "Hex-encoded bytes of the serialized block",
// GetBlockChainInfoCmd help. // GetBlockChainInfoCmd help.