From 154f2f25d3744dac8b9c05425fac8e2f7165e818 Mon Sep 17 00:00:00 2001 From: Connor Stein Date: Mon, 23 Dec 2019 11:11:32 -0500 Subject: [PATCH] porting https://github.com/reinerRubin/btcd bug fix --- btcjson/chainsvrcmds.go | 8 +++--- btcjson/chainsvrcmds_test.go | 43 +++++++++++++++++++------------ btcjson/chainsvrresults.go | 50 ++++++++++++++++++++++-------------- btcjson/cmdinfo_test.go | 2 +- btcjson/example_test.go | 29 ++++++++++----------- btcjson/help.go | 37 ++++++++++++++++---------- btcjson/help_test.go | 2 +- rpcclient/chain.go | 33 ++++++++++++++++++++---- rpcserver.go | 30 +++++++++++++++------- rpcserverhelp.go | 12 +++++---- 10 files changed, 156 insertions(+), 90 deletions(-) diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go index 406357bd06..126780dfd5 100644 --- a/btcjson/chainsvrcmds.go +++ b/btcjson/chainsvrcmds.go @@ -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 *uint32 `jsonrpcdefault:"1"` } // 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 *uint32) *GetBlockCmd { return &GetBlockCmd{ Hash: hash, - Verbose: verbose, - VerboseTx: verboseTx, + Verbosity: verbosity, } } diff --git a/btcjson/chainsvrcmds_test.go b/btcjson/chainsvrcmds_test.go index 8cb4ee765a..79fccbc776 100644 --- a/btcjson/chainsvrcmds_test.go +++ b/btcjson/chainsvrcmds_test.go @@ -142,16 +142,15 @@ func TestChainSvrCmds(t *testing.T) { { name: "getblock", newCmd: func() (interface{}, error) { - return btcjson.NewCmd("getblock", "123") + return btcjson.NewCmd("getblock", "123", 0) }, staticCmd: func() interface{} { - return btcjson.NewGetBlockCmd("123", nil, nil) + return btcjson.NewGetBlockCmd("123", btcjson.Uint32(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.Uint32(0), }, }, { @@ -160,32 +159,44 @@ func TestChainSvrCmds(t *testing.T) { // 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) + verbosityPtr := btcjson.Uint32(1) + return btcjson.NewCmd("getblock", "123", &verbosityPtr) }, staticCmd: func() interface{} { - return btcjson.NewGetBlockCmd("123", btcjson.Bool(true), nil) + return btcjson.NewGetBlockCmd("123", btcjson.Uint32(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.Uint32(1), }, }, { name: "getblock required optional2", newCmd: func() (interface{}, error) { - return btcjson.NewCmd("getblock", "123", true, true) + return btcjson.NewCmd("getblock", "123", 2) }, staticCmd: func() interface{} { - return btcjson.NewGetBlockCmd("123", btcjson.Bool(true), btcjson.Bool(true)) + return btcjson.NewGetBlockCmd("123", btcjson.Uint32(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", + Verbosity: btcjson.Uint32(2), + }, + }, + { + name: "getblock; default verbose level must be 1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getblock", "123") + }, + staticCmd: func() interface{} { + return btcjson.NewGetBlockCmd("123", nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123"],"id":1}`, unmarshalled: &btcjson.GetBlockCmd{ Hash: "123", - Verbose: btcjson.Bool(true), - VerboseTx: btcjson.Bool(true), + Verbosity: btcjson.Uint32(1), }, }, { diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 7e6c710766..25d0b646aa 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -24,27 +24,39 @@ type GetBlockHeaderVerboseResult struct { NextHash string `json:"nextblockhash,omitempty"` } +// GetBlockBaseVerboseResult models the common data from the getblock command when +// verbose flag set to 1 or 2. When the verbose flag is not set, getblock +// returns a hex-encoded string. +type GetBlockBaseVerboseResult 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"` + 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"` +} + // 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 (default). type GetBlockVerboseResult 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 []string `json:"tx,omitempty"` - RawTx []TxRawResult `json:"rawtx,omitempty"` - 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"` + *GetBlockBaseVerboseResult + Tx []string `json:"tx,omitempty"` +} + +// GetBlockVerboseTxResult models the data from the getblock command when the +// verbose flag is set to 2. +type GetBlockVerboseTxResult struct { + *GetBlockBaseVerboseResult + Tx []TxRawResult `json:"tx,omitempty"` } // CreateMultiSigResult models the data returned from the createmultisig diff --git a/btcjson/cmdinfo_test.go b/btcjson/cmdinfo_test.go index 044040279a..61a693e404 100644 --- a/btcjson/cmdinfo_test.go +++ b/btcjson/cmdinfo_test.go @@ -151,7 +151,7 @@ func TestMethodUsageText(t *testing.T) { { name: "getblock", method: "getblock", - expected: `getblock "hash" (verbose=true verbosetx=false)`, + expected: `getblock "hash" (verbosity=1)`, }, } diff --git a/btcjson/example_test.go b/btcjson/example_test.go index 527252c7fb..e18a30692c 100644 --- a/btcjson/example_test.go +++ b/btcjson/example_test.go @@ -11,17 +11,18 @@ import ( "github.com/btcsuite/btcd/btcjson" ) -// This example demonstrates how to create and marshal a command into a JSON-RPC -// request. +// Create a new getblock command. Notice the call to btcjson.Uint32 which is a +// convenience function for creating a pointer out of a primitive for +// optional parameters. Notice the nil parameter indicates +// to use the default parameter for that fields. This is a common +// pattern used in all of the NewCmdr functions in this package for +// optional fields. func ExampleMarshalCmd() { - // Create a new getblock command. Notice the nil parameter indicates - // to use the default parameter for that fields. This is a common - // pattern used in all of the NewCmd functions in this package for - // optional fields. Also, notice the call to btcjson.Bool which is a - // convenience function for creating a pointer out of a primitive for - // optional parameters. + // Create a new getblock command. blockHash := "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" - gbCmd := btcjson.NewGetBlockCmd(blockHash, btcjson.Bool(false), nil) + gbCmd := btcjson.NewGetBlockCmd(blockHash, btcjson.Uint32(2)) + // or + // gbCmd := btcjson.NewGetBlockCmd(blockHash, nil) // 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 +39,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",2],"id":1} } // This example demonstrates how to unmarshal a JSON-RPC request and then @@ -46,7 +47,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 +85,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. diff --git a/btcjson/help.go b/btcjson/help.go index f502d09fd8..ed3bd7931f 100644 --- a/btcjson/help.go +++ b/btcjson/help.go @@ -110,8 +110,8 @@ func resultStructHelp(xT descLookupFunc, rt reflect.Type, indentLevel int) []str fieldType := reflectTypeToJSONType(xT, rtfType) fieldDescKey := typeName + "-" + fieldName fieldExamples, isComplex := reflectTypeToJSONExample(xT, - rtfType, indentLevel, fieldDescKey) - if isComplex { + rtfType, indentLevel, fieldDescKey, rtf.Anonymous) + if isComplex && !rtf.Anonymous { var brace string kind := rtfType.Kind() if kind == reflect.Array || kind == reflect.Slice { @@ -123,6 +123,8 @@ func resultStructHelp(xT descLookupFunc, rt reflect.Type, indentLevel int) []str fieldName, brace, fieldType, xT(fieldDescKey)) results = append(results, result) results = append(results, fieldExamples...) + } else if isComplex && rtf.Anonymous { + results = append(results, fieldExamples...) } else { result := fmt.Sprintf("%s\"%s\": %s,\t(%s)\t%s", indent, fieldName, fieldExamples[0], fieldType, @@ -140,7 +142,7 @@ func resultStructHelp(xT descLookupFunc, rt reflect.Type, indentLevel int) []str // a tab writer. A bool is also returned which specifies whether or not the // type results in a complex JSON object since they need to be handled // differently. -func reflectTypeToJSONExample(xT descLookupFunc, rt reflect.Type, indentLevel int, fieldDescKey string) ([]string, bool) { +func reflectTypeToJSONExample(xT descLookupFunc, rt reflect.Type, indentLevel int, fieldDescKey string, embeddedStruct bool) ([]string, bool) { // Indirect pointer if needed. if rt.Kind() == reflect.Ptr { rt = rt.Elem() @@ -163,7 +165,12 @@ func reflectTypeToJSONExample(xT descLookupFunc, rt reflect.Type, indentLevel in case reflect.Struct: indent := strings.Repeat(" ", indentLevel) - results := resultStructHelp(xT, rt, indentLevel+1) + nextIndentLevel := indentLevel + + if !embeddedStruct { + nextIndentLevel++ + } + results := resultStructHelp(xT, rt, nextIndentLevel) // An opening brace is needed for the first indent level. For // all others, it will be included as a part of the previous @@ -174,20 +181,22 @@ func reflectTypeToJSONExample(xT descLookupFunc, rt reflect.Type, indentLevel in copy(newResults[1:], results) results = newResults } - - // The closing brace has a comma after it except for the first - // indent level. The final tabs are necessary so the tab writer - // lines things up properly. - closingBrace := indent + "}" - if indentLevel > 0 { - closingBrace += "," + if !embeddedStruct { + // The closing brace has a comma after it except for the first + // indent level. The final tabs are necessary so the tab writer + // lines things up properly. + closingBrace := indent + "}" + if indentLevel > 0 { + closingBrace += "," + } + results = append(results, closingBrace+"\t\t") } - results = append(results, closingBrace+"\t\t") + return results, true case reflect.Array, reflect.Slice: results, isComplex := reflectTypeToJSONExample(xT, rt.Elem(), - indentLevel, fieldDescKey) + indentLevel, fieldDescKey, false) // When the result is complex, it is because this is an array of // objects. @@ -251,7 +260,7 @@ func reflectTypeToJSONExample(xT descLookupFunc, rt reflect.Type, indentLevel in // type. func resultTypeHelp(xT descLookupFunc, rt reflect.Type, fieldDescKey string) string { // Generate the JSON example for the result type. - results, isComplex := reflectTypeToJSONExample(xT, rt, 0, fieldDescKey) + results, isComplex := reflectTypeToJSONExample(xT, rt, 0, fieldDescKey, false) // When this is a primitive type, add the associated JSON type and // result description into the final string, format it accordingly, diff --git a/btcjson/help_test.go b/btcjson/help_test.go index 918aa14479..ee406ee4e9 100644 --- a/btcjson/help_test.go +++ b/btcjson/help_test.go @@ -246,7 +246,7 @@ func TestHelpReflectInternals(t *testing.T) { // Ensure the generated example is as expected. examples, isComplex := btcjson.TstReflectTypeToJSONExample(xT, - test.reflectType, test.indentLevel, "fdk") + test.reflectType, test.indentLevel, "fdk", false) if isComplex != test.isComplex { t.Errorf("Test #%d (%s) unexpected isComplex - got: %v, "+ "want: %v", i, test.name, isComplex, diff --git a/rpcclient/chain.go b/rpcclient/chain.go index 996d80458c..f7fe310e27 100644 --- a/rpcclient/chain.go +++ b/rpcclient/chain.go @@ -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, btcjson.Uint32(0)) return c.sendCmd(cmd) } @@ -141,7 +141,7 @@ func (c *Client) GetBlockVerboseAsync(blockHash *chainhash.Hash) FutureGetBlockV hash = blockHash.String() } - cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(true), nil) + cmd := btcjson.NewGetBlockCmd(hash, btcjson.Uint32(1)) return c.sendCmd(cmd) } @@ -154,18 +154,41 @@ func (c *Client) GetBlockVerbose(blockHash *chainhash.Hash) (*btcjson.GetBlockVe return c.GetBlockVerboseAsync(blockHash).Receive() } +// FutureGetBlockVerboseTxResult is a future promise to deliver the result of a +// GetBlockVerboseTxAsync RPC invocation (or an applicable error). +type FutureGetBlockVerboseTxResult 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 FutureGetBlockVerboseTxResult) Receive() (*btcjson.GetBlockVerboseTxResult, error) { + res, err := receiveFuture(r) + if err != nil { + return nil, err + } + + // Unmarshal the raw result into a BlockResult. + 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() } - cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(true), btcjson.Bool(true)) + cmd := btcjson.NewGetBlockCmd(hash, btcjson.Uint32(2)) + return c.sendCmd(cmd) } @@ -174,7 +197,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() } diff --git a/rpcserver.go b/rpcserver.go index e762cc1a9d..a56171558a 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1082,13 +1082,13 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i } } - // When the verbose flag isn't set, simply return the serialized block + // When the verbosity value set to 0, simply return the serialized block // as a hex-encoded string. - if c.Verbose != nil && !*c.Verbose { + if *c.Verbosity == 0 { return hex.EncodeToString(blkBytes), nil } - // The verbose flag is set, so generate the JSON object and return it. + // Generate the JSON object and return it. // Deserialize the block. blk, err := btcutil.NewBlockFromBytes(blkBytes) @@ -1117,9 +1117,12 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i nextHashString = nextHash.String() } - params := s.cfg.ChainParams - blockHeader := &blk.MsgBlock().Header - blockReply := btcjson.GetBlockVerboseResult{ + var ( + blockReply interface{} + params = s.cfg.ChainParams + blockHeader = &blk.MsgBlock().Header + ) + baseBlockReply := &btcjson.GetBlockBaseVerboseResult{ Hash: c.Hash, Version: blockHeader.Version, VersionHex: fmt.Sprintf("%08x", blockHeader.Version), @@ -1137,14 +1140,19 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i NextHash: nextHashString, } - if c.VerboseTx == nil || !*c.VerboseTx { + // If verbose level does not match 0 or 1 + // we can consider it 2 (current bitcoin core behavior) + if *c.Verbosity == 1 { transactions := blk.Transactions() txNames := make([]string, len(transactions)) for i, tx := range transactions { txNames[i] = tx.Hash().String() } - blockReply.Tx = txNames + blockReply = btcjson.GetBlockVerboseResult{ + GetBlockBaseVerboseResult: baseBlockReply, + Tx: txNames, + } } else { txns := blk.Transactions() rawTxns := make([]btcjson.TxRawResult, len(txns)) @@ -1157,7 +1165,11 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i } rawTxns[i] = *rawTxn } - blockReply.RawTx = rawTxns + + blockReply = btcjson.GetBlockVerboseTxResult{ + GetBlockBaseVerboseResult: baseBlockReply, + Tx: rawTxns, + } } return blockReply, nil diff --git a/rpcserverhelp.go b/rpcserverhelp.go index cc6935b512..6d84cb497a 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -162,11 +162,13 @@ 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 the block format returns", + "getblock--condition0": "verbosity=0", + "getblock--condition1": "verbosity=1", + "getblock--condition2": "verbosity=2", "getblock--result0": "Hex-encoded bytes of the serialized block", + "getblock--result1": "JSON object with information about block", + "getblock--result2": "JSON object with information about block and information about each transaction.", // GetBlockChainInfoCmd help. "getblockchaininfo--synopsis": "Returns information about the current blockchain state and the status of any active soft-fork deployments.", @@ -698,7 +700,7 @@ var rpcResultTypes = map[string][]interface{}{ "getaddednodeinfo": {(*[]string)(nil), (*[]btcjson.GetAddedNodeInfoResult)(nil)}, "getbestblock": {(*btcjson.GetBestBlockResult)(nil)}, "getbestblockhash": {(*string)(nil)}, - "getblock": {(*string)(nil), (*btcjson.GetBlockVerboseResult)(nil)}, + "getblock": {(*string)(nil), (*btcjson.GetBlockVerboseResult)(nil), (*btcjson.GetBlockVerboseTxResult)(nil)}, "getblockcount": {(*int64)(nil)}, "getblockhash": {(*string)(nil)}, "getblockheader": {(*string)(nil), (*btcjson.GetBlockHeaderVerboseResult)(nil)},