From a6c79c7a913b1f9f5e0a7c2aa9c6416ecd430c84 Mon Sep 17 00:00:00 2001 From: David Hill Date: Sat, 4 Jul 2015 13:42:43 -0400 Subject: [PATCH] Implement getblockheader RPC. This mimics Bitcoin Core commit 076badb60f33f0c32b035de220ca14c52a423a2a --- btcjson/chainsvrcmds.go | 16 +++++++++ btcjson/chainsvrcmds_test.go | 15 ++++++++ btcjson/chainsvrresults.go | 17 ++++++++++ docs/json_rpc_api.md | 57 +++++++++++++++++++------------ rpcserver.go | 66 ++++++++++++++++++++++++++++++++++++ rpcserverhelp.go | 22 ++++++++++++ 6 files changed, 172 insertions(+), 21 deletions(-) diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go index 69a81f12..11bdf27e 100644 --- a/btcjson/chainsvrcmds.go +++ b/btcjson/chainsvrcmds.go @@ -172,6 +172,21 @@ func NewGetBlockHashCmd(index int64) *GetBlockHashCmd { } } +// GetBlockHeaderCmd defines the getblockheader JSON-RPC command. +type GetBlockHeaderCmd struct { + Hash string + Verbose *bool `jsonrpcdefault:"true"` +} + +// NewGetBlockHeaderCmd returns a new instance which can be used to issue a +// getblockheader JSON-RPC command. +func NewGetBlockHeaderCmd(hash string, verbose *bool) *GetBlockHeaderCmd { + return &GetBlockHeaderCmd{ + Hash: hash, + Verbose: verbose, + } +} + // TemplateRequest is a request object as defined in BIP22 // (https://en.bitcoin.it/wiki/BIP_0022), it is optionally provided as an // pointer argument to GetBlockTemplateCmd. @@ -695,6 +710,7 @@ func init() { MustRegisterCmd("getblockchaininfo", (*GetBlockChainInfoCmd)(nil), flags) MustRegisterCmd("getblockcount", (*GetBlockCountCmd)(nil), flags) MustRegisterCmd("getblockhash", (*GetBlockHashCmd)(nil), flags) + MustRegisterCmd("getblockheader", (*GetBlockHeaderCmd)(nil), flags) MustRegisterCmd("getblocktemplate", (*GetBlockTemplateCmd)(nil), flags) MustRegisterCmd("getchaintips", (*GetChainTipsCmd)(nil), flags) MustRegisterCmd("getconnectioncount", (*GetConnectionCountCmd)(nil), flags) diff --git a/btcjson/chainsvrcmds_test.go b/btcjson/chainsvrcmds_test.go index 38021524..baaf7233 100644 --- a/btcjson/chainsvrcmds_test.go +++ b/btcjson/chainsvrcmds_test.go @@ -199,6 +199,20 @@ func TestChainSvrCmds(t *testing.T) { marshalled: `{"jsonrpc":"1.0","method":"getblockhash","params":[123],"id":1}`, unmarshalled: &btcjson.GetBlockHashCmd{Index: 123}, }, + { + name: "getblockheader", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getblockheader", "123") + }, + staticCmd: func() interface{} { + return btcjson.NewGetBlockHeaderCmd("123", nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"getblockheader","params":["123"],"id":1}`, + unmarshalled: &btcjson.GetBlockHeaderCmd{ + Hash: "123", + Verbose: btcjson.Bool(true), + }, + }, { name: "getblocktemplate", newCmd: func() (interface{}, error) { @@ -926,6 +940,7 @@ func TestChainSvrCmds(t *testing.T) { t.Errorf("Test #%d (%s) unexpected marshalled data - "+ "got %s, want %s", i, test.name, marshalled, test.marshalled) + t.Errorf("\n%s\n%s", marshalled, test.marshalled) continue } diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index c0efd7cd..518d78fe 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -6,6 +6,23 @@ package btcjson import "encoding/json" +// GetBlockHeaderVerboseResult models the data from the getblockheader command when +// the verbose flag is set. When the verbose flag is not set, getblockheader +// returns a hex-encoded string. +type GetBlockHeaderVerboseResult struct { + Hash string `json:"hash"` + Confirmations uint64 `json:"confirmations"` + Height int32 `json:"height"` + Version int32 `json:"version"` + MerkleRoot string `json:"merkleroot"` + Time int64 `json:"time"` + Nonce uint64 `json:"nonce"` + Bits string `json:"bits"` + Difficulty float64 `json:"difficulty"` + PreviousHash string `json:"previousblockhash,omitempty"` + 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. diff --git a/docs/json_rpc_api.md b/docs/json_rpc_api.md index a07f98c9..bf95a845 100644 --- a/docs/json_rpc_api.md +++ b/docs/json_rpc_api.md @@ -157,27 +157,28 @@ the method name for further details such as parameter and return information. |7|[getblock](#getblock)|Y|Returns information about a block given its hash.| |8|[getblockcount](#getblockcount)|Y|Returns the number of blocks in the longest block chain.| |9|[getblockhash](#getblockhash)|Y|Returns hash of the block in best block chain at the given height.| -|10|[getconnectioncount](#getconnectioncount)|N|Returns the number of active connections to other peers.| -|11|[getdifficulty](#getdifficulty)|Y|Returns the proof-of-work difficulty as a multiple of the minimum difficulty.| -|12|[getgenerate](#getgenerate)|N|Return if the server is set to generate coins (mine) or not.| -|13|[gethashespersec](#gethashespersec)|N|Returns a recent hashes per second performance measurement while generating coins (mining).| -|14|[getinfo](#getinfo)|Y|Returns a JSON object containing various state info.| -|15|[getmempoolinfo](#getmempoolinfo)|N|Returns a JSON object containing mempool-related information.| -|16|[getmininginfo](#getmininginfo)|N|Returns a JSON object containing mining-related information.| -|17|[getnettotals](#getnettotals)|Y|Returns a JSON object containing network traffic statistics.| -|18|[getnetworkhashps](#getnetworkhashps)|Y|Returns the estimated network hashes per second for the block heights provided by the parameters.| -|19|[getpeerinfo](#getpeerinfo)|N|Returns information about each connected network peer as an array of json objects.| -|20|[getrawmempool](#getrawmempool)|Y|Returns an array of hashes for all of the transactions currently in the memory pool.| -|21|[getrawtransaction](#getrawtransaction)|Y|Returns information about a transaction given its hash.| -|22|[getwork](#getwork)|N|Returns formatted hash data to work on or checks and submits solved data.
NOTE: Since btcd does not have the wallet integrated to provide payment addresses, btcd must be configured via the `--miningaddr` option to provide which payment addresses to pay created blocks to for this RPC to function.| -|23|[help](#help)|Y|Returns a list of all commands or help for a specified command.| -|24|[ping](#ping)|N|Queues a ping to be sent to each connected peer.| -|25|[sendrawtransaction](#sendrawtransaction)|Y|Submits the serialized, hex-encoded transaction to the local peer and relays it to the network.
btcd does not yet implement the `allowhighfees` parameter, so it has no effect| -|26|[setgenerate](#setgenerate) |N|Set the server to generate coins (mine) or not.
NOTE: Since btcd does not have the wallet integrated to provide payment addresses, btcd must be configured via the `--miningaddr` option to provide which payment addresses to pay created blocks to for this RPC to function.| -|27|[stop](#stop)|N|Shutdown btcd.| -|28|[submitblock](#submitblock)|Y|Attempts to submit a new serialized, hex-encoded block to the network.| -|29|[validateaddress](#validateaddress)|Y|Verifies the given address is valid. NOTE: Since btcd does not have a wallet integrated, btcd will only return whether the address is valid or not.| -|30|[verifychain](#verifychain)|N|Verifies the block chain database.| +|10|[getblockheader](#getblockheader)|Y|Returns the block header of the block.| +|11|[getconnectioncount](#getconnectioncount)|N|Returns the number of active connections to other peers.| +|12|[getdifficulty](#getdifficulty)|Y|Returns the proof-of-work difficulty as a multiple of the minimum difficulty.| +|13|[getgenerate](#getgenerate)|N|Return if the server is set to generate coins (mine) or not.| +|14|[gethashespersec](#gethashespersec)|N|Returns a recent hashes per second performance measurement while generating coins (mining).| +|15|[getinfo](#getinfo)|Y|Returns a JSON object containing various state info.| +|16|[getmempoolinfo](#getmempoolinfo)|N|Returns a JSON object containing mempool-related information.| +|17|[getmininginfo](#getmininginfo)|N|Returns a JSON object containing mining-related information.| +|18|[getnettotals](#getnettotals)|Y|Returns a JSON object containing network traffic statistics.| +|19|[getnetworkhashps](#getnetworkhashps)|Y|Returns the estimated network hashes per second for the block heights provided by the parameters.| +|20|[getpeerinfo](#getpeerinfo)|N|Returns information about each connected network peer as an array of json objects.| +|21|[getrawmempool](#getrawmempool)|Y|Returns an array of hashes for all of the transactions currently in the memory pool.| +|22|[getrawtransaction](#getrawtransaction)|Y|Returns information about a transaction given its hash.| +|23|[getwork](#getwork)|N|Returns formatted hash data to work on or checks and submits solved data.
NOTE: Since btcd does not have the wallet integrated to provide payment addresses, btcd must be configured via the `--miningaddr` option to provide which payment addresses to pay created blocks to for this RPC to function.| +|24|[help](#help)|Y|Returns a list of all commands or help for a specified command.| +|25|[ping](#ping)|N|Queues a ping to be sent to each connected peer.| +|26|[sendrawtransaction](#sendrawtransaction)|Y|Submits the serialized, hex-encoded transaction to the local peer and relays it to the network.
btcd does not yet implement the `allowhighfees` parameter, so it has no effect| +|27|[setgenerate](#setgenerate) |N|Set the server to generate coins (mine) or not.
NOTE: Since btcd does not have the wallet integrated to provide payment addresses, btcd must be configured via the `--miningaddr` option to provide which payment addresses to pay created blocks to for this RPC to function.| +|28|[stop](#stop)|N|Shutdown btcd.| +|29|[submitblock](#submitblock)|Y|Attempts to submit a new serialized, hex-encoded block to the network.| +|30|[validateaddress](#validateaddress)|Y|Verifies the given address is valid. NOTE: Since btcd does not have a wallet integrated, btcd will only return whether the address is valid or not.| +|31|[verifychain](#verifychain)|N|Verifies the block chain database.| **5.2 Method Details**
@@ -295,6 +296,20 @@ the method name for further details such as parameter and return information. [Return to Overview](#MethodOverview)
*** +
+ +| | | +|---|---| +|Method|getblockheader| +|Parameters|1. block hash (string, required) - the hash of the block
2. verbose (boolean, optional, default=true) - specifies the block header is returned as a JSON object instead of a hex-encoded string| +|Description|Returns hex-encoded bytes of the serialized block header.| +|Returns (verbose=false)|`"data" (string) hex-encoded bytes of the serialized block`| +|Returns (verbose=true)|`{ (json object)`
  `"hash": "blockhash", (string) the hash of the block (same as provided)`
  `"confirmations": n, (numeric) the number of confirmations`
  `"height": n, (numeric) the height of the block in the block chain`
  `"version": n, (numeric) the block version`
  `"merkleroot": "hash", (string) root hash of the merkle tree`
  `"time": n, (numeric) the block time in seconds since 1 Jan 1970 GMT`
  `"nonce": n, (numeric) the block nonce`
  `"bits": n, (numeric) the bits which represent the block difficulty`
  `"difficulty": n.nn, (numeric) the proof-of-work difficulty as a multiple of the minimum difficulty`
  `"previousblockhash": "hash", (string) the hash of the previous block`
  `"nextblockhash": "hash", (string) the hash of the next block (only if there is one)`
`}`| +|Example Return (verbose=false)|`"0200000035ab154183570282ce9afc0b494c9fc6a3cfea05aa8c1add2ecc564900000000`
`38ba3d78e4500a5a7570dbe61960398add4410d278b21cd9708e6d9743f374d544fc0552`
`27f1001c29c1ea3b"`
**Newlines added for display purposes. The actual return does not contain newlines.**| +|Example Return (verbose=true)|`{`
  `"hash": "00000000009e2958c15ff9290d571bf9459e93b19765c6801ddeccadbb160a1e",`
  `"confirmations": 392076,`
  `"height": 100000,`
  `"version": 2,`
  `"merkleroot": "d574f343976d8e70d91cb278d21044dd8a396019e6db70755a0a50e4783dba38",`
  `"time": 1376123972,`
  `"nonce": 1005240617,`
  `"bits": "1c00f127",`
  `"difficulty": 271.75767393,`
  `"previousblockhash": "000000004956cc2edd1a8caa05eacfa3c69f4c490bfc9ace820257834115ab35",`
  `"nextblockhash": "0000000000629d100db387f37d0f37c51118f250fb0946310a8c37316cbc4028"`
`}`| +[Return to Overview](#MethodOverview)
+ +***
| | | diff --git a/rpcserver.go b/rpcserver.go index 58fcf657..1672ee20 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -139,6 +139,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "getblock": handleGetBlock, "getblockcount": handleGetBlockCount, "getblockhash": handleGetBlockHash, + "getblockheader": handleGetBlockHeader, "getblocktemplate": handleGetBlockTemplate, "getconnectioncount": handleGetConnectionCount, "getcurrentnet": handleGetCurrentNet, @@ -1089,6 +1090,71 @@ func handleGetBlockHash(s *rpcServer, cmd interface{}, closeChan <-chan struct{} return sha.String(), nil } +// handleGetBlockHeader implements the getblockheader command. +func handleGetBlockHeader(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + c := cmd.(*btcjson.GetBlockHeaderCmd) + + sha, err := wire.NewShaHashFromStr(c.Hash) + if err != nil { + return nil, err + } + + if c.Verbose == nil || *c.Verbose { + blk, err := s.server.db.FetchBlockBySha(sha) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidAddressOrKey, + Message: "Invalid address or key: " + err.Error(), + } + } + + _, maxIdx, err := s.server.db.NewestSha() + if err != nil { + context := "Failed to get newest hash" + return nil, internalRPCError(err.Error(), context) + } + + var shaNextStr string + shaNext, err := s.server.db.FetchBlockShaByHeight(int64(blk.Height() + 1)) + if err == nil { + shaNextStr = shaNext.String() + } + + msgBlock := blk.MsgBlock() + blockHeaderReply := btcjson.GetBlockHeaderVerboseResult{ + Hash: c.Hash, + Confirmations: uint64(1 + maxIdx - blk.Height()), + Height: int32(blk.Height()), + Version: msgBlock.Header.Version, + MerkleRoot: msgBlock.Header.MerkleRoot.String(), + NextHash: shaNextStr, + PreviousHash: msgBlock.Header.PrevBlock.String(), + Nonce: uint64(msgBlock.Header.Nonce), + Time: msgBlock.Header.Timestamp.Unix(), + Bits: strconv.FormatInt(int64(msgBlock.Header.Bits), 16), + Difficulty: getDifficultyRatio(msgBlock.Header.Bits), + } + return blockHeaderReply, nil + } + + // Verbose disabled + blkHeader, err := s.server.db.FetchBlockHeaderBySha(sha) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidAddressOrKey, + Message: "Invalid address or key: " + err.Error(), + } + } + + buf := bytes.NewBuffer(make([]byte, 0, wire.MaxBlockHeaderPayload)) + if err = blkHeader.BtcEncode(buf, maxProtocolVersion); err != nil { + errStr := fmt.Sprintf("Failed to serialize data: %v", err) + return nil, internalRPCError(errStr, "") + } + + return hex.EncodeToString(buf.Bytes()), nil +} + // encodeTemplateID encodes the passed details into an ID that can be used to // uniquely identify a block template. func encodeTemplateID(prevHash *wire.ShaHash, lastGenerated time.Time) string { diff --git a/rpcserverhelp.go b/rpcserverhelp.go index 4f44b8e9..368c2b11 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -180,6 +180,27 @@ var helpDescsEnUS = map[string]string{ "getblockhash-index": "The block height", "getblockhash--result0": "The block hash", + // GetBlockHeaderCmd help. + "getblockheader--synopsis": "Returns information about a block header given its hash.", + "getblockheader-hash": "The hash of the block", + "getblockheader-verbose": "Specifies the block header is returned as a JSON object instead of hex-encoded string", + "getblockheader--condition0": "verbose=false", + "getblockheader--condition1": "verbose=true", + "getblockheader--result0": "The block header hash", + + // GetBlockHeaderVerboseResult help. + "getblockheaderverboseresult-hash": "The hash of the block (same as provided)", + "getblockheaderverboseresult-confirmations": "The number of confirmations", + "getblockheaderverboseresult-height": "The height of the block in the block chain", + "getblockheaderverboseresult-version": "The block version", + "getblockheaderverboseresult-merkleroot": "Root hash of the merkle tree", + "getblockheaderverboseresult-time": "The block time in seconds since 1 Jan 1970 GMT", + "getblockheaderverboseresult-nonce": "The block nonce", + "getblockheaderverboseresult-bits": "The bits which represent the block difficulty", + "getblockheaderverboseresult-difficulty": "The proof-of-work difficulty as a multiple of the minimum difficulty", + "getblockheaderverboseresult-previousblockhash": "The hash of the previous block", + "getblockheaderverboseresult-nextblockhash": "The hash of the next block (only if there is one)", + // TemplateRequest help. "templaterequest-mode": "This is 'template', 'proposal', or omitted", "templaterequest-capabilities": "List of capabilities", @@ -540,6 +561,7 @@ var rpcResultTypes = map[string][]interface{}{ "getblock": []interface{}{(*string)(nil), (*btcjson.GetBlockVerboseResult)(nil)}, "getblockcount": []interface{}{(*int64)(nil)}, "getblockhash": []interface{}{(*string)(nil)}, + "getblockheader": []interface{}{(*string)(nil), (*btcjson.GetBlockHeaderVerboseResult)(nil)}, "getblocktemplate": []interface{}{(*btcjson.GetBlockTemplateResult)(nil), (*string)(nil), nil}, "getconnectioncount": []interface{}{(*int32)(nil)}, "getcurrentnet": []interface{}{(*uint32)(nil)},