diff --git a/btcjson/btcdextcmds.go b/btcjson/btcdextcmds.go
index 25af8d21..963ccb3a 100644
--- a/btcjson/btcdextcmds.go
+++ b/btcjson/btcdextcmds.go
@@ -90,6 +90,27 @@ func NewGetCurrentNetCmd() *GetCurrentNetCmd {
return &GetCurrentNetCmd{}
}
+// GetHeadersCmd defines the getheaders JSON-RPC command.
+//
+// NOTE: This is a btcsuite extension ported from
+// github.com/decred/dcrd/dcrjson.
+type GetHeadersCmd struct {
+ BlockLocators []string `json:"blocklocators"`
+ HashStop string `json:"hashstop"`
+}
+
+// NewGetHeadersCmd returns a new instance which can be used to issue a
+// getheaders JSON-RPC command.
+//
+// NOTE: This is a btcsuite extension ported from
+// github.com/decred/dcrd/dcrjson.
+func NewGetHeadersCmd(blockLocators []string, hashStop string) *GetHeadersCmd {
+ return &GetHeadersCmd{
+ BlockLocators: blockLocators,
+ HashStop: hashStop,
+ }
+}
+
// VersionCmd defines the version JSON-RPC command.
//
// NOTE: This is a btcsuite extension ported from
@@ -112,5 +133,6 @@ func init() {
MustRegisterCmd("generate", (*GenerateCmd)(nil), flags)
MustRegisterCmd("getbestblock", (*GetBestBlockCmd)(nil), flags)
MustRegisterCmd("getcurrentnet", (*GetCurrentNetCmd)(nil), flags)
+ MustRegisterCmd("getheaders", (*GetHeadersCmd)(nil), flags)
MustRegisterCmd("version", (*VersionCmd)(nil), flags)
}
diff --git a/btcjson/btcdextcmds_test.go b/btcjson/btcdextcmds_test.go
index 1d87f7c4..10e6da38 100644
--- a/btcjson/btcdextcmds_test.go
+++ b/btcjson/btcdextcmds_test.go
@@ -136,6 +136,46 @@ func TestBtcdExtCmds(t *testing.T) {
marshalled: `{"jsonrpc":"1.0","method":"getcurrentnet","params":[],"id":1}`,
unmarshalled: &btcjson.GetCurrentNetCmd{},
},
+ {
+ name: "getheaders",
+ newCmd: func() (interface{}, error) {
+ return btcjson.NewCmd("getheaders", []string{}, "")
+ },
+ staticCmd: func() interface{} {
+ return btcjson.NewGetHeadersCmd(
+ []string{},
+ "",
+ )
+ },
+ marshalled: `{"jsonrpc":"1.0","method":"getheaders","params":[[],""],"id":1}`,
+ unmarshalled: &btcjson.GetHeadersCmd{
+ BlockLocators: []string{},
+ HashStop: "",
+ },
+ },
+ {
+ name: "getheaders - with arguments",
+ newCmd: func() (interface{}, error) {
+ return btcjson.NewCmd("getheaders", []string{"000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16", "0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10"}, "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7")
+ },
+ staticCmd: func() interface{} {
+ return btcjson.NewGetHeadersCmd(
+ []string{
+ "000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16",
+ "0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10",
+ },
+ "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7",
+ )
+ },
+ marshalled: `{"jsonrpc":"1.0","method":"getheaders","params":[["000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16","0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10"],"000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7"],"id":1}`,
+ unmarshalled: &btcjson.GetHeadersCmd{
+ BlockLocators: []string{
+ "000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16",
+ "0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10",
+ },
+ HashStop: "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7",
+ },
+ },
{
name: "version",
newCmd: func() (interface{}, error) {
diff --git a/btcjson/btcdextresults.go b/btcjson/btcdextresults.go
index 86cda258..0f7bc704 100644
--- a/btcjson/btcdextresults.go
+++ b/btcjson/btcdextresults.go
@@ -1,5 +1,5 @@
-// Copyright (c) 2016 The btcsuite developers
-// Copyright (c) 2015-2016 The Decred developers
+// Copyright (c) 2016-2017 The btcsuite developers
+// Copyright (c) 2015-2017 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
diff --git a/btcjson/btcdextresults_test.go b/btcjson/btcdextresults_test.go
index c4d7462b..478f088c 100644
--- a/btcjson/btcdextresults_test.go
+++ b/btcjson/btcdextresults_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2016 The btcsuite developers
+// Copyright (c) 2016-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
diff --git a/chaincfg/chainhash/hash.go b/chaincfg/chainhash/hash.go
index 83004d50..2b1cec02 100644
--- a/chaincfg/chainhash/hash.go
+++ b/chaincfg/chainhash/hash.go
@@ -84,35 +84,45 @@ func NewHash(newHash []byte) (*Hash, error) {
// the hexadecimal string of a byte-reversed hash, but any missing characters
// result in zero padding at the end of the Hash.
func NewHashFromStr(hash string) (*Hash, error) {
- // Return error if hash string is too long.
- if len(hash) > MaxHashStringSize {
- return nil, ErrHashStrSize
- }
-
- // Hex decoder expects the hash to be a multiple of two.
- if len(hash)%2 != 0 {
- hash = "0" + hash
- }
-
- // Convert string hash to bytes.
- buf, err := hex.DecodeString(hash)
+ ret := new(Hash)
+ err := Decode(ret, hash)
if err != nil {
return nil, err
}
-
- // Un-reverse the decoded bytes, copying into in leading bytes of a
- // Hash. There is no need to explicitly pad the result as any
- // missing (when len(buf) < HashSize) bytes from the decoded hex string
- // will remain zeros at the end of the Hash.
- var ret Hash
- blen := len(buf)
- mid := blen / 2
- if blen%2 != 0 {
- mid++
- }
- blen--
- for i, b := range buf[:mid] {
- ret[i], ret[blen-i] = buf[blen-i], b
- }
- return &ret, nil
+ return ret, nil
+}
+
+// Decode decodes the byte-reversed hexadecimal string encoding of a Hash to a
+// destination.
+func Decode(dst *Hash, src string) error {
+ // Return error if hash string is too long.
+ if len(src) > MaxHashStringSize {
+ return ErrHashStrSize
+ }
+
+ // Hex decoder expects the hash to be a multiple of two. When not, pad
+ // with a leading zero.
+ var srcBytes []byte
+ if len(src)%2 == 0 {
+ srcBytes = []byte(src)
+ } else {
+ srcBytes = make([]byte, 1+len(src))
+ srcBytes[0] = '0'
+ copy(srcBytes[1:], src)
+ }
+
+ // Hex decode the source bytes to a temporary destination.
+ var reversedHash Hash
+ _, err := hex.Decode(reversedHash[HashSize-hex.DecodedLen(len(srcBytes)):], srcBytes)
+ if err != nil {
+ return err
+ }
+
+ // Reverse copy from the temporary hash to destination. Because the
+ // temporary was zeroed, the written result will be correctly padded.
+ for i, b := range reversedHash[:HashSize/2] {
+ dst[i], dst[HashSize-1-i] = reversedHash[HashSize-1-i], b
+ }
+
+ return nil
}
diff --git a/docs/json_rpc_api.md b/docs/json_rpc_api.md
index 27d36caf..2e2cbd5d 100644
--- a/docs/json_rpc_api.md
+++ b/docs/json_rpc_api.md
@@ -584,6 +584,7 @@ The following is an overview of the RPC methods which are implemented by btcd, b
|5|[node](#node)|N|Attempts to add or remove a peer. |None|
|6|[generate](#generate)|N|When in simnet or regtest mode, generate a set number of blocks. |None|
|7|[version](#version)|Y|Returns the JSON-RPC API version.|
+|8|[getheaders](#getheaders)|Y|Returns block headers starting with the first known block hash from the request.|
@@ -678,6 +679,19 @@ The following is an overview of the RPC methods which are implemented by btcd, b
***
+
+
+| | |
+|---|---|
+|Method|getheaders|
+|Parameters|1. Block Locators (JSON array, required)
`[ (json array of strings)`
`"blocklocator", (string) the known block hash`
`...`
`]`
2. hashstop (string) - last desired block's hash|
+|Description|Returns block headers starting with the first known block hash from the request.|
+|Returns|`[ (json array of strings)`
`"blockheader",`
`...`
`]`|
+|Example Return|`[`
`"0000002099417930b2ae09feda10e38b58c0f6bb44b4d60fa33f0e000000000000000000d53...",`
`"000000203ba25a173bfd24d09e0c76002a910b685ca297bd09a17b020000000000000000702..."`
`]`|
+[Return to Overview](#MethodOverview)
+
+***
+
### 7. Websocket Extension Methods (Websocket-specific)
diff --git a/rpcserver.go b/rpcserver.go
index c34af5c1..5581699a 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -1,5 +1,5 @@
-// Copyright (c) 2013-2016 The btcsuite developers
-// Copyright (c) 2015-2016 The Decred developers
+// Copyright (c) 2013-2017 The btcsuite developers
+// Copyright (c) 2015-2017 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@@ -45,9 +45,9 @@ import (
// API version constants
const (
- jsonrpcSemverString = "1.0.0"
+ jsonrpcSemverString = "1.1.0"
jsonrpcSemverMajor = 1
- jsonrpcSemverMinor = 0
+ jsonrpcSemverMinor = 1
jsonrpcSemverPatch = 0
)
@@ -163,6 +163,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
"getdifficulty": handleGetDifficulty,
"getgenerate": handleGetGenerate,
"gethashespersec": handleGetHashesPerSec,
+ "getheaders": handleGetHeaders,
"getinfo": handleGetInfo,
"getmempoolinfo": handleGetMempoolInfo,
"getmininginfo": handleGetMiningInfo,
@@ -270,6 +271,7 @@ var rpcLimited = map[string]struct{}{
"getblockhash": {},
"getcurrentnet": {},
"getdifficulty": {},
+ "getheaders": {},
"getinfo": {},
"getnettotals": {},
"getnetworkhashps": {},
@@ -2148,6 +2150,65 @@ func handleGetHashesPerSec(s *rpcServer, cmd interface{}, closeChan <-chan struc
return int64(s.server.cpuMiner.HashesPerSecond()), nil
}
+// handleGetHeaders implements the getheaders command.
+//
+// NOTE: This is a btcsuite extension ported from
+// github.com/decred/dcrd.
+func handleGetHeaders(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
+ c := cmd.(*btcjson.GetHeadersCmd)
+ blockLocators := make([]*chainhash.Hash, len(c.BlockLocators))
+ for i := range c.BlockLocators {
+ blockLocator, err := chainhash.NewHashFromStr(c.BlockLocators[i])
+ if err != nil {
+ return nil, &btcjson.RPCError{
+ Code: btcjson.ErrRPCInvalidParameter,
+ Message: "Failed to decode block locator: " +
+ err.Error(),
+ }
+ }
+ blockLocators[i] = blockLocator
+ }
+ var hashStop chainhash.Hash
+ if c.HashStop != "" {
+ err := chainhash.Decode(&hashStop, c.HashStop)
+ if err != nil {
+ return nil, &btcjson.RPCError{
+ Code: btcjson.ErrRPCInvalidParameter,
+ Message: "Failed to decode hashstop: " + err.Error(),
+ }
+ }
+ }
+ blockHashes, err := s.server.locateBlocks(blockLocators, &hashStop)
+ if err != nil {
+ return nil, &btcjson.RPCError{
+ Code: btcjson.ErrRPCDatabase,
+ Message: "Failed to fetch hashes of block " +
+ "headers: " + err.Error(),
+ }
+ }
+ blockHeaders, err := fetchHeaders(s.server.db, blockHashes)
+ if err != nil {
+ return nil, &btcjson.RPCError{
+ Code: btcjson.ErrRPCDatabase,
+ Message: "Failed to fetch headers of located blocks: " +
+ err.Error(),
+ }
+ }
+
+ hexBlockHeaders := make([]string, len(blockHeaders))
+ var buf bytes.Buffer
+ for i, h := range blockHeaders {
+ err := h.Serialize(&buf)
+ if err != nil {
+ return nil, internalRPCError(err.Error(),
+ "Failed to serialize block header")
+ }
+ hexBlockHeaders[i] = hex.EncodeToString(buf.Bytes())
+ buf.Reset()
+ }
+ return hexBlockHeaders, nil
+}
+
// handleGetInfo implements the getinfo command. We only return the fields
// that are not related to wallet functionality.
func handleGetInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
diff --git a/rpcserverhelp.go b/rpcserverhelp.go
index 80dfb3a5..4c136f47 100644
--- a/rpcserverhelp.go
+++ b/rpcserverhelp.go
@@ -1,5 +1,5 @@
-// Copyright (c) 2015-2016 The btcsuite developers
-// Copyright (c) 2015-2016 The Decred developers
+// Copyright (c) 2015-2017 The btcsuite developers
+// Copyright (c) 2015-2017 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@@ -360,6 +360,12 @@ var helpDescsEnUS = map[string]string{
"infowalletresult-relayfee": "The minimum relay fee for non-free transactions in BTC/KB",
"infowalletresult-errors": "Any current errors",
+ // GetHeadersCmd help.
+ "getheaders--synopsis": "Returns block headers starting with the first known block hash from the request",
+ "getheaders-blocklocators": "JSON array of hex-encoded hashes of blocks. Headers are returned starting from the first known hash in this list",
+ "getheaders-hashstop": "Block hash to stop including block headers for; if not found, all headers to the latest known block are returned.",
+ "getheaders--result0": "Serialized block headers of all located blocks, limited to some arbitrary maximum number of hashes (currently 2000, which matches the wire protocol headers message, but this is not guaranteed)",
+
// GetInfoCmd help.
"getinfo--synopsis": "Returns a JSON object containing various state info.",
@@ -646,6 +652,7 @@ var rpcResultTypes = map[string][]interface{}{
"getdifficulty": {(*float64)(nil)},
"getgenerate": {(*bool)(nil)},
"gethashespersec": {(*float64)(nil)},
+ "getheaders": {(*[]string)(nil)},
"getinfo": {(*btcjson.InfoChainResult)(nil)},
"getmempoolinfo": {(*btcjson.GetMempoolInfoResult)(nil)},
"getmininginfo": {(*btcjson.GetMiningInfoResult)(nil)},
diff --git a/server.go b/server.go
index efa5011a..352233fb 100644
--- a/server.go
+++ b/server.go
@@ -1,4 +1,5 @@
-// Copyright (c) 2013-2016 The btcsuite developers
+// Copyright (c) 2013-2017 The btcsuite developers
+// Copyright (c) 2015-2017 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@@ -640,58 +641,30 @@ func (sp *serverPeer) OnGetBlocks(_ *peer.Peer, msg *wire.MsgGetBlocks) {
}
}
-// OnGetHeaders is invoked when a peer receives a getheaders bitcoin
-// message.
-func (sp *serverPeer) OnGetHeaders(_ *peer.Peer, msg *wire.MsgGetHeaders) {
- // Ignore getheaders requests if not in sync.
- if !sp.server.blockManager.IsCurrent() {
- return
- }
-
+// locateBlocks returns the hashes of the blocks after the first known block in
+// locators, until hashStop is reached, or up to a max of
+// wire.MaxBlockHeadersPerMsg block hashes. This implements the search
+// algorithm used by getheaders.
+func (s *server) locateBlocks(locators []*chainhash.Hash, hashStop *chainhash.Hash) ([]chainhash.Hash, error) {
// Attempt to look up the height of the provided stop hash.
- chain := sp.server.blockManager.chain
+ chain := s.blockManager.chain
endIdx := int32(math.MaxInt32)
- height, err := chain.BlockHeightByHash(&msg.HashStop)
+ height, err := chain.BlockHeightByHash(hashStop)
if err == nil {
endIdx = height + 1
}
// There are no block locators so a specific header is being requested
// as identified by the stop hash.
- if len(msg.BlockLocatorHashes) == 0 {
+ if len(locators) == 0 {
// No blocks with the stop hash were found so there is nothing
// to do. Just return. This behavior mirrors the reference
// implementation.
if endIdx == math.MaxInt32 {
- return
+ return nil, nil
}
- // Fetch the raw block header bytes from the database.
- var headerBytes []byte
- err := sp.server.db.View(func(dbTx database.Tx) error {
- var err error
- headerBytes, err = dbTx.FetchBlockHeader(&msg.HashStop)
- return err
- })
- if err != nil {
- peerLog.Warnf("Lookup of known block hash failed: %v",
- err)
- return
- }
-
- // Deserialize the block header.
- var header wire.BlockHeader
- err = header.Deserialize(bytes.NewReader(headerBytes))
- if err != nil {
- peerLog.Warnf("Block header deserialize failed: %v",
- err)
- return
- }
-
- headersMsg := wire.NewMsgHeaders()
- headersMsg.AddBlockHeader(&header)
- sp.QueueMessage(headersMsg, nil)
- return
+ return []chainhash.Hash{*hashStop}, nil
}
// Find the most recent known block based on the block locator.
@@ -700,8 +673,8 @@ func (sp *serverPeer) OnGetHeaders(_ *peer.Peer, msg *wire.MsgGetHeaders) {
// over with the genesis block if unknown block locators are provided.
// This mirrors the behavior in the reference implementation.
startIdx := int32(1)
- for _, hash := range msg.BlockLocatorHashes {
- height, err := chain.BlockHeightByHash(hash)
+ for _, loc := range locators {
+ height, err := chain.BlockHeightByHash(loc)
if err == nil {
// Start with the next hash since we know this one.
startIdx = height + 1
@@ -709,43 +682,67 @@ func (sp *serverPeer) OnGetHeaders(_ *peer.Peer, msg *wire.MsgGetHeaders) {
}
}
- // Don't attempt to fetch more than we can put into a single message.
+ // Don't attempt to fetch more than we can put into a single wire
+ // message.
if endIdx-startIdx > wire.MaxBlockHeadersPerMsg {
endIdx = startIdx + wire.MaxBlockHeadersPerMsg
}
// Fetch the inventory from the block database.
- hashList, err := chain.HeightRange(startIdx, endIdx)
- if err != nil {
- peerLog.Warnf("Header lookup failed: %v", err)
- return
- }
+ return chain.HeightRange(startIdx, endIdx)
+}
- // Generate headers message and send it.
- headersMsg := wire.NewMsgHeaders()
- err = sp.server.db.View(func(dbTx database.Tx) error {
- for i := range hashList {
- headerBytes, err := dbTx.FetchBlockHeader(&hashList[i])
- if err != nil {
- return err
- }
-
- var header wire.BlockHeader
- err = header.Deserialize(bytes.NewReader(headerBytes))
- if err != nil {
- return err
- }
- headersMsg.AddBlockHeader(&header)
+// fetchHeaders fetches and decodes headers from the db for each hash in
+// blockHashes.
+func fetchHeaders(db database.DB, blockHashes []chainhash.Hash) ([]*wire.BlockHeader, error) {
+ headers := make([]*wire.BlockHeader, 0, len(blockHashes))
+ err := db.View(func(dbTx database.Tx) error {
+ rawHeaders, err := dbTx.FetchBlockHeaders(blockHashes)
+ if err != nil {
+ return err
+ }
+ for _, headerBytes := range rawHeaders {
+ h := new(wire.BlockHeader)
+ err = h.Deserialize(bytes.NewReader(headerBytes))
+ if err != nil {
+ return err
+ }
+ headers = append(headers, h)
}
-
return nil
})
- if err != nil {
- peerLog.Warnf("Failed to build headers: %v", err)
+ return headers, err
+}
+
+// OnGetHeaders is invoked when a peer receives a getheaders bitcoin
+// message.
+func (sp *serverPeer) OnGetHeaders(_ *peer.Peer, msg *wire.MsgGetHeaders) {
+ // Ignore getheaders requests if not in sync.
+ if !sp.server.blockManager.IsCurrent() {
return
}
- sp.QueueMessage(headersMsg, nil)
+ blockHashes, err := sp.server.locateBlocks(msg.BlockLocatorHashes,
+ &msg.HashStop)
+ if err != nil {
+ peerLog.Errorf("OnGetHeaders: failed to fetch hashes: %v", err)
+ return
+ }
+ blockHeaders, err := fetchHeaders(sp.server.db, blockHashes)
+ if err != nil {
+ peerLog.Errorf("OnGetHeaders: failed to fetch block headers: "+
+ "%v", err)
+ return
+ }
+
+ if len(blockHeaders) > wire.MaxBlockHeadersPerMsg {
+ peerLog.Warnf("OnGetHeaders: fetched more block headers than " +
+ "allowed per message")
+ // Can still recover from this error, just slice off the extra
+ // headers and continue queing the message.
+ blockHeaders = blockHeaders[:wire.MaxBlockHeaderPayload]
+ }
+ sp.QueueMessage(&wire.MsgHeaders{Headers: blockHeaders}, nil)
}
// enforceNodeBloomFlag disconnects the peer if the server is not configured to