diff --git a/blockchain/chain.go b/blockchain/chain.go index b5fb6f89..d1ee628b 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -1290,6 +1290,159 @@ func (b *BlockChain) HeightRange(startHeight, endHeight int32) ([]chainhash.Hash 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. +// +// This function MUST be called with the chain state lock held (for reads). +func (b *BlockChain) locateInventory(locator BlockLocator, hashStop *chainhash.Hash, maxEntries uint32) (*blockNode, uint32) { + // There are no block locators so a specific block is being requested + // as identified by the stop hash. + stopNode := b.index.LookupNode(hashStop) + 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 main chain. In the + // case none of the hashes in the locator are in the main chain, fall + // back to the genesis block. + startNode := b.bestChain.Genesis() + for _, hash := range locator { + node := b.index.LookupNode(hash) + if node != nil && b.bestChain.Contains(node) { + startNode = node + break + } + } + + // Start at the block after the most recently known block. When there + // is no next block it means the most recently known block is the tip of + // the best chain, so there is nothing more to do. + startNode = b.bestChain.Next(startNode) + if startNode == nil { + return nil, 0 + } + + // Calculate how many entries are needed. + total := uint32((b.bestChain.Tip().height - startNode.height) + 1) + if stopNode != nil && b.bestChain.Contains(stopNode) && + stopNode.height >= startNode.height { + + total = uint32((stopNode.height - startNode.height) + 1) + } + if total > maxEntries { + total = maxEntries + } + + return startNode, total +} + +// 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 chain state lock held (for reads). +func (b *BlockChain) locateBlocks(locator BlockLocator, hashStop *chainhash.Hash, maxHashes uint32) []chainhash.Hash { + // Find the node after the first known block in the locator and the + // total number of nodes after it needed while respecting the stop hash + // and max entries. + node, total := b.locateInventory(locator, hashStop, maxHashes) + if total == 0 { + return nil + } + + // Populate and return the found hashes. + hashes := make([]chainhash.Hash, 0, total) + for i := uint32(0); i < total; i++ { + hashes = append(hashes, node.hash) + node = b.bestChain.Next(node) + } + return hashes +} + +// 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 +// +// This function is safe for concurrent access. +func (b *BlockChain) LocateBlocks(locator BlockLocator, hashStop *chainhash.Hash, maxHashes uint32) []chainhash.Hash { + b.chainLock.RLock() + hashes := b.locateBlocks(locator, hashStop, maxHashes) + b.chainLock.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. +// +// This function MUST be called with the chain state lock held (for reads). +func (b *BlockChain) locateHeaders(locator BlockLocator, hashStop *chainhash.Hash, maxHeaders uint32) []wire.BlockHeader { + // Find the node after the first known block in the locator and the + // total number of nodes after it needed while respecting the stop hash + // and max entries. + node, total := b.locateInventory(locator, hashStop, maxHeaders) + if total == 0 { + return nil + } + + // Populate and return the found headers. + headers := make([]wire.BlockHeader, 0, total) + for i := uint32(0); i < total; i++ { + headers = append(headers, node.Header()) + node = b.bestChain.Next(node) + } + return headers +} + +// 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 +// +// This function is safe for concurrent access. +func (b *BlockChain) LocateHeaders(locator BlockLocator, hashStop *chainhash.Hash) []wire.BlockHeader { + b.chainLock.RLock() + headers := b.locateHeaders(locator, hashStop, wire.MaxBlockHeadersPerMsg) + b.chainLock.RUnlock() + return headers +} + // IndexManager provides a generic interface that the is called when blocks are // connected and disconnected to and from the tip of the main chain for the // purpose of supporting optional indexes. diff --git a/blockchain/chain_test.go b/blockchain/chain_test.go index 1d6920e4..afb07f9a 100644 --- a/blockchain/chain_test.go +++ b/blockchain/chain_test.go @@ -5,6 +5,7 @@ package blockchain import ( + "reflect" "testing" "time" @@ -437,3 +438,365 @@ func TestCalcSequenceLock(t *testing.T) { } } } + +// nodeHashes is a convenience function that returns the hashes for all of the +// passed indexes of the provided nodes. It is used to construct expected hash +// slices in the tests. +func nodeHashes(nodes []*blockNode, indexes ...int) []chainhash.Hash { + hashes := make([]chainhash.Hash, 0, len(indexes)) + for _, idx := range indexes { + hashes = append(hashes, nodes[idx].hash) + } + return hashes +} + +// nodeHeaders is a convenience function that returns the headers for all of +// the passed indexes of the provided nodes. It is used to construct expected +// located headers in the tests. +func nodeHeaders(nodes []*blockNode, indexes ...int) []wire.BlockHeader { + headers := make([]wire.BlockHeader, 0, len(indexes)) + for _, idx := range indexes { + headers = append(headers, nodes[idx].Header()) + } + return headers +} + +// TestLocateInventory ensures that locating inventory via the LocateHeaders and +// LocateBlocks functions behaves as expected. +func TestLocateInventory(t *testing.T) { + // Construct a synthetic block chain with a block index consisting of + // the following structure. + // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 + // \-> 16a -> 17a + tip := tstTip + chain := newFakeChain(&chaincfg.MainNetParams) + branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18) + branch1Nodes := chainedNodes(branch0Nodes[14], 2) + for _, node := range branch0Nodes { + chain.index.AddNode(node) + } + for _, node := range branch1Nodes { + chain.index.AddNode(node) + } + chain.bestChain.SetTip(tip(branch0Nodes)) + + // Create chain views for different branches of the overall chain to + // simulate a local and remote node on different parts of the chain. + localView := newChainView(tip(branch0Nodes)) + remoteView := newChainView(tip(branch1Nodes)) + + // Create a chain view for a completely unrelated block chain to + // simulate a remote node on a totally different chain. + unrelatedBranchNodes := chainedNodes(nil, 5) + unrelatedView := newChainView(tip(unrelatedBranchNodes)) + + tests := []struct { + name string + locator BlockLocator // locator for requested inventory + hashStop chainhash.Hash // stop hash for locator + maxAllowed uint32 // max to locate, 0 = wire const + headers []wire.BlockHeader // expected located headers + hashes []chainhash.Hash // expected located hashes + }{ + { + // Empty block locators and unknown stop hash. No + // inventory should be located. + name: "no locators, no stop", + locator: nil, + hashStop: chainhash.Hash{}, + headers: nil, + hashes: nil, + }, + { + // Empty block locators and stop hash in side chain. + // The expected result is the requested block. + name: "no locators, stop in side", + locator: nil, + hashStop: tip(branch1Nodes).hash, + headers: nodeHeaders(branch1Nodes, 1), + hashes: nodeHashes(branch1Nodes, 1), + }, + { + // Empty block locators and stop hash in main chain. + // The expected result is the requested block. + name: "no locators, stop in main", + locator: nil, + hashStop: branch0Nodes[12].hash, + headers: nodeHeaders(branch0Nodes, 12), + hashes: nodeHashes(branch0Nodes, 12), + }, + { + // Locators based on remote being on side chain and a + // stop hash local node doesn't know about. The + // expected result is the blocks after the fork point in + // the main chain and the stop hash has no effect. + name: "remote side chain, unknown stop", + locator: remoteView.BlockLocator(nil), + hashStop: chainhash.Hash{0x01}, + headers: nodeHeaders(branch0Nodes, 15, 16, 17), + hashes: nodeHashes(branch0Nodes, 15, 16, 17), + }, + { + // Locators based on remote being on side chain and a + // stop hash in side chain. The expected result is the + // blocks after the fork point in the main chain and the + // stop hash has no effect. + name: "remote side chain, stop in side", + locator: remoteView.BlockLocator(nil), + hashStop: tip(branch1Nodes).hash, + headers: nodeHeaders(branch0Nodes, 15, 16, 17), + hashes: nodeHashes(branch0Nodes, 15, 16, 17), + }, + { + // Locators based on remote being on side chain and a + // stop hash in main chain, but before fork point. The + // expected result is the blocks after the fork point in + // the main chain and the stop hash has no effect. + name: "remote side chain, stop in main before", + locator: remoteView.BlockLocator(nil), + hashStop: branch0Nodes[13].hash, + headers: nodeHeaders(branch0Nodes, 15, 16, 17), + hashes: nodeHashes(branch0Nodes, 15, 16, 17), + }, + { + // Locators based on remote being on side chain and a + // stop hash in main chain, but exactly at the fork + // point. The expected result is the blocks after the + // fork point in the main chain and the stop hash has no + // effect. + name: "remote side chain, stop in main exact", + locator: remoteView.BlockLocator(nil), + hashStop: branch0Nodes[14].hash, + headers: nodeHeaders(branch0Nodes, 15, 16, 17), + hashes: nodeHashes(branch0Nodes, 15, 16, 17), + }, + { + // Locators based on remote being on side chain and a + // stop hash in main chain just after the fork point. + // The expected result is the blocks after the fork + // point in the main chain up to and including the stop + // hash. + name: "remote side chain, stop in main after", + locator: remoteView.BlockLocator(nil), + hashStop: branch0Nodes[15].hash, + headers: nodeHeaders(branch0Nodes, 15), + hashes: nodeHashes(branch0Nodes, 15), + }, + { + // Locators based on remote being on side chain and a + // stop hash in main chain some time after the fork + // point. The expected result is the blocks after the + // fork point in the main chain up to and including the + // stop hash. + name: "remote side chain, stop in main after more", + locator: remoteView.BlockLocator(nil), + hashStop: branch0Nodes[16].hash, + headers: nodeHeaders(branch0Nodes, 15, 16), + hashes: nodeHashes(branch0Nodes, 15, 16), + }, + { + // Locators based on remote being on main chain in the + // past and a stop hash local node doesn't know about. + // The expected result is the blocks after the known + // point in the main chain and the stop hash has no + // effect. + name: "remote main chain past, unknown stop", + locator: localView.BlockLocator(branch0Nodes[12]), + hashStop: chainhash.Hash{0x01}, + headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17), + hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17), + }, + { + // Locators based on remote being on main chain in the + // past and a stop hash in a side chain. The expected + // result is the blocks after the known point in the + // main chain and the stop hash has no effect. + name: "remote main chain past, stop in side", + locator: localView.BlockLocator(branch0Nodes[12]), + hashStop: tip(branch1Nodes).hash, + headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17), + hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17), + }, + { + // Locators based on remote being on main chain in the + // past and a stop hash in the main chain before that + // point. The expected result is the blocks after the + // known point in the main chain and the stop hash has + // no effect. + name: "remote main chain past, stop in main before", + locator: localView.BlockLocator(branch0Nodes[12]), + hashStop: branch0Nodes[11].hash, + headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17), + hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17), + }, + { + // Locators based on remote being on main chain in the + // past and a stop hash in the main chain exactly at that + // point. The expected result is the blocks after the + // known point in the main chain and the stop hash has + // no effect. + name: "remote main chain past, stop in main exact", + locator: localView.BlockLocator(branch0Nodes[12]), + hashStop: branch0Nodes[12].hash, + headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17), + hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17), + }, + { + // Locators based on remote being on main chain in the + // past and a stop hash in the main chain just after + // that point. The expected result is the blocks after + // the known point in the main chain and the stop hash + // has no effect. + name: "remote main chain past, stop in main after", + locator: localView.BlockLocator(branch0Nodes[12]), + hashStop: branch0Nodes[13].hash, + headers: nodeHeaders(branch0Nodes, 13), + hashes: nodeHashes(branch0Nodes, 13), + }, + { + // Locators based on remote being on main chain in the + // past and a stop hash in the main chain some time + // after that point. The expected result is the blocks + // after the known point in the main chain and the stop + // hash has no effect. + name: "remote main chain past, stop in main after more", + locator: localView.BlockLocator(branch0Nodes[12]), + hashStop: branch0Nodes[15].hash, + headers: nodeHeaders(branch0Nodes, 13, 14, 15), + hashes: nodeHashes(branch0Nodes, 13, 14, 15), + }, + { + // Locators based on remote being at exactly the same + // point in the main chain and a stop hash local node + // doesn't know about. The expected result is no + // located inventory. + name: "remote main chain same, unknown stop", + locator: localView.BlockLocator(nil), + hashStop: chainhash.Hash{0x01}, + headers: nil, + hashes: nil, + }, + { + // Locators based on remote being at exactly the same + // point in the main chain and a stop hash at exactly + // the same point. The expected result is no located + // inventory. + name: "remote main chain same, stop same point", + locator: localView.BlockLocator(nil), + hashStop: tip(branch0Nodes).hash, + headers: nil, + hashes: nil, + }, + { + // Locators from remote that don't include any blocks + // the local node knows. This would happen if the + // remote node is on a completely separate chain that + // isn't rooted with the same genesis block. The + // expected result is the blocks after the genesis + // block. + name: "remote unrelated chain", + locator: unrelatedView.BlockLocator(nil), + hashStop: chainhash.Hash{}, + headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), + hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), + }, + { + // Locators from remote for second block in main chain + // and no stop hash, but with an overridden max limit. + // The expected result is the blocks after the second + // block limited by the max. + name: "remote genesis", + locator: locatorHashes(branch0Nodes, 0), + hashStop: chainhash.Hash{}, + maxAllowed: 3, + headers: nodeHeaders(branch0Nodes, 1, 2, 3), + hashes: nodeHashes(branch0Nodes, 1, 2, 3), + }, + { + // Poorly formed locator. + // + // Locator from remote that only includes a single + // block on a side chain the local node knows. The + // expected result is the blocks after the genesis + // block since even though the block is known, it is on + // a side chain and there are no more locators to find + // the fork point. + name: "weak locator, single known side block", + locator: locatorHashes(branch1Nodes, 1), + hashStop: chainhash.Hash{}, + headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), + hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), + }, + { + // Poorly formed locator. + // + // Locator from remote that only includes multiple + // blocks on a side chain the local node knows however + // none in the main chain. The expected result is the + // blocks after the genesis block since even though the + // blocks are known, they are all on a side chain and + // there are no more locators to find the fork point. + name: "weak locator, multiple known side blocks", + locator: locatorHashes(branch1Nodes, 1), + hashStop: chainhash.Hash{}, + headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), + hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), + }, + { + // Poorly formed locator. + // + // Locator from remote that only includes multiple + // blocks on a side chain the local node knows however + // none in the main chain but includes a stop hash in + // the main chain. The expected result is the blocks + // after the genesis block up to the stop hash since + // even though the blocks are known, they are all on a + // side chain and there are no more locators to find the + // fork point. + name: "weak locator, multiple known side blocks, stop in main", + locator: locatorHashes(branch1Nodes, 1), + hashStop: branch0Nodes[5].hash, + headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5), + hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5), + }, + } + for _, test := range tests { + // Ensure the expected headers are located. + var headers []wire.BlockHeader + if test.maxAllowed != 0 { + // Need to use the unexported function to override the + // max allowed for headers. + chain.chainLock.RLock() + headers = chain.locateHeaders(test.locator, + &test.hashStop, test.maxAllowed) + chain.chainLock.RUnlock() + } else { + headers = chain.LocateHeaders(test.locator, + &test.hashStop) + } + if !reflect.DeepEqual(headers, test.headers) { + t.Errorf("%s: unxpected headers -- got %v, want %v", + test.name, headers, test.headers) + continue + } + + // Ensure the expected block hashes are located. + maxAllowed := uint32(wire.MaxBlocksPerMsg) + if test.maxAllowed != 0 { + maxAllowed = test.maxAllowed + } + hashes := chain.LocateBlocks(test.locator, &test.hashStop, + maxAllowed) + if !reflect.DeepEqual(hashes, test.hashes) { + t.Errorf("%s: unxpected hashes -- got %v, want %v", + test.name, hashes, test.hashes) + continue + } + } +} diff --git a/rpcadaptors.go b/rpcadapters.go similarity index 98% rename from rpcadaptors.go rename to rpcadapters.go index 0e8fa393..771985e4 100644 --- a/rpcadaptors.go +++ b/rpcadapters.go @@ -273,6 +273,6 @@ func (b *rpcSyncMgr) SyncPeerID() int32 { // // This function is safe for concurrent access and is part of the // rpcserverSyncManager interface implementation. -func (b *rpcSyncMgr) LocateBlocks(locators []*chainhash.Hash, hashStop *chainhash.Hash) ([]chainhash.Hash, error) { - return b.server.locateBlocks(locators, hashStop) +func (b *rpcSyncMgr) LocateHeaders(locators []*chainhash.Hash, hashStop *chainhash.Hash) []wire.BlockHeader { + return b.server.chain.LocateHeaders(locators, hashStop) } diff --git a/rpcserver.go b/rpcserver.go index 07b4674e..602e793b 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2186,26 +2186,7 @@ func handleGetHeaders(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) return nil, rpcDecodeHexError(c.HashStop) } } - blockHashes, err := s.cfg.SyncMgr.LocateBlocks(blockLocators, &hashStop) - if err != nil { - return nil, &btcjson.RPCError{ - Code: btcjson.ErrRPCDatabase, - Message: "Failed to fetch hashes of block headers: " + - err.Error(), - } - } - headers := make([]wire.BlockHeader, 0, len(blockHashes)) - for i := range blockHashes { - header, err := s.cfg.Chain.FetchHeader(&blockHashes[i]) - if err != nil { - return nil, &btcjson.RPCError{ - Code: btcjson.ErrRPCBlockNotFound, - Message: "Failed to fetch header of block: " + - err.Error(), - } - } - headers = append(headers, header) - } + headers := s.cfg.SyncMgr.LocateHeaders(blockLocators, &hashStop) // Return the serialized block headers as hex-encoded strings. hexBlockHeaders := make([]string, len(headers)) @@ -4143,11 +4124,11 @@ type rpcserverSyncManager interface { // used to sync from or 0 if there is none. SyncPeerID() int32 - // LocateBlocks returns the hashes of the blocks after the first known + // LocateHeaders 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. - LocateBlocks(locators []*chainhash.Hash, hashStop *chainhash.Hash) ([]chainhash.Hash, error) + LocateHeaders(locators []*chainhash.Hash, hashStop *chainhash.Hash) []wire.BlockHeader } // rpcserverConfig is a descriptor containing the RPC server configuration. diff --git a/server.go b/server.go index 9e45870f..626c8ee0 100644 --- a/server.go +++ b/server.go @@ -636,46 +636,19 @@ func (sp *serverPeer) OnGetData(_ *peer.Peer, msg *wire.MsgGetData) { // OnGetBlocks is invoked when a peer receives a getblocks bitcoin // message. func (sp *serverPeer) OnGetBlocks(_ *peer.Peer, msg *wire.MsgGetBlocks) { - // Return all block hashes to the latest one (up to max per message) if - // no stop hash was specified. - // Attempt to find the ending index of the stop hash if specified. - chain := sp.server.blockManager.chain - endIdx := int32(math.MaxInt32) - if !msg.HashStop.IsEqual(&zeroHash) { - height, err := chain.BlockHeightByHash(&msg.HashStop) - if err == nil { - endIdx = height + 1 - } - } - - // Find the most recent known block based on the block locator. + // Find the most recent known block in the best chain 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. - startIdx := int32(1) - for _, hash := range msg.BlockLocatorHashes { - height, err := chain.BlockHeightByHash(hash) - if err == nil { - // Start with the next hash since we know this one. - startIdx = height + 1 - break - } - } - - // Don't attempt to fetch more than we can put into a single message. - autoContinue := false - if endIdx-startIdx > wire.MaxBlocksPerMsg { - endIdx = startIdx + wire.MaxBlocksPerMsg - autoContinue = true - } - - // Fetch the inventory from the block database. - hashList, err := chain.HeightRange(startIdx, endIdx) - if err != nil { - peerLog.Warnf("Block lookup failed: %v", err) - return - } + chain := sp.server.blockManager.chain + hashList := chain.LocateBlocks(msg.BlockLocatorHashes, &msg.HashStop, + wire.MaxBlocksPerMsg) // Generate inventory message. invMsg := wire.NewMsgInv() @@ -687,7 +660,7 @@ func (sp *serverPeer) OnGetBlocks(_ *peer.Peer, msg *wire.MsgGetBlocks) { // Send the inventory message if there is anything to send. if len(invMsg.InvList) > 0 { invListLen := len(invMsg.InvList) - if autoContinue && invListLen == wire.MaxBlocksPerMsg { + if invListLen == wire.MaxBlocksPerMsg { // Intentionally use a copy of the final hash so there // is not a reference into the inventory slice which // would prevent the entire slice from being eligible @@ -699,71 +672,6 @@ func (sp *serverPeer) OnGetBlocks(_ *peer.Peer, msg *wire.MsgGetBlocks) { } } -// locateBlocks returns the hashes of the blocks after the first known block in -// locators, until hashStop is reached, or up to a max of -// wire.MaxBlockHeadersPerMsg block hashes. This implements the search -// algorithm used by getheaders. -func (s *server) locateBlocks(locators []*chainhash.Hash, hashStop *chainhash.Hash) ([]chainhash.Hash, error) { - // Attempt to look up the height of the provided stop hash. - endIdx := int32(math.MaxInt32) - height, err := s.chain.BlockHeightByHash(hashStop) - if err == nil { - endIdx = height + 1 - } - - // There are no block locators so a specific header is being requested - // as identified by the stop hash. - if len(locators) == 0 { - // No blocks with the stop hash were found so there is nothing - // to do. Just return. This behavior mirrors the reference - // implementation. - if endIdx == math.MaxInt32 { - return nil, nil - } - - return []chainhash.Hash{*hashStop}, nil - } - - // Find the most recent known block based on the block locator. - // 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. - startIdx := int32(1) - for _, loc := range locators { - height, err := s.chain.BlockHeightByHash(loc) - if err == nil { - // Start with the next hash since we know this one. - startIdx = height + 1 - break - } - } - - // Don't attempt to fetch more than we can put into a single wire - // message. - if endIdx-startIdx > wire.MaxBlockHeadersPerMsg { - endIdx = startIdx + wire.MaxBlockHeadersPerMsg - } - - // Fetch the inventory from the block database. - return s.chain.HeightRange(startIdx, endIdx) -} - -// fetchHeaders fetches and decodes headers from the db for each hash in -// blockHashes. -func fetchHeaders(chain *blockchain.BlockChain, blockHashes []chainhash.Hash) ([]wire.BlockHeader, error) { - headers := make([]wire.BlockHeader, 0, len(blockHashes)) - for i := range blockHashes { - header, err := chain.FetchHeader(&blockHashes[i]) - if err != nil { - return nil, err - } - headers = append(headers, header) - } - - return headers, nil -} - // OnGetHeaders is invoked when a peer receives a getheaders bitcoin // message. func (sp *serverPeer) OnGetHeaders(_ *peer.Peer, msg *wire.MsgGetHeaders) { @@ -772,30 +680,28 @@ func (sp *serverPeer) OnGetHeaders(_ *peer.Peer, msg *wire.MsgGetHeaders) { return } - blockHashes, err := sp.server.locateBlocks(msg.BlockLocatorHashes, - &msg.HashStop) - if err != nil { - peerLog.Errorf("OnGetHeaders: failed to fetch hashes: %v", err) - return - } - headers, err := fetchHeaders(sp.server.blockManager.chain, blockHashes) - if err != nil { - peerLog.Errorf("OnGetHeaders: failed to fetch block headers: "+ - "%v", err) + // Find the most recent known block in the best chain based on the block + // locator and fetch all of the headers after it until either + // wire.MaxBlockHeadersPerMsg 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. + chain := sp.server.blockManager.chain + headers := chain.LocateHeaders(msg.BlockLocatorHashes, &msg.HashStop) + if len(headers) == 0 { + // Nothing to send. return } + + // Send found headers to the requesting peer. blockHeaders := make([]*wire.BlockHeader, len(headers)) for i := range headers { blockHeaders[i] = &headers[i] } - - if len(blockHeaders) > wire.MaxBlockHeadersPerMsg { - peerLog.Warnf("OnGetHeaders: fetched more block headers than " + - "allowed per message") - // Can still recover from this error, just slice off the extra - // headers and continue queing the message. - blockHeaders = blockHeaders[:wire.MaxBlockHeaderPayload] - } sp.QueueMessage(&wire.MsgHeaders{Headers: blockHeaders}, nil) }