From d2daf334a531a80e8d537e2f1528f3f8c902aeb2 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Mon, 19 Aug 2019 15:35:13 +0300 Subject: [PATCH] [NOD-241] Implement lower resolution peer rendezvous point discovery (#353) * [NOD-241] Implement lower resolution peer rendezvous point discovery * [NOD-241] Implement lower resolution peer rendezvous point discovery * [NOD-241] Find exact rendezvous point * [NOD-241] Find exact rendezvous point * [NOD-241] Fix tests * [NOD-241] Remove hash stop from MsgBlockLocator and add tests to MsgBlockLocator and MsgGetBlockLocator * [NOD-241] Change everywhere startHash to hashStart and change comments * [NOD-241] Fix locateBlockNodes to stop at hashStop * [NOD-241] Formatted locatorSummary. * [NOD-241] Fix node reversal * [NOD-241] Fix hash start and hash stop order, and don't include startNode in dag.blockLocator * [NOD-241] rename locateBlockNodes -> getBlueBlocksBetween and add a comment to it * [NOD-241] change hash start to start hash and hash stop to stop hash * [NOD-241] Move block locator stuff to a different file * [NOD-241] Rename msggetblocks.go to msggetblockinvs.go * [NOD-241] Format project * [NOD-241] Rename rpcserverSyncManager.LocateHeaders to GetBlueBlocksHeadersBetween * [NOD-241] Move the logic of finding the highest shared block to OnBlockLocator * [NOD-241] Rename chainHeight -> nextChainHeight * [NOD-241] Fix typo in comment --- blockdag/blocklocator.go | 143 ++++++++++ blockdag/dag.go | 265 ++++-------------- blockdag/external_dag_test.go | 10 + btcjson/btcdextcmds.go | 10 +- btcjson/btcdextcmds_test.go | 26 +- cmd/genaddr/genaddr.go | 5 + netsync/manager.go | 88 +++--- peer/log.go | 27 +- peer/peer.go | 126 ++++----- peer/peer_test.go | 6 +- rpcclient/extensions.go | 18 +- server/p2p/p2p.go | 127 ++++++--- server/rpc/rpcadapters.go | 10 +- server/rpc/rpcserver.go | 29 +- server/rpc/rpcserverhelp.go | 10 +- wire/bench_test.go | 24 +- wire/message.go | 66 +++-- wire/message_test.go | 12 +- wire/msgblocklocator.go | 112 ++++++++ ...blocks_test.go => msgblocklocator_test.go} | 137 +++------ wire/msggetblockinvs.go | 66 +++++ wire/msggetblockinvs_test.go | 238 ++++++++++++++++ wire/msggetblocklocator.go | 65 +++++ wire/msggetblocklocator_test.go | 221 +++++++++++++++ wire/msggetblocks.go | 140 --------- wire/msggetheaders.go | 80 +----- wire/msggetheaders_test.go | 153 +++------- wire/msgsendheaders.go | 3 +- 28 files changed, 1304 insertions(+), 913 deletions(-) create mode 100644 blockdag/blocklocator.go create mode 100644 wire/msgblocklocator.go rename wire/{msggetblocks_test.go => msgblocklocator_test.go} (60%) create mode 100644 wire/msggetblockinvs.go create mode 100644 wire/msggetblockinvs_test.go create mode 100644 wire/msggetblocklocator.go create mode 100644 wire/msggetblocklocator_test.go delete mode 100644 wire/msggetblocks.go diff --git a/blockdag/blocklocator.go b/blockdag/blocklocator.go new file mode 100644 index 000000000..6579dafb9 --- /dev/null +++ b/blockdag/blocklocator.go @@ -0,0 +1,143 @@ +package blockdag + +import ( + "github.com/daglabs/btcd/util" + "github.com/daglabs/btcd/util/daghash" +) + +// BlockLocator is used to help locate a specific block. The algorithm for +// building the block locator is to add block hashes in reverse order on the +// block's selected parent chain until the desired stop block is reached. +// In order to keep the list of locator hashes to a reasonable number of entries, +// the step between each entry is doubled each loop iteration to exponentially +// decrease the number of hashes as a function of the distance from the block +// being located. +// +// For example, assume a selected parent chain with IDs as depicted below, and the +// stop block is genesis: +// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 +// +// The block locator for block 17 would be the hashes of blocks: +// [17 16 14 11 7 2 genesis] +type BlockLocator []*daghash.Hash + +// BlockLocatorFromHashes returns a block locator from start and stop hash. +// See BlockLocator for details on the algorithm used to create a block locator. +// +// In addition to the general algorithm referenced above, this function will +// return the block locator for the selected tip if the passed hash is not currently +// known. +// +// This function is safe for concurrent access. +func (dag *BlockDAG) BlockLocatorFromHashes(startHash, stopHash *daghash.Hash) BlockLocator { + dag.dagLock.RLock() + defer dag.dagLock.RUnlock() + startNode := dag.index.LookupNode(startHash) + var stopNode *blockNode + if !stopHash.IsEqual(&daghash.ZeroHash) { + stopNode = dag.index.LookupNode(stopHash) + } + return dag.blockLocator(startNode, stopNode) +} + +// LatestBlockLocator returns a block locator for the current tips of the DAG. +// +// This function is safe for concurrent access. +func (dag *BlockDAG) LatestBlockLocator() BlockLocator { + dag.dagLock.RLock() + defer dag.dagLock.RUnlock() + return dag.blockLocator(nil, nil) +} + +// blockLocator returns a block locator for the passed start and stop nodes. +// The default value for the start node is the selected tip, and the default +// values of the stop node is the genesis block. +// +// See the BlockLocator type comments for more details. +// +// This function MUST be called with the DAG state lock held (for reads). +func (dag *BlockDAG) blockLocator(startNode, stopNode *blockNode) BlockLocator { + // Use the selected tip if requested. + if startNode == nil { + startNode = dag.virtual.selectedParent + } + + if stopNode == nil { + stopNode = dag.genesis + } + + // We use the selected parent of the start node, so the + // block locator won't contain the start node. + startNode = startNode.selectedParent + + // If the start node or the stop node are not in the + // virtual's selected parent chain, we replace them with their + // closest selected parent that is part of the virtual's + // selected parent chain. + for !dag.IsInSelectedParentChain(stopNode.hash) { + stopNode = stopNode.selectedParent + } + + for !dag.IsInSelectedParentChain(startNode.hash) { + startNode = startNode.selectedParent + } + + // Calculate the max number of entries that will ultimately be in the + // block locator. See the description of the algorithm for how these + // numbers are derived. + + // startNode.hash + stopNode.hash. + // Then floor(log2(startNode.chainHeight-stopNode.chainHeight)) entries for the skip portion. + maxEntries := 2 + util.FastLog2Floor(startNode.chainHeight-stopNode.chainHeight) + locator := make(BlockLocator, 0, maxEntries) + + step := uint64(1) + for node := startNode; node != nil; { + locator = append(locator, node.hash) + + // Nothing more to add once the stop node has been added. + if node.chainHeight == stopNode.chainHeight { + break + } + + // Calculate chainHeight of previous node to include ensuring the + // final node is stopNode. + nextChainHeight := node.chainHeight - step + if nextChainHeight < stopNode.chainHeight { + nextChainHeight = stopNode.chainHeight + } + + // walk backwards through the nodes to the correct ancestor. + node = node.SelectedAncestor(nextChainHeight) + + // Double the distance between included hashes. + step *= 2 + } + + return locator +} + +// FindNextLocatorBoundaries returns the lowest unknown block locator, hash +// and the highest known block locator hash. This is used to create the +// next block locator to find the highest shared known chain block with the +// sync peer. +// +// This function MUST be called with the DAG state lock held (for reads). +func (dag *BlockDAG) FindNextLocatorBoundaries(locator BlockLocator) (startHash, stopHash *daghash.Hash) { + // Find the most recent locator block hash in the DAG. In the case none of + // the hashes in the locator are in the DAG, fall back to the genesis block. + stopNode := dag.genesis + nextBlockLocatorIndex := int64(len(locator) - 1) + for i, hash := range locator { + node := dag.index.LookupNode(hash) + if node != nil { + stopNode = node + nextBlockLocatorIndex = int64(i) - 1 + break + } + } + if nextBlockLocatorIndex < 0 { + return nil, stopNode.hash + } + return locator[nextBlockLocatorIndex], stopNode.hash +} diff --git a/blockdag/dag.go b/blockdag/dag.go index 53a8ff6cd..0b2e4cff5 100644 --- a/blockdag/dag.go +++ b/blockdag/dag.go @@ -31,21 +31,6 @@ const ( FinalityInterval = 100 ) -// BlockLocator is used to help locate a specific block. The algorithm for -// building the block locator is to add block hashes in reverse order on the -// block's selected parent chain until the genesis block is reached. -// In order to keep the list of locator hashes to a reasonable number of entries, -// the step between each entry is doubled each loop iteration to exponentially -// decrease the number of hashes as a function of the distance from the block -// being located. -// -// For example, assume a selected parent chain with IDs as depicted below: -// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 -// -// The block locator for block 17 would be the hashes of blocks: -// [17 16 14 11 7 2 genesis] -type BlockLocator []*daghash.Hash - // orphanBlock represents a block that we don't yet have the parent for. It // is a normal block plus an expiration time to prevent caching the orphan // forever. @@ -1520,90 +1505,6 @@ func (dag *BlockDAG) HeaderByHash(hash *daghash.Hash) (*wire.BlockHeader, error) return node.Header(), nil } -// BlockLocatorFromHash traverses the selected parent chain of the given block hash -// until it finds a block that exists in the virtual's selected parent chain, and -// then it returns its block locator. -// See BlockLocator for details on the algorithm used to create a block locator. -// -// In addition to the general algorithm referenced above, this function will -// return the block locator for the selected tip if the passed hash is not currently -// known. -// -// This function is safe for concurrent access. -func (dag *BlockDAG) BlockLocatorFromHash(hash *daghash.Hash) BlockLocator { - dag.dagLock.RLock() - defer dag.dagLock.RUnlock() - node := dag.index.LookupNode(hash) - if node != nil { - for !dag.IsInSelectedParentChain(node.hash) { - node = node.selectedParent - } - } - locator := dag.blockLocator(node) - return locator -} - -// LatestBlockLocator returns a block locator for the current tips of the DAG. -// -// This function is safe for concurrent access. -func (dag *BlockDAG) LatestBlockLocator() BlockLocator { - dag.dagLock.RLock() - defer dag.dagLock.RUnlock() - locator := dag.blockLocator(nil) - return locator -} - -// blockLocator returns a block locator for the passed block node. The passed -// node can be nil in which case the block locator for the selected tip will be -// returned. -// -// See the BlockLocator type comments for more details. -// -// This function MUST be called with the DAG state lock held (for reads). -func (dag *BlockDAG) blockLocator(node *blockNode) BlockLocator { - // Use the selected tip if requested. - if node == nil { - node = dag.virtual.selectedParent - } - if node == nil { - return nil - } - - // Calculate the max number of entries that will ultimately be in the - // block locator. See the description of the algorithm for how these - // numbers are derived. - - // Requested hash itself + genesis block. - // Then floor(log2(height-10)) entries for the skip portion. - maxEntries := 2 + util.FastLog2Floor(node.height) - locator := make(BlockLocator, 0, maxEntries) - - step := uint64(1) - for node != nil { - locator = append(locator, node.hash) - - // Nothing more to add once the genesis block has been added. - if node.height == 0 { - break - } - - // Calculate height of previous node to include ensuring the - // final node is the genesis block. - height := node.height - step - if height < 0 { - height = 0 - } - - // walk backwards through the nodes to the correct ancestor. - node = node.SelectedAncestor(height) - - // Double the distance between included hashes. - step *= 2 - } - - return locator -} - // BlockChainHeightByHash returns the chain height of the block with the given // hash in the DAG. // @@ -1708,67 +1609,13 @@ func (dag *BlockDAG) IntervalBlockHashes(endHash *daghash.Hash, interval uint64, return hashes, nil } -// locateInventory returns the node of the block after the first known block in -// the locator along with the number of subsequent nodes needed to either reach -// the provided stop hash or the provided max number of entries. -// -// In addition, there are two special cases: -// -// - When no locators are provided, the stop hash is treated as a request for -// that block, so it will either return the node associated with the stop hash -// if it is known, or nil if it is unknown -// - When locators are provided, but none of them are known, nodes starting -// after the genesis block will be returned -// -// This is primarily a helper function for the locateBlocks and locateHeaders -// functions. +// getBlueBlocksHashesBetween returns the hashes of the blocks after the provided +// start hash until the provided stop hash is reached, or up to the +// provided max number of block hashes. // // This function MUST be called with the DAG state lock held (for reads). -func (dag *BlockDAG) locateInventory(locator BlockLocator, stopHash *daghash.Hash, maxEntries uint32) (*blockNode, uint32) { - // There are no block locators so a specific block is being requested - // as identified by the stop hash. - stopNode := dag.index.LookupNode(stopHash) - if len(locator) == 0 { - if stopNode == nil { - // No blocks with the stop hash were found so there is - // nothing to do. - return nil, 0 - } - return stopNode, 1 - } - - // Find the most recent locator block hash in the DAG. In the case none of - // the hashes in the locator are in the DAG, fall back to the genesis block. - startNode := dag.genesis - for _, hash := range locator { - node := dag.index.LookupNode(hash) - if node != nil { - startNode = node - break - } - } - - // Estimate how many entries are needed. - estimatedEntries := uint32((dag.selectedTip().blueScore - startNode.blueScore) + 1) - if stopNode != nil && stopNode.height >= startNode.height { - estimatedEntries = uint32((stopNode.blueScore - startNode.blueScore) + 1) - } - if estimatedEntries > maxEntries { - estimatedEntries = maxEntries - } - - return startNode, estimatedEntries -} - -// locateBlocks returns the hashes of the blocks after the first known block in -// the locator until the provided stop hash is reached, or up to the provided -// max number of block hashes. -// -// See the comment on the exported function for more details on special cases. -// -// This function MUST be called with the DAG state lock held (for reads). -func (dag *BlockDAG) locateBlocks(locator BlockLocator, stopHash *daghash.Hash, maxHashes uint32) []*daghash.Hash { - nodes := dag.locateBlockNodes(locator, stopHash, maxHashes) +func (dag *BlockDAG) getBlueBlocksHashesBetween(startHash, stopHash *daghash.Hash, maxHashes uint64) []*daghash.Hash { + nodes := dag.getBlueBlocksBetween(startHash, stopHash, maxHashes) hashes := make([]*daghash.Hash, len(nodes)) for i, node := range nodes { hashes[i] = node.hash @@ -1776,67 +1623,65 @@ func (dag *BlockDAG) locateBlocks(locator BlockLocator, stopHash *daghash.Hash, return hashes } -func (dag *BlockDAG) locateBlockNodes(locator BlockLocator, stopHash *daghash.Hash, maxEntries uint32) []*blockNode { - // Find the first known block in the locator and the estimated number of - // nodes after it needed while respecting the stop hash and max entries. - node, estimatedEntries := dag.locateInventory(locator, stopHash, maxEntries) - if estimatedEntries == 0 { +func (dag *BlockDAG) getBlueBlocksBetween(startHash, stopHash *daghash.Hash, maxEntries uint64) []*blockNode { + startNode := dag.index.LookupNode(startHash) + if startNode == nil { return nil } stopNode := dag.index.LookupNode(stopHash) + if stopNode == nil { + stopNode = dag.selectedTip() + } + + // In order to get no more then maxEntries of blue blocks from + // the future of the start node (including itself), we iterate + // the selected parent chain of the stopNode and add the blues + // each node (including the stopNode itself). This is why the + // number of returned blocks will be + // stopNode.blueScore-startNode.blueScore+1. + // If stopNode.blueScore-startNode.blueScore+1 > maxEntries, we + // first iterate on the selected parent chain of the stop node + // until we find a new stop node + // where stopNode.blueScore-startNode.blueScore+1 <= maxEntries + + for stopNode.blueScore-startNode.blueScore+1 > maxEntries { + stopNode = stopNode.selectedParent + } // Populate and return the found nodes. - nodes := make([]*blockNode, 0, estimatedEntries) - queue := newUpHeap() - queue.pushSet(node.children) - - visited := newSet() - for queue.Len() > 0 && uint32(len(nodes)) < maxEntries { - var current *blockNode - current = queue.pop() - if !visited.contains(current) { - visited.add(current) - isBeforeStop := (stopNode == nil) || (current.height < stopNode.height) - if isBeforeStop || current.hash.IsEqual(stopHash) { - nodes = append(nodes, current) - } - if isBeforeStop { - queue.pushSet(current.children) - } + nodes := make([]*blockNode, 0, stopNode.blueScore-startNode.blueScore+1) + nodes = append(nodes, stopNode) + for current := stopNode; current != startNode; current = current.selectedParent { + for _, blue := range current.blues { + nodes = append(nodes, blue) } } - return nodes + reversedNodes := make([]*blockNode, len(nodes)) + for i, node := range nodes { + reversedNodes[len(reversedNodes)-i-1] = node + } + return reversedNodes } -// LocateBlocks returns the hashes of the blocks after the first known block in -// the locator until the provided stop hash is reached, or up to the provided -// max number of block hashes. -// -// In addition, there are two special cases: -// -// - When no locators are provided, the stop hash is treated as a request for -// that block, so it will either return the stop hash itself if it is known, -// or nil if it is unknown -// - When locators are provided, but none of them are known, hashes starting -// after the genesis block will be returned +// GetBlueBlocksHashesBetween returns the hashes of the blue blocks after the +// provided start hash until the provided stop hash is reached, or up to the +// provided max number of block hashes. // // This function is safe for concurrent access. -func (dag *BlockDAG) LocateBlocks(locator BlockLocator, stopHash *daghash.Hash, maxHashes uint32) []*daghash.Hash { +func (dag *BlockDAG) GetBlueBlocksHashesBetween(startHash, stopHash *daghash.Hash, maxHashes uint64) []*daghash.Hash { dag.dagLock.RLock() - hashes := dag.locateBlocks(locator, stopHash, maxHashes) + hashes := dag.getBlueBlocksHashesBetween(startHash, stopHash, maxHashes) dag.dagLock.RUnlock() return hashes } -// locateHeaders returns the headers of the blocks after the first known block -// in the locator until the provided stop hash is reached, or up to the provided -// max number of block headers. -// -// See the comment on the exported function for more details on special cases. +// getBlueBlocksHeadersBetween returns the headers of the blue blocks after the +// provided start hash until the provided stop hash is reached, or up to the +// provided max number of block headers. // // This function MUST be called with the DAG state lock held (for reads). -func (dag *BlockDAG) locateHeaders(locator BlockLocator, stopHash *daghash.Hash, maxHeaders uint32) []*wire.BlockHeader { - nodes := dag.locateBlockNodes(locator, stopHash, maxHeaders) +func (dag *BlockDAG) getBlueBlocksHeadersBetween(startHash, stopHash *daghash.Hash, maxHeaders uint64) []*wire.BlockHeader { + nodes := dag.getBlueBlocksBetween(startHash, stopHash, maxHeaders) headers := make([]*wire.BlockHeader, len(nodes)) for i, node := range nodes { headers[i] = node.Header() @@ -1880,22 +1725,14 @@ func (dag *BlockDAG) RUnlock() { dag.dagLock.RUnlock() } -// LocateHeaders returns the headers of the blocks after the first known block -// in the locator until the provided stop hash is reached, or up to a max of -// wire.MaxBlockHeadersPerMsg headers. -// -// In addition, there are two special cases: -// -// - When no locators are provided, the stop hash is treated as a request for -// that header, so it will either return the header for the stop hash itself -// if it is known, or nil if it is unknown -// - When locators are provided, but none of them are known, headers starting -// after the genesis block will be returned +// GetBlueBlocksHeadersBetween returns the headers of the blocks after the provided +// start hash until the provided stop hash is reached, or up to the +// provided max number of block headers. // // This function is safe for concurrent access. -func (dag *BlockDAG) LocateHeaders(locator BlockLocator, stopHash *daghash.Hash) []*wire.BlockHeader { +func (dag *BlockDAG) GetBlueBlocksHeadersBetween(startHash, stopHash *daghash.Hash) []*wire.BlockHeader { dag.dagLock.RLock() - headers := dag.locateHeaders(locator, stopHash, wire.MaxBlockHeadersPerMsg) + headers := dag.getBlueBlocksHeadersBetween(startHash, stopHash, wire.MaxBlockHeadersPerMsg) dag.dagLock.RUnlock() return headers } diff --git a/blockdag/external_dag_test.go b/blockdag/external_dag_test.go index 15d2afa62..5a8bbbdfc 100644 --- a/blockdag/external_dag_test.go +++ b/blockdag/external_dag_test.go @@ -160,6 +160,16 @@ func TestFinality(t *testing.T) { } } +// TestFinalityInterval tests that the finality interval is +// smaller then wire.MaxInvPerMsg, so when a peer receives +// a getblocks message it should always be able to send +// all the necessary invs. +func TestFinalityInterval(t *testing.T) { + if blockdag.FinalityInterval > wire.MaxInvPerMsg { + t.Errorf("blockdag.FinalityInterval should be lower or equal to wire.MaxInvPerMsg") + } +} + // TestSubnetworkRegistry tests the full subnetwork registry flow func TestSubnetworkRegistry(t *testing.T) { params := dagconfig.SimNetParams diff --git a/btcjson/btcdextcmds.go b/btcjson/btcdextcmds.go index e1cbb860c..5b3a00b55 100644 --- a/btcjson/btcdextcmds.go +++ b/btcjson/btcdextcmds.go @@ -108,8 +108,8 @@ func NewGetTopHeadersCmd(startHash *string) *GetTopHeadersCmd { // NOTE: This is a btcsuite extension ported from // github.com/decred/dcrd/dcrjson. type GetHeadersCmd struct { - BlockLocators []string `json:"blockLocators"` - StopHash string `json:"stopHash"` + StartHash string `json:"startHash"` + StopHash string `json:"stopHash"` } // NewGetHeadersCmd returns a new instance which can be used to issue a @@ -117,10 +117,10 @@ type GetHeadersCmd struct { // // NOTE: This is a btcsuite extension ported from // github.com/decred/dcrd/dcrjson. -func NewGetHeadersCmd(blockLocators []string, stopHash string) *GetHeadersCmd { +func NewGetHeadersCmd(startHash, stopHash string) *GetHeadersCmd { return &GetHeadersCmd{ - BlockLocators: blockLocators, - StopHash: stopHash, + StartHash: startHash, + StopHash: stopHash, } } diff --git a/btcjson/btcdextcmds_test.go b/btcjson/btcdextcmds_test.go index 44a763063..62c04d1d2 100644 --- a/btcjson/btcdextcmds_test.go +++ b/btcjson/btcdextcmds_test.go @@ -139,41 +139,35 @@ func TestBtcdExtCmds(t *testing.T) { { name: "getHeaders", newCmd: func() (interface{}, error) { - return btcjson.NewCmd("getHeaders", []string{}, "") + return btcjson.NewCmd("getHeaders", "", "") }, staticCmd: func() interface{} { return btcjson.NewGetHeadersCmd( - []string{}, + "", "", ) }, - marshalled: `{"jsonrpc":"1.0","method":"getHeaders","params":[[],""],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"getHeaders","params":["",""],"id":1}`, unmarshalled: &btcjson.GetHeadersCmd{ - BlockLocators: []string{}, - StopHash: "", + StartHash: "", + StopHash: "", }, }, { name: "getHeaders - with arguments", newCmd: func() (interface{}, error) { - return btcjson.NewCmd("getHeaders", []string{"000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16", "0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10"}, "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7") + return btcjson.NewCmd("getHeaders", "000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16", "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7") }, staticCmd: func() interface{} { return btcjson.NewGetHeadersCmd( - []string{ - "000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16", - "0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10", - }, + "000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16", "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7", ) }, - marshalled: `{"jsonrpc":"1.0","method":"getHeaders","params":[["000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16","0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10"],"000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7"],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"getHeaders","params":["000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16","000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7"],"id":1}`, unmarshalled: &btcjson.GetHeadersCmd{ - BlockLocators: []string{ - "000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16", - "0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10", - }, - StopHash: "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7", + StartHash: "000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16", + StopHash: "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7", }, }, { diff --git a/cmd/genaddr/genaddr.go b/cmd/genaddr/genaddr.go index 500ed1eb1..791cc011b 100644 --- a/cmd/genaddr/genaddr.go +++ b/cmd/genaddr/genaddr.go @@ -18,6 +18,11 @@ func main() { os.Exit(1) } fmt.Printf("\nPrivate key (base-58): %s\n", base58.Encode(privateKey.Serialize())) + wif, err := util.NewWIF(privateKey, activeNetParams.PrivateKeyID, true) + if err != nil { + panic(fmt.Sprintf("error generating wif: %s", err)) + } + fmt.Printf("\nPrivate key wif: %s\n", wif) addr, err := util.NewAddressPubKeyHashFromPublicKey(privateKey.PubKey().SerializeCompressed(), activeNetParams.Prefix) if err != nil { fmt.Fprintf(os.Stderr, "Failed to generate p2pkh address: %s", err) diff --git a/netsync/manager.go b/netsync/manager.go index 39ec7f8bd..38bbcd45e 100644 --- a/netsync/manager.go +++ b/netsync/manager.go @@ -157,7 +157,7 @@ type SyncManager struct { shutdown int32 dag *blockdag.BlockDAG txMemPool *mempool.TxPool - chainParams *dagconfig.Params + dagParams *dagconfig.Params progressLogger *blockProgressLogger msgChan chan interface{} wg sync.WaitGroup @@ -177,6 +177,40 @@ type SyncManager struct { nextCheckpoint *dagconfig.Checkpoint } +func (sm *SyncManager) PushGetBlockInvsOrHeaders(peer *peerpkg.Peer, startHash *daghash.Hash) error { + // When the current height is less than a known checkpoint we + // can use block headers to learn about which blocks comprise + // the DAG up to the checkpoint and perform less validation + // for them. This is possible since each header contains the + // hash of the previous header and a merkle root. Therefore if + // we validate all of the received headers link together + // properly and the checkpoint hashes match, we can be sure the + // hashes for the blocks in between are accurate. Further, once + // the full blocks are downloaded, the merkle root is computed + // and compared against the value in the header which proves the + // full block hasn't been tampered with. + // + // Once we have passed the final checkpoint, or checkpoints are + // disabled, use standard inv messages learn about the blocks + // and fully validate them. Finally, regression test mode does + // not support the headers-first approach so do normal block + // downloads when in regression test mode. + if sm.nextCheckpoint != nil && + sm.dag.ChainHeight() < sm.nextCheckpoint.ChainHeight && + sm.dagParams != &dagconfig.RegressionNetParams { + //TODO: (Ori) This is probably wrong. Done only for compilation + err := peer.PushGetHeadersMsg(startHash, sm.nextCheckpoint.Hash) + if err != nil { + return err + } + sm.headersFirstMode = true + log.Infof("Downloading headers for blocks %d to "+ + "%d from peer %s", sm.dag.ChainHeight()+1, + sm.nextCheckpoint.ChainHeight, peer.Addr()) //TODO: (Ori) This is probably wrong. Done only for compilation + } + return peer.PushGetBlockInvsMsg(startHash, &daghash.ZeroHash) +} + // resetHeaderState sets the headers-first mode state to values appropriate for // syncing from a new peer. func (sm *SyncManager) resetHeaderState(newestHash *daghash.Hash, newestHeight uint64) { @@ -261,39 +295,16 @@ func (sm *SyncManager) startSync() { // to send. sm.requestedBlocks = make(map[daghash.Hash]struct{}) - locator := sm.dag.LatestBlockLocator() - log.Infof("Syncing to block %s from peer %s", bestPeer.SelectedTip(), bestPeer.Addr()) - // When the current height is less than a known checkpoint we - // can use block headers to learn about which blocks comprise - // the chain up to the checkpoint and perform less validation - // for them. This is possible since each header contains the - // hash of the previous header and a merkle root. Therefore if - // we validate all of the received headers link together - // properly and the checkpoint hashes match, we can be sure the - // hashes for the blocks in between are accurate. Further, once - // the full blocks are downloaded, the merkle root is computed - // and compared against the value in the header which proves the - // full block hasn't been tampered with. - // - // Once we have passed the final checkpoint, or checkpoints are - // disabled, use standard inv messages learn about the blocks - // and fully validate them. Finally, regression test mode does - // not support the headers-first approach so do normal block - // downloads when in regression test mode. if sm.nextCheckpoint != nil && sm.dag.ChainHeight() < sm.nextCheckpoint.ChainHeight && - sm.chainParams != &dagconfig.RegressionNetParams { //TODO: (Ori) This is probably wrong. Done only for compilation - - bestPeer.PushGetHeadersMsg(locator, sm.nextCheckpoint.Hash) - sm.headersFirstMode = true - log.Infof("Downloading headers for blocks %d to "+ - "%d from peer %s", sm.dag.ChainHeight()+1, - sm.nextCheckpoint.ChainHeight, bestPeer.Addr()) //TODO: (Ori) This is probably wrong. Done only for compilation + sm.dagParams != &dagconfig.RegressionNetParams { + //TODO: (Ori) This is probably wrong. Done only for compilation + bestPeer.PushGetBlockLocatorMsg(sm.nextCheckpoint.Hash, sm.dagParams.GenesisHash) } else { - bestPeer.PushGetBlockInvsMsg(locator, &daghash.ZeroHash) + bestPeer.PushGetBlockLocatorMsg(&daghash.ZeroHash, sm.dagParams.GenesisHash) } sm.syncPeer = bestPeer } else { @@ -307,7 +318,7 @@ func (sm *SyncManager) isSyncCandidate(peer *peerpkg.Peer) bool { // Typically a peer is not a candidate for sync if it's not a full node, // however regression test is special in that the regression tool is // not a full node and still needs to be considered a sync candidate. - if sm.chainParams == &dagconfig.RegressionNetParams { + if sm.dagParams == &dagconfig.RegressionNetParams { // The peer is not a candidate if it's not coming from localhost // or the hostname can't be determined for some reason. host, _, err := net.SplitHostPort(peer.Addr()) @@ -517,7 +528,7 @@ func (sm *SyncManager) handleBlockMsg(bmsg *blockMsg) { // the peer or ignore the block when we're in regression test // mode in this case so the chain code is actually fed the // duplicate blocks. - if sm.chainParams != &dagconfig.RegressionNetParams { + if sm.dagParams != &dagconfig.RegressionNetParams { log.Warnf("Got unrequested block %s from %s -- "+ "disconnecting", blockHash, peer.Addr()) peer.Disconnect() @@ -654,8 +665,7 @@ func (sm *SyncManager) handleBlockMsg(bmsg *blockMsg) { parentHash := sm.nextCheckpoint.Hash sm.nextCheckpoint = sm.findNextHeaderCheckpoint(prevHeight) if sm.nextCheckpoint != nil { - locator := blockdag.BlockLocator([]*daghash.Hash{parentHash}) - err := peer.PushGetHeadersMsg(locator, sm.nextCheckpoint.Hash) + err := peer.PushGetHeadersMsg(parentHash, sm.nextCheckpoint.Hash) if err != nil { log.Warnf("Failed to send getheaders message to "+ "peer %s: %s", peer.Addr(), err) @@ -673,8 +683,7 @@ func (sm *SyncManager) handleBlockMsg(bmsg *blockMsg) { sm.headersFirstMode = false sm.headerList.Init() log.Infof("Reached the final checkpoint -- switching to normal mode") - locator := blockdag.BlockLocator([]*daghash.Hash{blockHash}) - err = peer.PushGetBlockInvsMsg(locator, &daghash.ZeroHash) + err = peer.PushGetBlockInvsMsg(blockHash, &daghash.ZeroHash) if err != nil { log.Warnf("Failed to send getblockinvs message to peer %s: %s", peer.Addr(), err) @@ -859,8 +868,7 @@ func (sm *SyncManager) handleHeadersMsg(hmsg *headersMsg) { // This header is not a checkpoint, so request the next batch of // headers starting from the latest known header and ending with the // next checkpoint. - locator := blockdag.BlockLocator([]*daghash.Hash{finalHash}) - err := peer.PushGetHeadersMsg(locator, sm.nextCheckpoint.Hash) + err := peer.PushGetHeadersMsg(finalHash, sm.nextCheckpoint.Hash) if err != nil { log.Warnf("Failed to send getheaders message to "+ "peer %s: %s", peer.Addr(), err) @@ -1015,10 +1023,8 @@ func (sm *SyncManager) handleInvMsg(imsg *invMsg) { if i == lastBlock && peer == sm.syncPeer { // Request blocks after the first block's ancestor that exists // in the selected path chain, one up to the - // final one the remote peer knows about (zero - // stop hash). - locator := sm.dag.BlockLocatorFromHash(iv.Hash) - peer.PushGetBlockInvsMsg(locator, &daghash.ZeroHash) + // final one the remote peer knows about. + peer.PushGetBlockLocatorMsg(iv.Hash, &daghash.ZeroHash) } } } @@ -1408,7 +1414,7 @@ func New(config *Config) (*SyncManager, error) { peerNotifier: config.PeerNotifier, dag: config.DAG, txMemPool: config.TxMemPool, - chainParams: config.ChainParams, + dagParams: config.ChainParams, rejectedTxns: make(map[daghash.TxID]struct{}), requestedTxns: make(map[daghash.TxID]struct{}), requestedBlocks: make(map[daghash.Hash]struct{}), diff --git a/peer/log.go b/peer/log.go index 0dfd293ef..156516841 100644 --- a/peer/log.go +++ b/peer/log.go @@ -12,7 +12,6 @@ import ( "github.com/btcsuite/btclog" "github.com/daglabs/btcd/logger" "github.com/daglabs/btcd/txscript" - "github.com/daglabs/btcd/util/daghash" "github.com/daglabs/btcd/util/panics" "github.com/daglabs/btcd/wire" ) @@ -90,16 +89,6 @@ func invSummary(invList []*wire.InvVect) string { return fmt.Sprintf("size %d", invLen) } -// locatorSummary returns a block locator as a human-readable string. -func locatorSummary(locator []*daghash.Hash, stopHash *daghash.Hash) string { - if len(locator) > 0 { - return fmt.Sprintf("locator %s, stop %s", locator[0], stopHash) - } - - return fmt.Sprintf("no locator, stop %s", stopHash) - -} - // sanitizeString strips any characters which are even remotely dangerous, such // as html control characters, from the passed string. It also limits it to // the passed maximum size, which can be 0 for unlimited. When the string is @@ -179,10 +168,22 @@ func messageSummary(msg wire.Message) string { return invSummary(msg.InvList) case *wire.MsgGetBlockInvs: - return locatorSummary(msg.BlockLocatorHashes, msg.StopHash) + return fmt.Sprintf("start hash %s, stop hash %s", msg.StartHash, + msg.StopHash) case *wire.MsgGetHeaders: - return locatorSummary(msg.BlockLocatorHashes, msg.StopHash) + return fmt.Sprintf("start hash %s, stop hash %s", msg.StartHash, + msg.StopHash) + + case *wire.MsgGetBlockLocator: + return fmt.Sprintf("start hash %s, stop hash %s", msg.StartHash, + msg.StopHash) + + case *wire.MsgBlockLocator: + if len(msg.BlockLocatorHashes) > 0 { + return fmt.Sprintf("locator first hash: %s, last hash: %s", msg.BlockLocatorHashes[0], msg.BlockLocatorHashes[len(msg.BlockLocatorHashes)-1]) + } + return fmt.Sprintf("no locator") case *wire.MsgHeaders: return fmt.Sprintf("num %d", len(msg.Headers)) diff --git a/peer/peer.go b/peer/peer.go index bea52a623..2d53907d1 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -137,6 +137,12 @@ type MessageListeners struct { // OnInv is invoked when a peer receives an inv bitcoin message. OnInv func(p *Peer, msg *wire.MsgInv) + // OnGetBlockLocator is invoked when a peer receives a getlocator bitcoin message. + OnGetBlockLocator func(p *Peer, msg *wire.MsgGetBlockLocator) + + // OnBlockLocator is invoked when a peer receives a locator bitcoin message. + OnBlockLocator func(p *Peer, msg *wire.MsgBlockLocator) + // OnHeaders is invoked when a peer receives a headers bitcoin message. OnHeaders func(p *Peer, msg *wire.MsgHeaders) @@ -445,10 +451,10 @@ type Peer struct { knownInventory *mruInventoryMap prevGetBlockInvsMtx sync.Mutex - prevGetBlockInvsBegin *daghash.Hash + prevGetBlockInvsStart *daghash.Hash prevGetBlockInvsStop *daghash.Hash prevGetHdrsMtx sync.Mutex - prevGetHdrsBegin *daghash.Hash + prevGetHdrsStart *daghash.Hash prevGetHdrsStop *daghash.Hash // These fields keep track of statistics for the peer and are protected @@ -858,33 +864,48 @@ func (p *Peer) PushAddrMsg(addresses []*wire.NetAddress, subnetworkID *subnetwor return msg.AddrList, nil } +func (p *Peer) PushGetBlockLocatorMsg(startHash, stopHash *daghash.Hash) { + msg := wire.NewMsgGetBlockLocator(startHash, stopHash) + p.QueueMessage(msg, nil) +} + // PushGetBlockInvsMsg sends a getblockinvs message for the provided block locator // and stop hash. It will ignore back-to-back duplicate requests. // // This function is safe for concurrent access. -func (p *Peer) PushGetBlockInvsMsg(locator blockdag.BlockLocator, stopHash *daghash.Hash) error { - // Extract the begin hash from the block locator, if one was specified, - // to use for filtering duplicate getblockinvs requests. - var beginHash *daghash.Hash - if len(locator) > 0 { - beginHash = locator[0] - } - +func (p *Peer) PushGetBlockInvsMsg(startHash, stopHash *daghash.Hash) error { // Filter duplicate getblockinvs requests. p.prevGetBlockInvsMtx.Lock() - isDuplicate := p.prevGetBlockInvsStop != nil && p.prevGetBlockInvsBegin != nil && - beginHash != nil && stopHash.IsEqual(p.prevGetBlockInvsStop) && - beginHash.IsEqual(p.prevGetBlockInvsBegin) + isDuplicate := p.prevGetBlockInvsStop != nil && p.prevGetBlockInvsStart != nil && + startHash != nil && stopHash.IsEqual(p.prevGetBlockInvsStop) && + startHash.IsEqual(p.prevGetBlockInvsStart) p.prevGetBlockInvsMtx.Unlock() if isDuplicate { - log.Tracef("Filtering duplicate [getblockinvs] with begin "+ - "hash %s, stop hash %s", beginHash, stopHash) + log.Tracef("Filtering duplicate [getblockinvs] with start "+ + "hash %s, stop hash %s", startHash, stopHash) return nil } // Construct the getblockinvs request and queue it to be sent. - msg := wire.NewMsgGetBlockInvs(stopHash) + msg := wire.NewMsgGetBlockInvs(startHash, stopHash) + p.QueueMessage(msg, nil) + + // Update the previous getblockinvs request information for filtering + // duplicates. + p.prevGetBlockInvsMtx.Lock() + p.prevGetBlockInvsStart = startHash + p.prevGetBlockInvsStop = stopHash + p.prevGetBlockInvsMtx.Unlock() + return nil +} + +// PushBlockLocatorMsg sends a locator message for the provided block locator. +// +// This function is safe for concurrent access. +func (p *Peer) PushBlockLocatorMsg(locator blockdag.BlockLocator) error { + // Construct the locator request and queue it to be sent. + msg := wire.NewMsgBlockLocator() for _, hash := range locator { err := msg.AddBlockLocatorHash(hash) if err != nil { @@ -892,13 +913,6 @@ func (p *Peer) PushGetBlockInvsMsg(locator blockdag.BlockLocator, stopHash *dagh } } p.QueueMessage(msg, nil) - - // Update the previous getblockinvs request information for filtering - // duplicates. - p.prevGetBlockInvsMtx.Lock() - p.prevGetBlockInvsBegin = beginHash - p.prevGetBlockInvsStop = stopHash - p.prevGetBlockInvsMtx.Unlock() return nil } @@ -906,42 +920,28 @@ func (p *Peer) PushGetBlockInvsMsg(locator blockdag.BlockLocator, stopHash *dagh // and stop hash. It will ignore back-to-back duplicate requests. // // This function is safe for concurrent access. -func (p *Peer) PushGetHeadersMsg(locator blockdag.BlockLocator, stopHash *daghash.Hash) error { - // Extract the begin hash from the block locator, if one was specified, - // to use for filtering duplicate getheaders requests. - var beginHash *daghash.Hash - if len(locator) > 0 { - beginHash = locator[0] - } - +func (p *Peer) PushGetHeadersMsg(startHash, stopHash *daghash.Hash) error { // Filter duplicate getheaders requests. p.prevGetHdrsMtx.Lock() - isDuplicate := p.prevGetHdrsStop != nil && p.prevGetHdrsBegin != nil && - beginHash != nil && stopHash.IsEqual(p.prevGetHdrsStop) && - beginHash.IsEqual(p.prevGetHdrsBegin) + isDuplicate := p.prevGetHdrsStop != nil && p.prevGetHdrsStart != nil && + startHash != nil && stopHash.IsEqual(p.prevGetHdrsStop) && + startHash.IsEqual(p.prevGetHdrsStart) p.prevGetHdrsMtx.Unlock() if isDuplicate { - log.Tracef("Filtering duplicate [getheaders] with begin hash %s", - beginHash) + log.Tracef("Filtering duplicate [getheaders] with start hash %s", + startHash) return nil } // Construct the getheaders request and queue it to be sent. - msg := wire.NewMsgGetHeaders() - msg.StopHash = stopHash - for _, hash := range locator { - err := msg.AddBlockLocatorHash(hash) - if err != nil { - return err - } - } + msg := wire.NewMsgGetHeaders(startHash, stopHash) p.QueueMessage(msg, nil) // Update the previous getheaders request information for filtering // duplicates. p.prevGetHdrsMtx.Lock() - p.prevGetHdrsBegin = beginHash + p.prevGetHdrsStart = startHash p.prevGetHdrsStop = stopHash p.prevGetHdrsMtx.Unlock() return nil @@ -1517,6 +1517,16 @@ out: p.cfg.Listeners.OnGetData(p, msg) } + case *wire.MsgGetBlockLocator: + if p.cfg.Listeners.OnGetBlockLocator != nil { + p.cfg.Listeners.OnGetBlockLocator(p, msg) + } + + case *wire.MsgBlockLocator: + if p.cfg.Listeners.OnBlockLocator != nil { + p.cfg.Listeners.OnBlockLocator(p, msg) + } + case *wire.MsgGetBlockInvs: if p.cfg.Listeners.OnGetBlockInvs != nil { p.cfg.Listeners.OnGetBlockInvs(p, msg) @@ -1748,26 +1758,6 @@ cleanup: log.Tracef("Peer queue handler done for %s", p) } -// shouldLogWriteError returns whether or not the passed error, which is -// expected to have come from writing to the remote peer in the outHandler, -// should be logged. -func (p *Peer) shouldLogWriteError(err error) bool { - // No logging when the peer is being forcibly disconnected. - if atomic.LoadInt32(&p.disconnect) != 0 { - return false - } - - // No logging when the remote peer has been disconnected. - if err == io.EOF { - return false - } - if opErr, ok := err.(*net.OpError); ok && !opErr.Temporary() { - return false - } - - return true -} - // outHandler handles all outgoing messages for the peer. It must be run as a // goroutine. It uses a buffered channel to serialize output messages while // allowing the sender to continue running asynchronously. @@ -1789,10 +1779,8 @@ out: err := p.writeMessage(msg.msg) if err != nil { p.Disconnect() - if p.shouldLogWriteError(err) { - log.Errorf("Failed to send message to "+ - "%s: %s", p, err) - } + log.Errorf("Failed to send message to "+ + "%s: %s", p, err) if msg.doneChan != nil { msg.doneChan <- struct{}{} } diff --git a/peer/peer_test.go b/peer/peer_test.go index b4281eb40..e9b782b43 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -514,11 +514,11 @@ func TestPeerListeners(t *testing.T) { }, { "OnGetBlockInvs", - wire.NewMsgGetBlockInvs(&daghash.Hash{}), + wire.NewMsgGetBlockInvs(&daghash.Hash{}, &daghash.Hash{}), }, { "OnGetHeaders", - wire.NewMsgGetHeaders(), + wire.NewMsgGetHeaders(&daghash.Hash{}, &daghash.Hash{}), }, { "OnGetCFilters", @@ -694,7 +694,7 @@ func TestOutboundPeer(t *testing.T) { p2.QueueMessage(wire.NewMsgPing(1), nil) p2.QueueMessage(wire.NewMsgMemPool(), nil) p2.QueueMessage(wire.NewMsgGetData(), nil) - p2.QueueMessage(wire.NewMsgGetHeaders(), nil) + p2.QueueMessage(wire.NewMsgGetHeaders(&daghash.ZeroHash, &daghash.ZeroHash), nil) p2.QueueMessage(wire.NewMsgFeeFilter(20000), nil) p2.Disconnect() diff --git a/rpcclient/extensions.go b/rpcclient/extensions.go index da2b3ae6e..a30aae89d 100644 --- a/rpcclient/extensions.go +++ b/rpcclient/extensions.go @@ -216,16 +216,16 @@ func (c *Client) GetTopHeaders(startHash *daghash.Hash) ([]wire.BlockHeader, err // // NOTE: This is a btcsuite extension ported from // github.com/decred/dcrrpcclient. -func (c *Client) GetHeadersAsync(blockLocators []*daghash.Hash, stopHash *daghash.Hash) FutureGetHeadersResult { - locators := make([]string, len(blockLocators)) - for i := range blockLocators { - locators[i] = blockLocators[i].String() +func (c *Client) GetHeadersAsync(startHash, stopHash *daghash.Hash) FutureGetHeadersResult { + startHashStr := "" + if startHash != nil { + startHashStr = startHash.String() } - hash := "" + stopHashStr := "" if stopHash != nil { - hash = stopHash.String() + stopHashStr = stopHash.String() } - cmd := btcjson.NewGetHeadersCmd(locators, hash) + cmd := btcjson.NewGetHeadersCmd(startHashStr, stopHashStr) return c.sendCmd(cmd) } @@ -235,8 +235,8 @@ func (c *Client) GetHeadersAsync(blockLocators []*daghash.Hash, stopHash *daghas // // NOTE: This is a btcsuite extension ported from // github.com/decred/dcrrpcclient. -func (c *Client) GetHeaders(blockLocators []*daghash.Hash, stopHash *daghash.Hash) ([]wire.BlockHeader, error) { - return c.GetHeadersAsync(blockLocators, stopHash).Receive() +func (c *Client) GetHeaders(startHash, stopHash *daghash.Hash) ([]wire.BlockHeader, error) { + return c.GetHeadersAsync(startHash, stopHash).Receive() } // FutureSessionResult is a future promise to deliver the result of a diff --git a/server/p2p/p2p.go b/server/p2p/p2p.go index 216cb2881..bcceb3397 100644 --- a/server/p2p/p2p.go +++ b/server/p2p/p2p.go @@ -682,21 +682,80 @@ func (sp *Peer) OnGetData(_ *peer.Peer, msg *wire.MsgGetData) { } } +// OnGetBlockLocator is invoked when a peer receives a getlocator bitcoin +// message. +func (sp *Peer) OnGetBlockLocator(_ *peer.Peer, msg *wire.MsgGetBlockLocator) { + locator := sp.server.DAG.BlockLocatorFromHashes(msg.StartHash, msg.StopHash) + + if len(locator) == 0 { + peerLog.Infof("Couldn't build a block locator between blocks %s and %s"+ + " that was requested from peer %s", + sp) + return + } + err := sp.PushBlockLocatorMsg(locator) + if err != nil { + peerLog.Errorf("Failed to send block locator message to peer %s: %s", + sp, err) + return + } +} + +// OnBlockLocator is invoked when a peer receives a locator bitcoin +// message. +func (sp *Peer) OnBlockLocator(_ *peer.Peer, msg *wire.MsgBlockLocator) { + // Find the highest known shared block between the peers, and asks + // the block and its future from the peer. If the block is not + // found, create a lower resolution block locator and send it to + // the peer in order to find it in the next iteration. + dag := sp.server.DAG + if len(msg.BlockLocatorHashes) == 0 { + peerLog.Warnf("Got empty block locator from peer %s", + sp) + return + } + // If the first hash of the block locator is known, it means we found + // the highest shared block. + firstHash := msg.BlockLocatorHashes[0] + exists, err := dag.BlockExists(firstHash) + if err != nil { + peerLog.Errorf("Error checking if first hash in the block"+ + " locator (%s) exists in the dag: %s", + msg.BlockLocatorHashes[0], sp, err) + return + } + if exists { + err := sp.server.SyncManager.PushGetBlockInvsOrHeaders(sp.Peer, firstHash) + if err != nil { + peerLog.Errorf("Failed pushing get blocks message for peer %s: %s", + sp, err) + return + } + return + } + startHash, stopHash := dag.FindNextLocatorBoundaries(msg.BlockLocatorHashes) + if startHash == nil { + panic("Couldn't find any unknown hashes in the block locator.") + } + sp.PushGetBlockLocatorMsg(startHash, stopHash) +} + // OnGetBlockInvs is invoked when a peer receives a getblockinvs bitcoin // message. +// It finds the blue future between msg.StartHash and msg.StopHash +// and send the invs to the requesting peer. func (sp *Peer) OnGetBlockInvs(_ *peer.Peer, msg *wire.MsgGetBlockInvs) { - // Find the most recent known block in the dag based on the block - // locator and fetch all of the block hashes after it until either - // wire.MaxBlocksPerMsg have been fetched or the provided stop hash is - // encountered. - // - // Use the block after the genesis block if no other blocks in the - // provided locator are known. This does mean the client will start - // over with the genesis block if unknown block locators are provided. - // - // This mirrors the behavior in the reference implementation. dag := sp.server.DAG - hashList := dag.LocateBlocks(msg.BlockLocatorHashes, msg.StopHash, + // We want to prevent a situation where the syncing peer needs + // to call getblocks once again, but the block we sent him + // won't affect his selected chain, so next time it'll try + // to find the highest shared chain block, it'll find the + // same one as before. + // To prevent that we use blockdag.FinalityInterval as maxHashes. + // This way, if one getblocks is not enough to get the peer + // synced, we can know for sure that its selected chain will + // change, so we'll have higher shared chain block. + hashList := dag.GetBlueBlocksHashesBetween(msg.StartHash, msg.StopHash, wire.MaxInvPerMsg) // Generate inventory message. @@ -728,10 +787,8 @@ func (sp *Peer) OnGetHeaders(_ *peer.Peer, msg *wire.MsgGetHeaders) { // Use the block after the genesis block if no other blocks in the // provided locator are known. This does mean the client will start // over with the genesis block if unknown block locators are provided. - // - // This mirrors the behavior in the reference implementation. dag := sp.server.DAG - headers := dag.LocateHeaders(msg.BlockLocatorHashes, msg.StopHash) + headers := dag.GetBlueBlocksHeadersBetween(msg.StartHash, msg.StopHash) // Send found headers to the requesting peer. blockHeaders := make([]*wire.BlockHeader, len(headers)) @@ -1771,26 +1828,28 @@ func disconnectPeer(peerList map[int32]*Peer, compareFunc func(*Peer) bool, when func newPeerConfig(sp *Peer) *peer.Config { return &peer.Config{ Listeners: peer.MessageListeners{ - OnVersion: sp.OnVersion, - OnMemPool: sp.OnMemPool, - OnTx: sp.OnTx, - OnBlock: sp.OnBlock, - OnInv: sp.OnInv, - OnHeaders: sp.OnHeaders, - OnGetData: sp.OnGetData, - OnGetBlockInvs: sp.OnGetBlockInvs, - OnGetHeaders: sp.OnGetHeaders, - OnGetCFilters: sp.OnGetCFilters, - OnGetCFHeaders: sp.OnGetCFHeaders, - OnGetCFCheckpt: sp.OnGetCFCheckpt, - OnFeeFilter: sp.OnFeeFilter, - OnFilterAdd: sp.OnFilterAdd, - OnFilterClear: sp.OnFilterClear, - OnFilterLoad: sp.OnFilterLoad, - OnGetAddr: sp.OnGetAddr, - OnAddr: sp.OnAddr, - OnRead: sp.OnRead, - OnWrite: sp.OnWrite, + OnVersion: sp.OnVersion, + OnMemPool: sp.OnMemPool, + OnTx: sp.OnTx, + OnBlock: sp.OnBlock, + OnInv: sp.OnInv, + OnHeaders: sp.OnHeaders, + OnGetData: sp.OnGetData, + OnGetBlockLocator: sp.OnGetBlockLocator, + OnBlockLocator: sp.OnBlockLocator, + OnGetBlockInvs: sp.OnGetBlockInvs, + OnGetHeaders: sp.OnGetHeaders, + OnGetCFilters: sp.OnGetCFilters, + OnGetCFHeaders: sp.OnGetCFHeaders, + OnGetCFCheckpt: sp.OnGetCFCheckpt, + OnFeeFilter: sp.OnFeeFilter, + OnFilterAdd: sp.OnFilterAdd, + OnFilterClear: sp.OnFilterClear, + OnFilterLoad: sp.OnFilterLoad, + OnGetAddr: sp.OnGetAddr, + OnAddr: sp.OnAddr, + OnRead: sp.OnRead, + OnWrite: sp.OnWrite, // Note: The reference client currently bans peers that send alerts // not signed with its key. We could verify against their key, but diff --git a/server/rpc/rpcadapters.go b/server/rpc/rpcadapters.go index 1551d874f..cb39961fd 100644 --- a/server/rpc/rpcadapters.go +++ b/server/rpc/rpcadapters.go @@ -269,12 +269,12 @@ func (b *rpcSyncMgr) SyncPeerID() int32 { return b.syncMgr.SyncPeerID() } -// LocateBlocks returns the hashes of the blocks after the first known block in -// the provided locators until the provided stop hash or the current tip is -// reached, up to a max of wire.MaxBlockHeadersPerMsg hashes. +// GetBlueBlocksHeadersBetween returns the headers of the blocks after the provided +// start hash until the provided stop hash is reached, or up to the +// provided max number of block headers. // // This function is safe for concurrent access and is part of the // rpcserverSyncManager interface implementation. -func (b *rpcSyncMgr) LocateHeaders(locators []*daghash.Hash, stopHash *daghash.Hash) []*wire.BlockHeader { - return b.server.DAG.LocateHeaders(locators, stopHash) +func (b *rpcSyncMgr) GetBlueBlocksHeadersBetween(startHash, stopHash *daghash.Hash) []*wire.BlockHeader { + return b.server.DAG.GetBlueBlocksHeadersBetween(startHash, stopHash) } diff --git a/server/rpc/rpcserver.go b/server/rpc/rpcserver.go index 6bd2ccf84..9197de867 100644 --- a/server/rpc/rpcserver.go +++ b/server/rpc/rpcserver.go @@ -2383,24 +2383,21 @@ func handleGetTopHeaders(s *Server, cmd interface{}, closeChan <-chan struct{}) func handleGetHeaders(s *Server, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*btcjson.GetHeadersCmd) - // Fetch the requested headers from chain while respecting the provided - // block locators and stop hash. - blockLocators := make([]*daghash.Hash, len(c.BlockLocators)) - for i := range c.BlockLocators { - blockLocator, err := daghash.NewHashFromStr(c.BlockLocators[i]) - if err != nil { - return nil, rpcDecodeHexError(c.BlockLocators[i]) - } - blockLocators[i] = blockLocator - } - var stopHash daghash.Hash - if c.StopHash != "" { - err := daghash.Decode(&stopHash, c.StopHash) + startHash := &daghash.ZeroHash + if c.StartHash != "" { + err := daghash.Decode(startHash, c.StartHash) if err != nil { return nil, rpcDecodeHexError(c.StopHash) } } - headers := s.cfg.SyncMgr.LocateHeaders(blockLocators, &stopHash) + stopHash := &daghash.ZeroHash + if c.StopHash != "" { + err := daghash.Decode(stopHash, c.StopHash) + if err != nil { + return nil, rpcDecodeHexError(c.StopHash) + } + } + headers := s.cfg.SyncMgr.GetBlueBlocksHeadersBetween(startHash, stopHash) // Return the serialized block headers as hex-encoded strings. hexBlockHeaders := make([]string, len(headers)) @@ -4238,11 +4235,11 @@ type rpcserverSyncManager interface { // used to sync from or 0 if there is none. SyncPeerID() int32 - // LocateHeaders returns the headers of the blocks after the first known + // GetBlueBlocksHeadersBetween returns the headers of the blocks after the first known // block in the provided locators until the provided stop hash or the // current tip is reached, up to a max of wire.MaxBlockHeadersPerMsg // hashes. - LocateHeaders(locators []*daghash.Hash, stopHash *daghash.Hash) []*wire.BlockHeader + GetBlueBlocksHeadersBetween(startHash, stopHash *daghash.Hash) []*wire.BlockHeader } // rpcserverConfig is a descriptor containing the RPC server configuration. diff --git a/server/rpc/rpcserverhelp.go b/server/rpc/rpcserverhelp.go index a735c956a..9517900d6 100644 --- a/server/rpc/rpcserverhelp.go +++ b/server/rpc/rpcserverhelp.go @@ -401,14 +401,14 @@ var helpDescsEnUS = map[string]string{ // 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-startHash": "Block hash to start including block headers from; if not found, it'll start from the virtual.", "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", - "getHeaders-stopHash": "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)", + "getHeaders--synopsis": "Returns block headers starting with the first known block hash from the request", + "getHeaders-startHash": "Block hash to start including headers from; if not found, it'll start from the genesis block.", + "getHeaders-stopHash": "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.", diff --git a/wire/bench_test.go b/wire/bench_test.go index 973eb8fe4..9f39022a8 100644 --- a/wire/bench_test.go +++ b/wire/bench_test.go @@ -377,18 +377,12 @@ func BenchmarkWriteBlockHeader(b *testing.B) { } // BenchmarkDecodeGetHeaders performs a benchmark on how long it takes to -// decode a getheaders message with the maximum number of block locator hashes. +// decode a getheaders message. func BenchmarkDecodeGetHeaders(b *testing.B) { - // Create a message with the maximum number of block locators. pver := ProtocolVersion var m MsgGetHeaders - for i := 0; i < MaxBlockLocatorsPerMsg; i++ { - hash, err := daghash.NewHashFromStr(fmt.Sprintf("%x", i)) - if err != nil { - b.Fatalf("NewHashFromStr: unexpected error: %v", err) - } - m.AddBlockLocatorHash(hash) - } + m.StartHash = &daghash.Hash{1} + m.StopHash = &daghash.Hash{1} // Serialize it so the bytes are available to test the decode below. var bb bytes.Buffer @@ -446,18 +440,12 @@ func BenchmarkDecodeHeaders(b *testing.B) { } // BenchmarkDecodeGetBlockInvs performs a benchmark on how long it takes to -// decode a getblockinvs message with the maximum number of block locator hashes. +// decode a getblockinvs message. func BenchmarkDecodeGetBlockInvs(b *testing.B) { - // Create a message with the maximum number of block locators. pver := ProtocolVersion var m MsgGetBlockInvs - for i := 0; i < MaxBlockLocatorsPerMsg; i++ { - hash, err := daghash.NewHashFromStr(fmt.Sprintf("%x", i)) - if err != nil { - b.Fatalf("NewHashFromStr: unexpected error: %v", err) - } - m.AddBlockLocatorHash(hash) - } + m.StartHash = &daghash.Hash{1} + m.StopHash = &daghash.Hash{1} // Serialize it so the bytes are available to test the decode below. var bb bytes.Buffer diff --git a/wire/message.go b/wire/message.go index 5a2651596..6a8da91c6 100644 --- a/wire/message.go +++ b/wire/message.go @@ -28,35 +28,37 @@ const MaxMessagePayload = (1024 * 1024 * 32) // 32MB // Commands used in bitcoin message headers which describe the type of message. const ( - CmdVersion = "version" - CmdVerAck = "verack" - CmdGetAddr = "getaddr" - CmdAddr = "addr" - CmdGetBlockInvs = "getblockinvs" - CmdInv = "inv" - CmdGetData = "getdata" - CmdNotFound = "notfound" - CmdBlock = "block" - CmdTx = "tx" - CmdGetHeaders = "getheaders" - CmdHeaders = "headers" - CmdPing = "ping" - CmdPong = "pong" - CmdAlert = "alert" - CmdMemPool = "mempool" - CmdFilterAdd = "filteradd" - CmdFilterClear = "filterclear" - CmdFilterLoad = "filterload" - CmdMerkleBlock = "merkleblock" - CmdReject = "reject" - CmdSendHeaders = "sendheaders" - CmdFeeFilter = "feefilter" - CmdGetCFilters = "getcfilters" - CmdGetCFHeaders = "getcfheaders" - CmdGetCFCheckpt = "getcfcheckpt" - CmdCFilter = "cfilter" - CmdCFHeaders = "cfheaders" - CmdCFCheckpt = "cfcheckpt" + CmdVersion = "version" + CmdVerAck = "verack" + CmdGetAddr = "getaddr" + CmdAddr = "addr" + CmdGetBlockInvs = "getblockinvs" + CmdInv = "inv" + CmdGetData = "getdata" + CmdNotFound = "notfound" + CmdBlock = "block" + CmdTx = "tx" + CmdGetHeaders = "getheaders" + CmdHeaders = "headers" + CmdPing = "ping" + CmdPong = "pong" + CmdAlert = "alert" + CmdMemPool = "mempool" + CmdFilterAdd = "filteradd" + CmdFilterClear = "filterclear" + CmdFilterLoad = "filterload" + CmdMerkleBlock = "merkleblock" + CmdReject = "reject" + CmdSendHeaders = "sendheaders" + CmdFeeFilter = "feefilter" + CmdGetCFilters = "getcfilters" + CmdGetCFHeaders = "getcfheaders" + CmdGetCFCheckpt = "getcfcheckpt" + CmdCFilter = "cfilter" + CmdCFHeaders = "cfheaders" + CmdCFCheckpt = "cfcheckpt" + CmdGetBlockLocator = "getlocator" + CmdBlockLocator = "locator" ) // Message is an interface that describes a bitcoin message. A type that @@ -99,6 +101,12 @@ func makeEmptyMessage(command string) (Message, error) { case CmdGetData: msg = &MsgGetData{} + case CmdGetBlockLocator: + msg = &MsgGetBlockLocator{} + + case CmdBlockLocator: + msg = &MsgBlockLocator{} + case CmdNotFound: msg = &MsgNotFound{} diff --git a/wire/message_test.go b/wire/message_test.go index 4e8fcaa4f..1a972e62b 100644 --- a/wire/message_test.go +++ b/wire/message_test.go @@ -51,7 +51,7 @@ func TestMessage(t *testing.T) { msgVerack := NewMsgVerAck() msgGetAddr := NewMsgGetAddr(false, nil) msgAddr := NewMsgAddr(false, nil) - msgGetBlockInvs := NewMsgGetBlockInvs(&daghash.Hash{}) + msgGetBlockInvs := NewMsgGetBlockInvs(&daghash.Hash{}, &daghash.Hash{}) msgBlock := &blockOne msgInv := NewMsgInv() msgGetData := NewMsgGetData() @@ -59,7 +59,9 @@ func TestMessage(t *testing.T) { msgTx := NewNativeMsgTx(1, nil, nil) msgPing := NewMsgPing(123123) msgPong := NewMsgPong(123123) - msgGetHeaders := NewMsgGetHeaders() + msgGetHeaders := NewMsgGetHeaders(&daghash.Hash{}, &daghash.Hash{}) + msgGetBlockLocator := NewMsgGetBlockLocator(&daghash.ZeroHash, &daghash.ZeroHash) + msgBlockLocator := NewMsgBlockLocator() msgSendHeaders := NewMsgSendHeaders() msgFeeFilter := NewMsgFeeFilter(123456) msgHeaders := NewMsgHeaders() @@ -90,7 +92,7 @@ func TestMessage(t *testing.T) { {msgVerack, msgVerack, pver, MainNet, 24}, {msgGetAddr, msgGetAddr, pver, MainNet, 26}, {msgAddr, msgAddr, pver, MainNet, 27}, - {msgGetBlockInvs, msgGetBlockInvs, pver, MainNet, 61}, + {msgGetBlockInvs, msgGetBlockInvs, pver, MainNet, 88}, {msgBlock, msgBlock, pver, MainNet, 372}, {msgInv, msgInv, pver, MainNet, 25}, {msgGetData, msgGetData, pver, MainNet, 25}, @@ -98,7 +100,9 @@ func TestMessage(t *testing.T) { {msgTx, msgTx, pver, MainNet, 58}, {msgPing, msgPing, pver, MainNet, 32}, {msgPong, msgPong, pver, MainNet, 32}, - {msgGetHeaders, msgGetHeaders, pver, MainNet, 61}, + {msgGetHeaders, msgGetHeaders, pver, MainNet, 88}, + {msgGetBlockLocator, msgGetBlockLocator, pver, MainNet, 88}, + {msgBlockLocator, msgBlockLocator, pver, MainNet, 25}, {msgSendHeaders, msgSendHeaders, pver, MainNet, 24}, {msgFeeFilter, msgFeeFilter, pver, MainNet, 32}, {msgHeaders, msgHeaders, pver, MainNet, 25}, diff --git a/wire/msgblocklocator.go b/wire/msgblocklocator.go new file mode 100644 index 000000000..fc86ebc5b --- /dev/null +++ b/wire/msgblocklocator.go @@ -0,0 +1,112 @@ +package wire + +import ( + "fmt" + "io" + + "github.com/daglabs/btcd/util/daghash" +) + +// MaxBlockLocatorsPerMsg is the maximum number of block locator hashes allowed +// per message. +const MaxBlockLocatorsPerMsg = 500 + +// MsgBlockLocator implements the Message interface and represents a bitcoin +// locator message. It is used to find the highest known chain block with +// a peer that is syncing with you. +type MsgBlockLocator struct { + BlockLocatorHashes []*daghash.Hash +} + +// AddBlockLocatorHash adds a new block locator hash to the message. +func (msg *MsgBlockLocator) AddBlockLocatorHash(hash *daghash.Hash) error { + if len(msg.BlockLocatorHashes) >= MaxBlockLocatorsPerMsg { + str := fmt.Sprintf("too many block locator hashes for message [max %d]", + MaxBlockLocatorsPerMsg) + return messageError("MsgBlockLocator.AddBlockLocatorHash", str) + } + + msg.BlockLocatorHashes = append(msg.BlockLocatorHashes, hash) + return nil +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgBlockLocator) BtcDecode(r io.Reader, pver uint32) error { + // Read num block locator hashes and limit to max. + count, err := ReadVarInt(r) + if err != nil { + return err + } + if count > MaxBlockLocatorsPerMsg { + str := fmt.Sprintf("too many block locator hashes for message "+ + "[count %d, max %d]", count, MaxBlockLocatorsPerMsg) + return messageError("MsgBlockLocator.BtcDecode", str) + } + + // Create a contiguous slice of hashes to deserialize into in order to + // reduce the number of allocations. + locatorHashes := make([]daghash.Hash, count) + msg.BlockLocatorHashes = make([]*daghash.Hash, 0, count) + for i := uint64(0); i < count; i++ { + hash := &locatorHashes[i] + err := ReadElement(r, hash) + if err != nil { + return err + } + err = msg.AddBlockLocatorHash(hash) + if err != nil { + return err + } + } + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgBlockLocator) BtcEncode(w io.Writer, pver uint32) error { + // Limit to max block locator hashes per message. + count := len(msg.BlockLocatorHashes) + if count > MaxBlockLocatorsPerMsg { + str := fmt.Sprintf("too many block locator hashes for message "+ + "[count %d, max %d]", count, MaxBlockLocatorsPerMsg) + return messageError("MsgBlockLocator.BtcEncode", str) + } + + err := WriteVarInt(w, uint64(count)) + if err != nil { + return err + } + + for _, hash := range msg.BlockLocatorHashes { + err := WriteElement(w, hash) + if err != nil { + return err + } + } + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgBlockLocator) Command() string { + return CmdBlockLocator +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgBlockLocator) MaxPayloadLength(pver uint32) uint32 { + // Num block locator hashes (varInt) + max allowed block + // locators. + return MaxVarIntPayload + (MaxBlockLocatorsPerMsg * + daghash.HashSize) +} + +// NewMsgBlockLocator returns a new bitcoin locator message that conforms to +// the Message interface. See MsgBlockLocator for details. +func NewMsgBlockLocator() *MsgBlockLocator { + return &MsgBlockLocator{ + BlockLocatorHashes: make([]*daghash.Hash, 0, + MaxBlockLocatorsPerMsg), + } +} diff --git a/wire/msggetblocks_test.go b/wire/msgblocklocator_test.go similarity index 60% rename from wire/msggetblocks_test.go rename to wire/msgblocklocator_test.go index 8748a775d..0a7f1c905 100644 --- a/wire/msggetblocks_test.go +++ b/wire/msgblocklocator_test.go @@ -1,7 +1,3 @@ -// Copyright (c) 2013-2016 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - package wire import ( @@ -14,42 +10,29 @@ import ( "github.com/davecgh/go-spew/spew" ) -// TestGetBlockInvs tests the MsgGetBlockInvs API. -func TestGetBlockInvs(t *testing.T) { +// TestBlockLocator tests the MsgBlockLocator API. +func TestBlockLocator(t *testing.T) { pver := ProtocolVersion - // Block 99500 hash. hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" locatorHash, err := daghash.NewHashFromStr(hashStr) if err != nil { t.Errorf("NewHashFromStr: %v", err) } - // Block 100000 hash. - hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" - stopHash, err := daghash.NewHashFromStr(hashStr) - if err != nil { - t.Errorf("NewHashFromStr: %v", err) - } - - // Ensure we get the same data back out. - msg := NewMsgGetBlockInvs(stopHash) - if !msg.StopHash.IsEqual(stopHash) { - t.Errorf("NewMsgGetBlockInvs: wrong stop hash - got %v, want %v", - msg.StopHash, stopHash) - } + msg := NewMsgBlockLocator() // Ensure the command is expected value. - wantCmd := "getblockinvs" + wantCmd := "locator" if cmd := msg.Command(); cmd != wantCmd { - t.Errorf("NewMsgGetBlockInvs: wrong command - got %v want %v", + t.Errorf("NewMsgBlockLocator: wrong command - got %v want %v", cmd, wantCmd) } // Ensure max payload is expected value for latest protocol version. - // Protocol version 4 bytes + num hashes (varInt) + max block locator - // hashes + hash stop. - wantPayload := uint32(16045) + // Num hashes (varInt) + max block locator + // hashes. + wantPayload := uint32(16009) maxPayload := msg.MaxPayloadLength(pver) if maxPayload != wantPayload { t.Errorf("MaxPayloadLength: wrong max payload length for "+ @@ -80,70 +63,46 @@ func TestGetBlockInvs(t *testing.T) { } } -// TestGetBlockInvsWire tests the MsgGetBlockInvs wire encode and decode for various -// numbers of block locator hashes and protocol versions. -func TestGetBlockInvsWire(t *testing.T) { - // Set protocol inside getblockinvs message. - pver := uint32(1) - - // Block 99499 hash. +// TestBlockLocatorWire tests the MsgBlockLocator wire encode and decode for various +// numbers of block locator hashes. +func TestBlockLocatorWire(t *testing.T) { hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" hashLocator, err := daghash.NewHashFromStr(hashStr) if err != nil { t.Errorf("NewHashFromStr: %v", err) } - // Block 99500 hash. hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" hashLocator2, err := daghash.NewHashFromStr(hashStr) if err != nil { t.Errorf("NewHashFromStr: %v", err) } - // Block 100000 hash. - hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" - stopHash, err := daghash.NewHashFromStr(hashStr) - if err != nil { - t.Errorf("NewHashFromStr: %v", err) - } - - // MsgGetBlockInvs message with no block locators or stop hash. - noLocators := NewMsgGetBlockInvs(&daghash.Hash{}) - noLocators.ProtocolVersion = pver + // MsgBlockLocator message with no block locators. + noLocators := NewMsgBlockLocator() noLocatorsEncoded := []byte{ - 0x01, 0x00, 0x00, 0x00, // Protocol version 1 0x00, // Varint for number of block locator hashes - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop } - // MsgGetBlockInvs message with multiple block locators and a stop hash. - multiLocators := NewMsgGetBlockInvs(stopHash) + // MsgBlockLocator message with multiple block locators. + multiLocators := NewMsgBlockLocator() multiLocators.AddBlockLocatorHash(hashLocator2) multiLocators.AddBlockLocatorHash(hashLocator) - multiLocators.ProtocolVersion = pver multiLocatorsEncoded := []byte{ - 0x01, 0x00, 0x00, 0x00, // Protocol version 1 0x02, // Varint for number of block locator hashes 0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63, 0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65, 0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b, - 0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash + 0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // first hash 0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60, 0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9, 0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40, - 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash - 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, - 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, - 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, - 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop + 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // second hash } tests := []struct { - in *MsgGetBlockInvs // Message to encode - out *MsgGetBlockInvs // Expected decoded message + in *MsgBlockLocator // Message to encode + out *MsgBlockLocator // Expected decoded message buf []byte // Wire encoding pver uint32 // Protocol version for wire encoding }{ @@ -180,7 +139,7 @@ func TestGetBlockInvsWire(t *testing.T) { } // Decode the message from wire format. - var msg MsgGetBlockInvs + var msg MsgBlockLocator rbuf := bytes.NewReader(test.buf) err = msg.BtcDecode(rbuf, test.pver) if err != nil { @@ -195,43 +154,32 @@ func TestGetBlockInvsWire(t *testing.T) { } } -// TestGetBlockInvsWireErrors performs negative tests against wire encode and -// decode of MsgGetBlockInvs to confirm error paths work correctly. -func TestGetBlockInvsWireErrors(t *testing.T) { - // Set protocol inside getheaders message. Use protocol version 1 +// TestBlockLocatorWireErrors performs negative tests against wire encode and +// decode of MsgBlockLocator to confirm error paths work correctly. +func TestBlockLocatorWireErrors(t *testing.T) { + // Set protocol inside locator message. Use protocol version 1 // specifically here instead of the latest because the test data is // using bytes encoded with that protocol version. pver := uint32(1) wireErr := &MessageError{} - // Block 99499 hash. hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" hashLocator, err := daghash.NewHashFromStr(hashStr) if err != nil { t.Errorf("NewHashFromStr: %v", err) } - // Block 99500 hash. hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" hashLocator2, err := daghash.NewHashFromStr(hashStr) if err != nil { t.Errorf("NewHashFromStr: %v", err) } - // Block 100000 hash. - hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" - stopHash, err := daghash.NewHashFromStr(hashStr) - if err != nil { - t.Errorf("NewHashFromStr: %v", err) - } - - // MsgGetBlockInvs message with multiple block locators and a stop hash. - baseGetBlockInvs := NewMsgGetBlockInvs(stopHash) - baseGetBlockInvs.ProtocolVersion = pver - baseGetBlockInvs.AddBlockLocatorHash(hashLocator2) - baseGetBlockInvs.AddBlockLocatorHash(hashLocator) - baseGetBlockInvsEncoded := []byte{ - 0x01, 0x00, 0x00, 0x00, // Protocol version 1 + // MsgBlockLocator message with multiple block locators and a stop hash. + baseGetBlocks := NewMsgBlockLocator() + baseGetBlocks.AddBlockLocatorHash(hashLocator2) + baseGetBlocks.AddBlockLocatorHash(hashLocator) + baseGetBlocksEncoded := []byte{ 0x02, // Varint for number of block locator hashes 0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63, 0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65, @@ -241,43 +189,34 @@ func TestGetBlockInvsWireErrors(t *testing.T) { 0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9, 0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40, 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash - 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, - 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, - 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, - 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop } // Message that forces an error by having more than the max allowed // block locator hashes. - maxGetBlockInvs := NewMsgGetBlockInvs(stopHash) + maxGetBlocks := NewMsgBlockLocator() for i := 0; i < MaxBlockLocatorsPerMsg; i++ { - maxGetBlockInvs.AddBlockLocatorHash(mainNetGenesisHash) + maxGetBlocks.AddBlockLocatorHash(mainNetGenesisHash) } - maxGetBlockInvs.BlockLocatorHashes = append(maxGetBlockInvs.BlockLocatorHashes, + maxGetBlocks.BlockLocatorHashes = append(maxGetBlocks.BlockLocatorHashes, mainNetGenesisHash) - maxGetBlockInvsEncoded := []byte{ - 0x01, 0x00, 0x00, 0x00, // Protocol version 1 + maxGetBlocksEncoded := []byte{ 0xfd, 0xf5, 0x01, // Varint for number of block loc hashes (501) } tests := []struct { - in *MsgGetBlockInvs // Value to encode + in *MsgBlockLocator // Value to encode buf []byte // Wire encoding pver uint32 // Protocol version for wire encoding max int // Max size of fixed buffer to induce errors writeErr error // Expected write error readErr error // Expected read error }{ - // Force error in protocol version. - {baseGetBlockInvs, baseGetBlockInvsEncoded, pver, 0, io.ErrShortWrite, io.EOF}, // Force error in block locator hash count. - {baseGetBlockInvs, baseGetBlockInvsEncoded, pver, 4, io.ErrShortWrite, io.EOF}, + {baseGetBlocks, baseGetBlocksEncoded, pver, 0, io.ErrShortWrite, io.EOF}, // Force error in block locator hashes. - {baseGetBlockInvs, baseGetBlockInvsEncoded, pver, 5, io.ErrShortWrite, io.EOF}, - // Force error in stop hash. - {baseGetBlockInvs, baseGetBlockInvsEncoded, pver, 69, io.ErrShortWrite, io.EOF}, + {baseGetBlocks, baseGetBlocksEncoded, pver, 1, io.ErrShortWrite, io.EOF}, // Force error with greater than max block locator hashes. - {maxGetBlockInvs, maxGetBlockInvsEncoded, pver, 7, wireErr, wireErr}, + {maxGetBlocks, maxGetBlocksEncoded, pver, 3, wireErr, wireErr}, } t.Logf("Running %d tests", len(tests)) @@ -302,7 +241,7 @@ func TestGetBlockInvsWireErrors(t *testing.T) { } // Decode from wire format. - var msg MsgGetBlockInvs + var msg MsgBlockLocator r := newFixedReader(test.max, test.buf) err = msg.BtcDecode(r, test.pver) if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { diff --git a/wire/msggetblockinvs.go b/wire/msggetblockinvs.go new file mode 100644 index 000000000..b87e551a0 --- /dev/null +++ b/wire/msggetblockinvs.go @@ -0,0 +1,66 @@ +// Copyright (c) 2013-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "io" + + "github.com/daglabs/btcd/util/daghash" +) + +// MsgGetBlockInvs implements the Message interface and represents a bitcoin +// getblockinvs message. It is used to request a list of blocks starting after the +// start hash and until the stop hash. +type MsgGetBlockInvs struct { + StartHash *daghash.Hash + StopHash *daghash.Hash +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgGetBlockInvs) BtcDecode(r io.Reader, pver uint32) error { + msg.StartHash = &daghash.Hash{} + err := ReadElement(r, msg.StartHash) + if err != nil { + return err + } + + msg.StopHash = &daghash.Hash{} + return ReadElement(r, msg.StopHash) +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgGetBlockInvs) BtcEncode(w io.Writer, pver uint32) error { + err := WriteElement(w, msg.StartHash) + if err != nil { + return err + } + + return WriteElement(w, msg.StopHash) +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgGetBlockInvs) Command() string { + return CmdGetBlockInvs +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgGetBlockInvs) MaxPayloadLength(pver uint32) uint32 { + // start hash + stop hash. + return 2 * daghash.HashSize +} + +// NewMsgGetBlockInvs returns a new bitcoin getblockinvs message that conforms to the +// Message interface using the passed parameters and defaults for the remaining +// fields. +func NewMsgGetBlockInvs(startHash, stopHash *daghash.Hash) *MsgGetBlockInvs { + return &MsgGetBlockInvs{ + StartHash: startHash, + StopHash: stopHash, + } +} diff --git a/wire/msggetblockinvs_test.go b/wire/msggetblockinvs_test.go new file mode 100644 index 000000000..ace0ffbe4 --- /dev/null +++ b/wire/msggetblockinvs_test.go @@ -0,0 +1,238 @@ +// Copyright (c) 2013-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/daglabs/btcd/util/daghash" + "github.com/davecgh/go-spew/spew" +) + +// TestGetBlockInvs tests the MsgGetBlockInvs API. +func TestGetBlockInvs(t *testing.T) { + pver := ProtocolVersion + + hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" + startHash, err := daghash.NewHashFromStr(hashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + stopHash, err := daghash.NewHashFromStr(hashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + // Ensure we get the same data back out. + msg := NewMsgGetBlockInvs(startHash, stopHash) + if !msg.StopHash.IsEqual(stopHash) { + t.Errorf("NewMsgGetBlockInvs: wrong stop hash - got %v, want %v", + msg.StopHash, stopHash) + } + + // Ensure the command is expected value. + wantCmd := "getblockinvs" + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgGetBlockInvs: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is start hash (32 bytes) + stop hash (32 bytes). + wantPayload := uint32(64) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } +} + +// TestGetBlockInvsWire tests the MsgGetBlockInvs wire encode and decode for various +// numbers of block locator hashes and protocol versions. +func TestGetBlockInvsWire(t *testing.T) { + hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" + startHash, err := daghash.NewHashFromStr(hashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + stopHash, err := daghash.NewHashFromStr(hashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + // MsgGetBlocks message with no start or stop hash. + noStartOrStop := NewMsgGetBlockInvs(&daghash.Hash{}, &daghash.Hash{}) + noStartOrStopEncoded := []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Start hash + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Stop hash + } + + // MsgGetBlockInvs message with a start hash and a stop hash. + withStartAndStopHash := NewMsgGetBlockInvs(startHash, stopHash) + withStartAndStopHashEncoded := []byte{ + 0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60, + 0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9, + 0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40, + 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Start hash + 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, + 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, + 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, + 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Stop hash + } + + tests := []struct { + in *MsgGetBlockInvs // Message to encode + out *MsgGetBlockInvs // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version with no block locators. + { + noStartOrStop, + noStartOrStop, + noStartOrStopEncoded, + ProtocolVersion, + }, + + // Latest protocol version with multiple block locators. + { + withStartAndStopHash, + withStartAndStopHash, + withStartAndStopHashEncoded, + ProtocolVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg MsgGetBlockInvs + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(&msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestGetBlockInvsWireErrors performs negative tests against wire encode and +// decode of MsgGetBlockInvs to confirm error paths work correctly. +func TestGetBlockInvsWireErrors(t *testing.T) { + // Set protocol inside getheaders message. + pver := ProtocolVersion + + hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" + startHash, err := daghash.NewHashFromStr(hashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + stopHash, err := daghash.NewHashFromStr(hashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + // MsgGetBlockInvs message with multiple block locators and a stop hash. + baseGetBlocks := NewMsgGetBlockInvs(startHash, stopHash) + baseGetBlocksEncoded := []byte{ + 0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60, + 0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9, + 0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40, + 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Start hash + 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, + 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, + 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, + 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Stop hash + } + + tests := []struct { + in *MsgGetBlockInvs // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in start hash. + {baseGetBlocks, baseGetBlocksEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in stop hash. + {baseGetBlocks, baseGetBlocksEncoded, pver, 32, io.ErrShortWrite, io.EOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type MessageError, check them for + // equality. + if _, ok := err.(*MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg MsgGetBlockInvs + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type MessageError, check them for + // equality. + if _, ok := err.(*MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + } +} diff --git a/wire/msggetblocklocator.go b/wire/msggetblocklocator.go new file mode 100644 index 000000000..f4b3fb8b5 --- /dev/null +++ b/wire/msggetblocklocator.go @@ -0,0 +1,65 @@ +package wire + +import ( + "github.com/daglabs/btcd/util/daghash" + "io" +) + +type MsgGetBlockLocator struct { + StartHash *daghash.Hash + StopHash *daghash.Hash +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgGetBlockLocator) BtcDecode(r io.Reader, pver uint32) error { + msg.StartHash = &daghash.Hash{} + err := ReadElement(r, msg.StartHash) + if err != nil { + return err + } + + msg.StopHash = &daghash.Hash{} + err = ReadElement(r, msg.StopHash) + if err != nil { + return err + } + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgGetBlockLocator) BtcEncode(w io.Writer, pver uint32) error { + err := WriteElement(w, msg.StartHash) + if err != nil { + return err + } + + err = WriteElement(w, msg.StopHash) + if err != nil { + return err + } + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgGetBlockLocator) Command() string { + return CmdGetBlockLocator +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgGetBlockLocator) MaxPayloadLength(pver uint32) uint32 { + return daghash.HashSize * 2 +} + +// NewMsgGetBlockLocator returns a new getlocator message that conforms to the +// Message interface using the passed parameters and defaults for the remaining +// fields. +func NewMsgGetBlockLocator(startHash, stopHash *daghash.Hash) *MsgGetBlockLocator { + return &MsgGetBlockLocator{ + StartHash: startHash, + StopHash: stopHash, + } +} diff --git a/wire/msggetblocklocator_test.go b/wire/msggetblocklocator_test.go new file mode 100644 index 000000000..158c645a1 --- /dev/null +++ b/wire/msggetblocklocator_test.go @@ -0,0 +1,221 @@ +package wire + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/daglabs/btcd/util/daghash" + "github.com/davecgh/go-spew/spew" +) + +// TestGetBlockLocator tests the MsgGetBlockLocator API. +func TestGetBlockLocator(t *testing.T) { + pver := ProtocolVersion + + hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" + startHash, err := daghash.NewHashFromStr(hashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + // Ensure the command is expected value. + wantCmd := "getlocator" + msg := NewMsgGetBlockLocator(startHash, &daghash.ZeroHash) + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgGetBlockLocator: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is start hash (32 bytes) + stop hash (32 bytes).. + wantPayload := uint32(64) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } +} + +// TestGetBlockLocatorWire tests the MsgGetBlockLocator wire encode and decode. +func TestGetBlockLocatorWire(t *testing.T) { + hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" + startHash, err := daghash.NewHashFromStr(hashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + stopHash, err := daghash.NewHashFromStr(hashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + // MsgGetBlockLocator message with no block locators or stop hash. + noStartAndStopHash := NewMsgGetBlockLocator(&daghash.ZeroHash, &daghash.ZeroHash) + noStartAndStopHashEncoded := []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Start hash + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Stop hash + } + + // MsgGetBlockLocator message with multiple block locators and a stop hash. + withStartAndStopHash := NewMsgGetBlockLocator(startHash, stopHash) + withStartAndStopHashEncoded := []byte{ + 0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60, + 0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9, + 0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40, + 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Start hash + 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, + 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, + 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, + 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Stop hash + } + + tests := []struct { + in *MsgGetBlockLocator // Message to encode + out *MsgGetBlockLocator // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Message with no start hash and stop hash. + { + noStartAndStopHash, + noStartAndStopHash, + noStartAndStopHashEncoded, + ProtocolVersion, + }, + + // Message with start hash and stop hash. + { + withStartAndStopHash, + withStartAndStopHash, + withStartAndStopHashEncoded, + ProtocolVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg MsgGetBlockLocator + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(&msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestGetBlockLocatorWireErrors performs negative tests against wire encode and +// decode of MsgGetBlockLocator to confirm error paths work correctly. +func TestGetBlockLocatorWireErrors(t *testing.T) { + // Set protocol inside getlocator message. + pver := ProtocolVersion + + hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" + startHash, err := daghash.NewHashFromStr(hashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + stopHash, err := daghash.NewHashFromStr(hashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + // MsgGetBlockLocator message with multiple block locators and a stop hash. + baseGetBlockLocator := NewMsgGetBlockLocator(startHash, stopHash) + baseGetBlockLocatorEncoded := []byte{ + 0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60, + 0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9, + 0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40, + 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Start hash + 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, + 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, + 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, + 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Stop hash + } + + tests := []struct { + in *MsgGetBlockLocator // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in start hash. + {baseGetBlockLocator, baseGetBlockLocatorEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in stop hash. + {baseGetBlockLocator, baseGetBlockLocatorEncoded, pver, 32, io.ErrShortWrite, io.EOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type MessageError, check them for + // equality. + if _, ok := err.(*MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg MsgGetBlockLocator + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type MessageError, check them for + // equality. + if _, ok := err.(*MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + } +} diff --git a/wire/msggetblocks.go b/wire/msggetblocks.go deleted file mode 100644 index 65115f9d1..000000000 --- a/wire/msggetblocks.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) 2013-2016 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package wire - -import ( - "fmt" - "io" - - "github.com/daglabs/btcd/util/daghash" -) - -// MaxBlockLocatorsPerMsg is the maximum number of block locator hashes allowed -// per message. -const MaxBlockLocatorsPerMsg = 500 - -// MsgGetBlockInvs implements the Message interface and represents a bitcoin -// getblockinvss message. It is used to request a list of blocks starting after -// the last known hash in the slice of block locator hashes. The list is -// returned via an inv message (MsgInv) and is limited by a specific hash to -// stop at or the maximum number of blocks per message, which is currently 500. -// -// Set the StopHash field to the hash at which to stop and use -// AddBlockLocatorHash to build up the list of block locator hashes. -// -// The algorithm for building the block locator hashes should be to add the -// hashes in reverse order until you reach the genesis block. In order to keep -// the list of locator hashes to a reasonable number of entries, first add the -// most recent 10 block hashes, then double the step each loop iteration to -// exponentially decrease the number of hashes the further away from head and -// closer to the genesis block you get. -type MsgGetBlockInvs struct { - ProtocolVersion uint32 - BlockLocatorHashes []*daghash.Hash - StopHash *daghash.Hash -} - -// AddBlockLocatorHash adds a new block locator hash to the message. -func (msg *MsgGetBlockInvs) AddBlockLocatorHash(hash *daghash.Hash) error { - if len(msg.BlockLocatorHashes)+1 > MaxBlockLocatorsPerMsg { - str := fmt.Sprintf("too many block locator hashes for message [max %d]", - MaxBlockLocatorsPerMsg) - return messageError("MsgGetBlockInvs.AddBlockLocatorHash", str) - } - - msg.BlockLocatorHashes = append(msg.BlockLocatorHashes, hash) - return nil -} - -// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. -// This is part of the Message interface implementation. -func (msg *MsgGetBlockInvs) BtcDecode(r io.Reader, pver uint32) error { - err := ReadElement(r, &msg.ProtocolVersion) - if err != nil { - return err - } - - // Read num block locator hashes and limit to max. - count, err := ReadVarInt(r) - if err != nil { - return err - } - if count > MaxBlockLocatorsPerMsg { - str := fmt.Sprintf("too many block locator hashes for message "+ - "[count %d, max %d]", count, MaxBlockLocatorsPerMsg) - return messageError("MsgGetBlockInvs.BtcDecode", str) - } - - // Create a contiguous slice of hashes to deserialize into in order to - // reduce the number of allocations. - locatorHashes := make([]daghash.Hash, count) - msg.BlockLocatorHashes = make([]*daghash.Hash, 0, count) - for i := uint64(0); i < count; i++ { - hash := &locatorHashes[i] - err := ReadElement(r, hash) - if err != nil { - return err - } - msg.AddBlockLocatorHash(hash) - } - - msg.StopHash = &daghash.Hash{} - return ReadElement(r, msg.StopHash) -} - -// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. -// This is part of the Message interface implementation. -func (msg *MsgGetBlockInvs) BtcEncode(w io.Writer, pver uint32) error { - count := len(msg.BlockLocatorHashes) - if count > MaxBlockLocatorsPerMsg { - str := fmt.Sprintf("too many block locator hashes for message "+ - "[count %d, max %d]", count, MaxBlockLocatorsPerMsg) - return messageError("MsgGetBlockInvs.BtcEncode", str) - } - - err := WriteElement(w, msg.ProtocolVersion) - if err != nil { - return err - } - - err = WriteVarInt(w, uint64(count)) - if err != nil { - return err - } - - for _, hash := range msg.BlockLocatorHashes { - err = WriteElement(w, hash) - if err != nil { - return err - } - } - - return WriteElement(w, msg.StopHash) -} - -// Command returns the protocol command string for the message. This is part -// of the Message interface implementation. -func (msg *MsgGetBlockInvs) Command() string { - return CmdGetBlockInvs -} - -// MaxPayloadLength returns the maximum length the payload can be for the -// receiver. This is part of the Message interface implementation. -func (msg *MsgGetBlockInvs) MaxPayloadLength(pver uint32) uint32 { - // Protocol version 4 bytes + num hashes (varInt) + max block locator - // hashes + hash stop. - return 4 + MaxVarIntPayload + (MaxBlockLocatorsPerMsg * daghash.HashSize) + daghash.HashSize -} - -// NewMsgGetBlockInvs returns a new bitcoin getblockinvs message that conforms -// to the Message interface using the passed parameters and defaults for the -// remaining fields. -func NewMsgGetBlockInvs(stopHash *daghash.Hash) *MsgGetBlockInvs { - return &MsgGetBlockInvs{ - ProtocolVersion: ProtocolVersion, - BlockLocatorHashes: make([]*daghash.Hash, 0, MaxBlockLocatorsPerMsg), - StopHash: stopHash, - } -} diff --git a/wire/msggetheaders.go b/wire/msggetheaders.go index b701e2fed..9a5fd6790 100644 --- a/wire/msggetheaders.go +++ b/wire/msggetheaders.go @@ -5,7 +5,6 @@ package wire import ( - "fmt" "io" "github.com/daglabs/btcd/util/daghash" @@ -28,55 +27,19 @@ import ( // exponentially decrease the number of hashes the further away from head and // closer to the genesis block you get. type MsgGetHeaders struct { - ProtocolVersion uint32 - BlockLocatorHashes []*daghash.Hash - StopHash *daghash.Hash -} - -// AddBlockLocatorHash adds a new block locator hash to the message. -func (msg *MsgGetHeaders) AddBlockLocatorHash(hash *daghash.Hash) error { - if len(msg.BlockLocatorHashes)+1 > MaxBlockLocatorsPerMsg { - str := fmt.Sprintf("too many block locator hashes for message [max %d]", - MaxBlockLocatorsPerMsg) - return messageError("MsgGetHeaders.AddBlockLocatorHash", str) - } - - msg.BlockLocatorHashes = append(msg.BlockLocatorHashes, hash) - return nil + StartHash *daghash.Hash + StopHash *daghash.Hash } // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgGetHeaders) BtcDecode(r io.Reader, pver uint32) error { - err := ReadElement(r, &msg.ProtocolVersion) + msg.StartHash = &daghash.Hash{} + err := ReadElement(r, msg.StartHash) if err != nil { return err } - // Read num block locator hashes and limit to max. - count, err := ReadVarInt(r) - if err != nil { - return err - } - if count > MaxBlockLocatorsPerMsg { - str := fmt.Sprintf("too many block locator hashes for message "+ - "[count %d, max %d]", count, MaxBlockLocatorsPerMsg) - return messageError("MsgGetHeaders.BtcDecode", str) - } - - // Create a contiguous slice of hashes to deserialize into in order to - // reduce the number of allocations. - locatorHashes := make([]daghash.Hash, count) - msg.BlockLocatorHashes = make([]*daghash.Hash, 0, count) - for i := uint64(0); i < count; i++ { - hash := &locatorHashes[i] - err := ReadElement(r, hash) - if err != nil { - return err - } - msg.AddBlockLocatorHash(hash) - } - msg.StopHash = &daghash.Hash{} return ReadElement(r, msg.StopHash) } @@ -84,31 +47,11 @@ func (msg *MsgGetHeaders) BtcDecode(r io.Reader, pver uint32) error { // BtcEncode encodes the receiver to w using the bitcoin protocol encoding. // This is part of the Message interface implementation. func (msg *MsgGetHeaders) BtcEncode(w io.Writer, pver uint32) error { - // Limit to max block locator hashes per message. - count := len(msg.BlockLocatorHashes) - if count > MaxBlockLocatorsPerMsg { - str := fmt.Sprintf("too many block locator hashes for message "+ - "[count %d, max %d]", count, MaxBlockLocatorsPerMsg) - return messageError("MsgGetHeaders.BtcEncode", str) - } - - err := WriteElement(w, msg.ProtocolVersion) + err := WriteElement(w, msg.StartHash) if err != nil { return err } - err = WriteVarInt(w, uint64(count)) - if err != nil { - return err - } - - for _, hash := range msg.BlockLocatorHashes { - err := WriteElement(w, hash) - if err != nil { - return err - } - } - return WriteElement(w, msg.StopHash) } @@ -121,18 +64,15 @@ func (msg *MsgGetHeaders) Command() string { // MaxPayloadLength returns the maximum length the payload can be for the // receiver. This is part of the Message interface implementation. func (msg *MsgGetHeaders) MaxPayloadLength(pver uint32) uint32 { - // Version 4 bytes + num block locator hashes (varInt) + max allowed block - // locators + hash stop. - return 4 + MaxVarIntPayload + (MaxBlockLocatorsPerMsg * - daghash.HashSize) + daghash.HashSize + // start hash + stop hash. + return 2 * daghash.HashSize } // NewMsgGetHeaders returns a new bitcoin getheaders message that conforms to // the Message interface. See MsgGetHeaders for details. -func NewMsgGetHeaders() *MsgGetHeaders { +func NewMsgGetHeaders(startHash, stopHash *daghash.Hash) *MsgGetHeaders { return &MsgGetHeaders{ - BlockLocatorHashes: make([]*daghash.Hash, 0, - MaxBlockLocatorsPerMsg), - StopHash: &daghash.ZeroHash, + StartHash: startHash, + StopHash: stopHash, } } diff --git a/wire/msggetheaders_test.go b/wire/msggetheaders_test.go index b38cd004d..20451c395 100644 --- a/wire/msggetheaders_test.go +++ b/wire/msggetheaders_test.go @@ -20,76 +20,37 @@ func TestGetHeaders(t *testing.T) { // Block 99500 hash. hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" - locatorHash, err := daghash.NewHashFromStr(hashStr) + startHash, err := daghash.NewHashFromStr(hashStr) if err != nil { t.Errorf("NewHashFromStr: %v", err) } // Ensure the command is expected value. wantCmd := "getheaders" - msg := NewMsgGetHeaders() + msg := NewMsgGetHeaders(startHash, &daghash.ZeroHash) if cmd := msg.Command(); cmd != wantCmd { t.Errorf("NewMsgGetHeaders: wrong command - got %v want %v", cmd, wantCmd) } - // Ensure max payload is expected value for latest protocol version. - // Protocol version 4 bytes + num hashes (varInt) + max block locator - // hashes + hash stop. - wantPayload := uint32(16045) + // Ensure max payload is start hash (32 bytes) + stop hash (32 bytes).. + wantPayload := uint32(64) maxPayload := msg.MaxPayloadLength(pver) if maxPayload != wantPayload { t.Errorf("MaxPayloadLength: wrong max payload length for "+ "protocol version %d - got %v, want %v", pver, maxPayload, wantPayload) } - - // Ensure block locator hashes are added properly. - err = msg.AddBlockLocatorHash(locatorHash) - if err != nil { - t.Errorf("AddBlockLocatorHash: %v", err) - } - if msg.BlockLocatorHashes[0] != locatorHash { - t.Errorf("AddBlockLocatorHash: wrong block locator added - "+ - "got %v, want %v", - spew.Sprint(msg.BlockLocatorHashes[0]), - spew.Sprint(locatorHash)) - } - - // Ensure adding more than the max allowed block locator hashes per - // message returns an error. - for i := 0; i < MaxBlockLocatorsPerMsg; i++ { - err = msg.AddBlockLocatorHash(locatorHash) - } - if err == nil { - t.Errorf("AddBlockLocatorHash: expected error on too many " + - "block locator hashes not received") - } } -// TestGetHeadersWire tests the MsgGetHeaders wire encode and decode for various -// numbers of block locator hashes and protocol versions. +// TestGetHeadersWire tests the MsgGetHeaders wire encode and decode. func TestGetHeadersWire(t *testing.T) { - // Set protocol inside getheaders message. Use protocol version 1 - // specifically here instead of the latest because the test data is - // using bytes encoded with that protocol version. - pver := uint32(1) - - // Block 99499 hash. hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" - hashLocator, err := daghash.NewHashFromStr(hashStr) + startHash, err := daghash.NewHashFromStr(hashStr) if err != nil { t.Errorf("NewHashFromStr: %v", err) } - // Block 99500 hash. - hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" - hashLocator2, err := daghash.NewHashFromStr(hashStr) - if err != nil { - t.Errorf("NewHashFromStr: %v", err) - } - - // Block 100000 hash. hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" stopHash, err := daghash.NewHashFromStr(hashStr) if err != nil { @@ -97,38 +58,29 @@ func TestGetHeadersWire(t *testing.T) { } // MsgGetHeaders message with no block locators or stop hash. - noLocators := NewMsgGetHeaders() - noLocators.ProtocolVersion = pver - noLocatorsEncoded := []byte{ - 0x01, 0x00, 0x00, 0x00, // Protocol version 1 - 0x00, // Varint for number of block locator hashes + noStartAndStopHash := NewMsgGetHeaders(&daghash.ZeroHash, &daghash.ZeroHash) + noStartAndStopHashEncoded := []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Start hash + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Stop hash } // MsgGetHeaders message with multiple block locators and a stop hash. - multiLocators := NewMsgGetHeaders() - multiLocators.ProtocolVersion = pver - multiLocators.StopHash = stopHash - multiLocators.AddBlockLocatorHash(hashLocator2) - multiLocators.AddBlockLocatorHash(hashLocator) - multiLocatorsEncoded := []byte{ - 0x01, 0x00, 0x00, 0x00, // Protocol version 1 - 0x02, // Varint for number of block locator hashes - 0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63, - 0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65, - 0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b, - 0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash + withStartAndStopHash := NewMsgGetHeaders(startHash, stopHash) + withStartAndStopHashEncoded := []byte{ 0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60, 0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9, 0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40, - 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash + 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Start hash 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, - 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop + 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Stop hash } tests := []struct { @@ -137,19 +89,19 @@ func TestGetHeadersWire(t *testing.T) { buf []byte // Wire encoding pver uint32 // Protocol version for wire encoding }{ - // Latest protocol version with no block locators. + // Message with no start hash and stop hash. { - noLocators, - noLocators, - noLocatorsEncoded, + noStartAndStopHash, + noStartAndStopHash, + noStartAndStopHashEncoded, ProtocolVersion, }, - // Latest protocol version with multiple block locators. + // Message with start hash and stop hash. { - multiLocators, - multiLocators, - multiLocatorsEncoded, + withStartAndStopHash, + withStartAndStopHash, + withStartAndStopHashEncoded, ProtocolVersion, }, } @@ -188,27 +140,15 @@ func TestGetHeadersWire(t *testing.T) { // TestGetHeadersWireErrors performs negative tests against wire encode and // decode of MsgGetHeaders to confirm error paths work correctly. func TestGetHeadersWireErrors(t *testing.T) { - // Set protocol inside getheaders message. Use protocol version 1 - // specifically here instead of the latest because the test data is - // using bytes encoded with that protocol version. + // Set protocol inside getheaders message. pver := ProtocolVersion - wireErr := &MessageError{} - // Block 99499 hash. hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" - hashLocator, err := daghash.NewHashFromStr(hashStr) + startHash, err := daghash.NewHashFromStr(hashStr) if err != nil { t.Errorf("NewHashFromStr: %v", err) } - // Block 99500 hash. - hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" - hashLocator2, err := daghash.NewHashFromStr(hashStr) - if err != nil { - t.Errorf("NewHashFromStr: %v", err) - } - - // Block 100000 hash. hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" stopHash, err := daghash.NewHashFromStr(hashStr) if err != nil { @@ -216,39 +156,16 @@ func TestGetHeadersWireErrors(t *testing.T) { } // MsgGetHeaders message with multiple block locators and a stop hash. - baseGetHeaders := NewMsgGetHeaders() - baseGetHeaders.ProtocolVersion = pver - baseGetHeaders.StopHash = stopHash - baseGetHeaders.AddBlockLocatorHash(hashLocator2) - baseGetHeaders.AddBlockLocatorHash(hashLocator) + baseGetHeaders := NewMsgGetHeaders(startHash, stopHash) baseGetHeadersEncoded := []byte{ - 0x01, 0x00, 0x00, 0x00, // Protocol version 1 - 0x02, // Varint for number of block locator hashes - 0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63, - 0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65, - 0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b, - 0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash 0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60, 0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9, 0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40, - 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash + 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Start hash 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, - 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop - } - - // Message that forces an error by having more than the max allowed - // block locator hashes. - maxGetHeaders := NewMsgGetHeaders() - for i := 0; i < MaxBlockLocatorsPerMsg; i++ { - maxGetHeaders.AddBlockLocatorHash(mainNetGenesisHash) - } - maxGetHeaders.BlockLocatorHashes = append(maxGetHeaders.BlockLocatorHashes, - mainNetGenesisHash) - maxGetHeadersEncoded := []byte{ - 0x01, 0x00, 0x00, 0x00, // Protocol version 1 - 0xfd, 0xf5, 0x01, // Varint for number of block loc hashes (501) + 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Stop hash } tests := []struct { @@ -259,16 +176,10 @@ func TestGetHeadersWireErrors(t *testing.T) { writeErr error // Expected write error readErr error // Expected read error }{ - // Force error in protocol version. + // Force error in start hash. {baseGetHeaders, baseGetHeadersEncoded, pver, 0, io.ErrShortWrite, io.EOF}, - // Force error in block locator hash count. - {baseGetHeaders, baseGetHeadersEncoded, pver, 4, io.ErrShortWrite, io.EOF}, - // Force error in block locator hashes. - {baseGetHeaders, baseGetHeadersEncoded, pver, 5, io.ErrShortWrite, io.EOF}, // Force error in stop hash. - {baseGetHeaders, baseGetHeadersEncoded, pver, 69, io.ErrShortWrite, io.EOF}, - // Force error with greater than max block locator hashes. - {maxGetHeaders, maxGetHeadersEncoded, pver, 7, wireErr, wireErr}, + {baseGetHeaders, baseGetHeadersEncoded, pver, 32, io.ErrShortWrite, io.EOF}, } t.Logf("Running %d tests", len(tests)) diff --git a/wire/msgsendheaders.go b/wire/msgsendheaders.go index 3f64791cb..e2a611267 100644 --- a/wire/msgsendheaders.go +++ b/wire/msgsendheaders.go @@ -12,8 +12,7 @@ import ( // sendheaders message. It is used to request the peer send block headers // rather than inventory vectors. // -// This message has no payload and was not added until protocol versions -// starting with SendHeadersVersion. +// This message has no payload. type MsgSendHeaders struct{} // BtcDecode decodes r using the bitcoin protocol encoding into the receiver.