From aa22480d5ea2cd95a19c70c93209f554b8312318 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Thu, 4 Apr 2019 11:06:19 +0300 Subject: [PATCH] [NOD-88] Add getTopHeaders rpc command (#242) * [NOD-88] Add getTopHeaders rpc command * [NOD-88] Fix gofmt error * [NOD-88] Remove unnecessary variable * [NOD-88] Remove GetTopHeaders from rpcSyncMgr --- blockdag/dag.go | 28 +++++++++++++++++++++++++++- btcjson/btcdextcmds.go | 14 ++++++++++++++ btcjson/btcdextcmds_test.go | 28 ++++++++++++++++++++++++++++ rpcclient/extensions.go | 18 ++++++++++++++++++ server/rpc/rpcserver.go | 34 ++++++++++++++++++++++++++++++++++ server/rpc/rpcserverhelp.go | 6 ++++++ 6 files changed, 127 insertions(+), 1 deletion(-) diff --git a/blockdag/dag.go b/blockdag/dag.go index 93f3955bd..20827ba3d 100644 --- a/blockdag/dag.go +++ b/blockdag/dag.go @@ -1421,7 +1421,7 @@ func (dag *BlockDAG) locateBlockNodes(locator BlockLocator, hashStop *daghash.Ha queue.pushSet(node.children) visited := newSet() - for i := uint32(0); queue.Len() > 0 && i < maxEntries; i++ { + for queue.Len() > 0 && uint32(len(nodes)) < maxEntries { var current *blockNode current = queue.pop() if !visited.contains(current) { @@ -1474,6 +1474,32 @@ func (dag *BlockDAG) locateHeaders(locator BlockLocator, hashStop *daghash.Hash, return headers } +// GetTopHeaders returns the top wire.MaxBlockHeadersPerMsg block headers ordered by height. +func (dag *BlockDAG) GetTopHeaders(startHash *daghash.Hash) ([]*wire.BlockHeader, error) { + startNode := &dag.virtual.blockNode + if startHash != nil { + startNode = dag.index.LookupNode(startHash) + if startNode == nil { + return nil, fmt.Errorf("Couldn't find the start hash %s in the dag", startHash) + } + } + headers := make([]*wire.BlockHeader, 0, startNode.blueScore) + queue := NewDownHeap() + queue.pushSet(startNode.parents) + + visited := newSet() + for i := uint32(0); queue.Len() > 0 && len(headers) < wire.MaxBlockHeadersPerMsg; i++ { + var current *blockNode + current = queue.pop() + if !visited.contains(current) { + visited.add(current) + headers = append(headers, current.Header()) + queue.pushSet(current.parents) + } + } + return headers, nil +} + // RLock locks the DAG's UTXO set for reading. func (dag *BlockDAG) RLock() { dag.dagLock.RLock() diff --git a/btcjson/btcdextcmds.go b/btcjson/btcdextcmds.go index 4ce888c14..9f2e38133 100644 --- a/btcjson/btcdextcmds.go +++ b/btcjson/btcdextcmds.go @@ -90,6 +90,19 @@ func NewGetCurrentNetCmd() *GetCurrentNetCmd { return &GetCurrentNetCmd{} } +// GetTopHeadersCmd defined the getTopHeaders JSON-RPC command. +type GetTopHeadersCmd struct { + StartHash *string `json:"startHash"` +} + +// NewGetTopHeadersCmd returns a new instance which can be used to issue a +// getTopHeaders JSON-RPC command. +func NewGetTopHeadersCmd(startHash *string) *GetTopHeadersCmd { + return &GetTopHeadersCmd{ + StartHash: startHash, + } +} + // GetHeadersCmd defines the getHeaders JSON-RPC command. // // NOTE: This is a btcsuite extension ported from @@ -134,5 +147,6 @@ func init() { MustRegisterCmd("getBestBlock", (*GetBestBlockCmd)(nil), flags) MustRegisterCmd("getCurrentNet", (*GetCurrentNetCmd)(nil), flags) MustRegisterCmd("getHeaders", (*GetHeadersCmd)(nil), flags) + MustRegisterCmd("getTopHeaders", (*GetTopHeadersCmd)(nil), flags) MustRegisterCmd("version", (*VersionCmd)(nil), flags) } diff --git a/btcjson/btcdextcmds_test.go b/btcjson/btcdextcmds_test.go index f4e26442d..acd565505 100644 --- a/btcjson/btcdextcmds_test.go +++ b/btcjson/btcdextcmds_test.go @@ -176,6 +176,34 @@ func TestBtcdExtCmds(t *testing.T) { HashStop: "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7", }, }, + { + name: "getTopHeaders", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getTopHeaders") + }, + staticCmd: func() interface{} { + return btcjson.NewGetTopHeadersCmd( + nil, + ) + }, + marshalled: `{"jsonrpc":"1.0","method":"getTopHeaders","params":[],"id":1}`, + unmarshalled: &btcjson.GetTopHeadersCmd{}, + }, + { + name: "getTopHeaders - with start hash", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getTopHeaders", "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7") + }, + staticCmd: func() interface{} { + return btcjson.NewGetTopHeadersCmd( + btcjson.String("000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7"), + ) + }, + marshalled: `{"jsonrpc":"1.0","method":"getTopHeaders","params":["000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7"],"id":1}`, + unmarshalled: &btcjson.GetTopHeadersCmd{ + StartHash: btcjson.String("000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7"), + }, + }, { name: "version", newCmd: func() (interface{}, error) { diff --git a/rpcclient/extensions.go b/rpcclient/extensions.go index c0d7c0d6e..dbca5e1ee 100644 --- a/rpcclient/extensions.go +++ b/rpcclient/extensions.go @@ -275,6 +275,24 @@ func (r FutureGetHeadersResult) Receive() ([]wire.BlockHeader, error) { return headers, nil } +// GetTopHeadersAsync 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 GetTopHeaders for the blocking version and more details. +func (c *Client) GetTopHeadersAsync(startHash *daghash.Hash) FutureGetHeadersResult { + var hash *string + if startHash != nil { + hash = btcjson.String(startHash.String()) + } + cmd := btcjson.NewGetTopHeadersCmd(hash) + return c.sendCmd(cmd) +} + +// GetTopHeaders sends a getTopHeaders rpc command to the server. +func (c *Client) GetTopHeaders(startHash *daghash.Hash) ([]wire.BlockHeader, error) { + return c.GetTopHeadersAsync(startHash).Receive() +} + // GetHeadersAsync 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. // diff --git a/server/rpc/rpcserver.go b/server/rpc/rpcserver.go index aeaf817ba..bcb574ea9 100644 --- a/server/rpc/rpcserver.go +++ b/server/rpc/rpcserver.go @@ -161,6 +161,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "getGenerate": handleGetGenerate, "getHashesPerSec": handleGetHashesPerSec, "getHeaders": handleGetHeaders, + "getTopHeaders": handleGetTopHeaders, "getInfo": handleGetInfo, "getManualNodeInfo": handleGetManualNodeInfo, "getMempoolInfo": handleGetMempoolInfo, @@ -2289,6 +2290,39 @@ func handleGetHashesPerSec(s *Server, cmd interface{}, closeChan <-chan struct{} return int64(s.cfg.CPUMiner.HashesPerSecond()), nil } +// handleGetTopHeaders implements the getTopHeaders command. +func handleGetTopHeaders(s *Server, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + c := cmd.(*btcjson.GetTopHeadersCmd) + + var startHash *daghash.Hash + if c.StartHash != nil { + startHash = &daghash.Hash{} + err := daghash.Decode(startHash, *c.StartHash) + if err != nil { + return nil, rpcDecodeHexError(*c.StartHash) + } + } + headers, err := s.cfg.DAG.GetTopHeaders(startHash) + if err != nil { + return nil, internalRPCError(err.Error(), + "Failed to get top headers") + } + + // Return the serialized block headers as hex-encoded strings. + hexBlockHeaders := make([]string, len(headers)) + var buf bytes.Buffer + for i, h := range headers { + 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 +} + // handleGetHeaders implements the getHeaders command. // // NOTE: This is a btcsuite extension originally ported from diff --git a/server/rpc/rpcserverhelp.go b/server/rpc/rpcserverhelp.go index e5b58153a..8989b6135 100644 --- a/server/rpc/rpcserverhelp.go +++ b/server/rpc/rpcserverhelp.go @@ -402,6 +402,11 @@ var helpDescsEnUS = map[string]string{ "infoWalletResult-relayFee": "The minimum relay fee for non-free transactions in BTC/KB", "infoWalletResult-errors": "Any current errors", + // GetTopHeadersCmd help. + "getTopHeaders--synopsis": "Returns the top block headers starting with the provided start hash (not inclusive)", + "getTopHeaders-startHash": "Block hash to stop including block headers for; if not found, all headers to the latest known block are returned.", + "getTopHeaders--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)", + // 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", @@ -707,6 +712,7 @@ var rpcResultTypes = map[string][]interface{}{ "getDifficulty": {(*float64)(nil)}, "getGenerate": {(*bool)(nil)}, "getHashesPerSec": {(*float64)(nil)}, + "getTopHeaders": {(*[]string)(nil)}, "getHeaders": {(*[]string)(nil)}, "getInfo": {(*btcjson.InfoDAGResult)(nil)}, "getManualNodeInfo": {(*string)(nil), (*btcjson.GetManualNodeInfoResult)(nil)},