Skip to content

Commit

Permalink
rpcclient: send legacy GetBlock request for backwards compatibility
Browse files Browse the repository at this point in the history
Without this, users of this library wouldn't be able to issue GetBlock
requests to nodes which haven't updated to support the latest request
format, namely the use of a single `int` parameter to denote verbosity
instead of two `bool`s.
  • Loading branch information
wpaulino committed May 15, 2020
1 parent d38279e commit 742935e
Showing 1 changed file with 84 additions and 11 deletions.
95 changes: 84 additions & 11 deletions rpcclient/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,61 @@ func (c *Client) GetBestBlockHash() (*chainhash.Hash, error) {
return c.GetBestBlockHashAsync().Receive()
}

// legacyGetBlockRequest constructs and sends a legacy getblock request which
// contains two separate bools to denote verbosity, in contract to a single int
// parameter.
func (c *Client) legacyGetBlockRequest(hash string, verbose,
verboseTx bool) ([]byte, error) {

hashJSON, err := json.Marshal(hash)
if err != nil {
return nil, err
}
verboseJSON, err := json.Marshal(btcjson.Bool(verbose))
if err != nil {
return nil, err
}
verboseTxJSON, err := json.Marshal(btcjson.Bool(verboseTx))
if err != nil {
return nil, err
}
return c.RawRequest("getblock", []json.RawMessage{
hashJSON, verboseJSON, verboseTxJSON,
})
}

// waitForGetBlockRes waits for the response of a getblock request. If the
// response indicates an invalid parameter was provided, a legacy style of the
// request is resent and its response is returned instead.
func (c *Client) waitForGetBlockRes(respChan chan *response, hash string,
verbose, verboseTx bool) ([]byte, error) {

res, err := receiveFuture(respChan)

// If we receive an invalid parameter error, then we may be
// communicating with a btcd node which only understands the legacy
// request, so we'll try that.
if err, ok := err.(*btcjson.RPCError); ok &&
err.Code == btcjson.ErrRPCInvalidParams.Code {
return c.legacyGetBlockRequest(hash, verbose, verboseTx)
}

// Otherwise, we can return the response as is.
return res, err
}

// FutureGetBlockResult is a future promise to deliver the result of a
// GetBlockAsync RPC invocation (or an applicable error).
type FutureGetBlockResult chan *response
type FutureGetBlockResult struct {
client *Client
hash string
Response chan *response
}

// Receive waits for the response promised by the future and returns the raw
// block requested from the server given its hash.
func (r FutureGetBlockResult) Receive() (*wire.MsgBlock, error) {
res, err := receiveFuture(r)
res, err := r.client.waitForGetBlockRes(r.Response, r.hash, false, false)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -98,7 +145,11 @@ func (c *Client) GetBlockAsync(blockHash *chainhash.Hash) FutureGetBlockResult {
}

cmd := btcjson.NewGetBlockCmd(hash, btcjson.Int(0))
return c.sendCmd(cmd)
return FutureGetBlockResult{
client: c,
hash: hash,
Response: c.sendCmd(cmd),
}
}

// GetBlock returns a raw block from the server given its hash.
Expand All @@ -111,12 +162,16 @@ func (c *Client) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {

// FutureGetBlockVerboseResult is a future promise to deliver the result of a
// GetBlockVerboseAsync RPC invocation (or an applicable error).
type FutureGetBlockVerboseResult chan *response
type FutureGetBlockVerboseResult struct {
client *Client
hash string
Response chan *response
}

// Receive waits for the response promised by the future and returns the data
// structure from the server with information about the requested block.
func (r FutureGetBlockVerboseResult) Receive() (*btcjson.GetBlockVerboseResult, error) {
res, err := receiveFuture(r)
res, err := r.client.waitForGetBlockRes(r.Response, r.hash, true, false)
if err != nil {
return nil, err
}
Expand All @@ -143,7 +198,11 @@ func (c *Client) GetBlockVerboseAsync(blockHash *chainhash.Hash) FutureGetBlockV
// 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)
return FutureGetBlockVerboseResult{
client: c,
hash: hash,
Response: c.sendCmd(cmd),
}
}

// GetBlockVerbose returns a data structure from the server with information
Expand All @@ -155,10 +214,18 @@ func (c *Client) GetBlockVerbose(blockHash *chainhash.Hash) (*btcjson.GetBlockVe
return c.GetBlockVerboseAsync(blockHash).Receive()
}

type FutureGetBlockVerboseTxResult chan *response
// FutureGetBlockVerboseTxResult is a future promise to deliver the result of a
// GetBlockVerboseTxResult RPC invocation (or an applicable error).
type FutureGetBlockVerboseTxResult struct {
client *Client
hash string
Response chan *response
}

// Receive waits for the response promised by the future and returns a verbose
// version of the block including detailed information about its transactions.
func (r FutureGetBlockVerboseTxResult) Receive() (*btcjson.GetBlockVerboseTxResult, error) {
res, err := receiveFuture(r)
res, err := r.client.waitForGetBlockRes(r.Response, r.hash, true, true)
if err != nil {
return nil, err
}
Expand All @@ -182,11 +249,17 @@ func (c *Client) GetBlockVerboseTxAsync(blockHash *chainhash.Hash) FutureGetBloc
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."
//
// If verbosity is 2, returns an Object with information about block
// and information about each transaction.
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Int(2))

return c.sendCmd(cmd)
return FutureGetBlockVerboseTxResult{
client: c,
hash: hash,
Response: c.sendCmd(cmd),
}
}

// GetBlockVerboseTx returns a data structure from the server with information
Expand Down

0 comments on commit 742935e

Please sign in to comment.