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.
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
@ -139,11 +138,10 @@ type GetBlockCmd struct {
//
// 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,
}
}

View file

@ -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),
},
},
{

View file

@ -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"`
@ -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"`

View file

@ -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)`,
},
}

View file

@ -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
@ -38,7 +38,7 @@ 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
@ -46,7 +46,7 @@ func ExampleMarshalCmd() {
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
@ -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.

View file

@ -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)
}
@ -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)
}
@ -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)
}
@ -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()
}

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
// as a hex-encoded string.
if c.Verbose != nil && !*c.Verbose {
if c.Verbosity != nil && *c.Verbosity == 0 {
return hex.EncodeToString(blkBytes), nil
}
@ -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 {

View file

@ -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.