Port getheaders JSON-RPC command from dcrd

This commit is contained in:
Alex 2016-12-16 16:59:25 -07:00
parent 765ca28711
commit 7c44b6472f
9 changed files with 254 additions and 103 deletions

View file

@ -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)
}

View file

@ -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) {

View file

@ -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.

View file

@ -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.

View file

@ -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
}

View file

@ -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.|
<a name="ExtMethodDetails" />
@ -678,6 +679,19 @@ The following is an overview of the RPC methods which are implemented by btcd, b
***
<a name="getheaders"/>
| | |
|---|---|
|Method|getheaders|
|Parameters|1. Block Locators (JSON array, required)<br />&nbsp;`[ (json array of strings)`<br />&nbsp;&nbsp;`"blocklocator", (string) the known block hash`<br />&nbsp;&nbsp;`...`<br />&nbsp;`]`<br />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)`<br />&nbsp;&nbsp;`"blockheader",`<br />&nbsp;&nbsp;`...`<br />`]`|
|Example Return|`[`<br />&nbsp;&nbsp;`"0000002099417930b2ae09feda10e38b58c0f6bb44b4d60fa33f0e000000000000000000d53...",`<br />&nbsp;&nbsp;`"000000203ba25a173bfd24d09e0c76002a910b685ca297bd09a17b020000000000000000702..."`<br />`]`|
[Return to Overview](#MethodOverview)<br />
***
<a name="WSExtMethods" />
### 7. Websocket Extension Methods (Websocket-specific)

View file

@ -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) {

View file

@ -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)},

129
server.go
View file

@ -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