From 742935e3a9da8c52350b2422cdba1ccbbf231d5d Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Thu, 14 May 2020 14:15:50 -0700 Subject: [PATCH] rpcclient: send legacy GetBlock request for backwards compatibility 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. --- rpcclient/chain.go | 95 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/rpcclient/chain.go b/rpcclient/chain.go index a5ec9e5412..6e701b1c97 100644 --- a/rpcclient/chain.go +++ b/rpcclient/chain.go @@ -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 } @@ -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. @@ -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 } @@ -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 @@ -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 } @@ -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