blockchain: HeightToHashRange function for pulling range of hashes.

This functionality is required to implement BIP 157.
This commit is contained in:
Jim Posen 2018-01-19 11:59:34 -08:00 committed by Olaoluwa Osuntokun
parent 621f347929
commit 07393c0dab
2 changed files with 134 additions and 0 deletions

View file

@ -1322,6 +1322,48 @@ func (b *BlockChain) HeightRange(startHeight, endHeight int32) ([]chainhash.Hash
return hashes, nil
}
// HeightToHashRange returns a range of block hashes for the given start height
// and end hash, inclusive on both ends. The hashes are for all blocks that are
// ancestors of endHash with height greater than or equal to startHeight. The
// end hash must belong to a block that is known to be valid.
//
// This function is safe for concurrent access.
func (b *BlockChain) HeightToHashRange(startHeight int32,
endHash *chainhash.Hash, maxResults int) ([]chainhash.Hash, error) {
endNode := b.index.LookupNode(endHash)
if endNode == nil {
return nil, fmt.Errorf("no known block header with hash %v", endHash)
}
if !b.index.NodeStatus(endNode).KnownValid() {
return nil, fmt.Errorf("block %v is not yet validated", endHash)
}
endHeight := endNode.height
if startHeight < 0 {
return nil, fmt.Errorf("start height (%d) is below 0", startHeight)
}
if startHeight > endHeight {
return nil, fmt.Errorf("start height (%d) is past end height (%d)",
startHeight, endHeight)
}
resultsLength := int(endHeight - startHeight + 1)
if resultsLength > maxResults {
return nil, fmt.Errorf("number of results (%d) would exceed max (%d)",
resultsLength, maxResults)
}
// Walk backwards from endHeight to startHeight, collecting block hashes.
node := endNode
hashes := make([]chainhash.Hash, resultsLength)
for i := resultsLength - 1; i >= 0; i-- {
hashes[i] = node.hash
node = node.parent
}
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.

View file

@ -800,3 +800,95 @@ func TestLocateInventory(t *testing.T) {
}
}
}
// TestHeightToHashRange ensures that fetching a range of block hashes by start
// height and end hash works as expected.
func TestHeightToHashRange(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 -> 18a (unvalidated)
tip := tstTip
chain := newFakeChain(&chaincfg.MainNetParams)
branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
branch1Nodes := chainedNodes(branch0Nodes[14], 3)
for _, node := range branch0Nodes {
chain.index.SetStatusFlags(node, statusValid)
chain.index.AddNode(node)
}
for _, node := range branch1Nodes {
if node.height < 18 {
chain.index.SetStatusFlags(node, statusValid)
}
chain.index.AddNode(node)
}
chain.bestChain.SetTip(tip(branch0Nodes))
tests := []struct {
name string
startHeight int32 // locator for requested inventory
endHash chainhash.Hash // stop hash for locator
maxResults int // max to locate, 0 = wire const
hashes []chainhash.Hash // expected located hashes
expectError bool
}{
{
name: "blocks below tip",
startHeight: 11,
endHash: branch0Nodes[14].hash,
maxResults: 10,
hashes: nodeHashes(branch0Nodes, 10, 11, 12, 13, 14),
},
{
name: "blocks on main chain",
startHeight: 15,
endHash: branch0Nodes[17].hash,
maxResults: 10,
hashes: nodeHashes(branch0Nodes, 14, 15, 16, 17),
},
{
name: "blocks on stale chain",
startHeight: 15,
endHash: branch1Nodes[1].hash,
maxResults: 10,
hashes: append(nodeHashes(branch0Nodes, 14),
nodeHashes(branch1Nodes, 0, 1)...),
},
{
name: "invalid start height",
startHeight: 19,
endHash: branch0Nodes[17].hash,
maxResults: 10,
expectError: true,
},
{
name: "too many results",
startHeight: 1,
endHash: branch0Nodes[17].hash,
maxResults: 10,
expectError: true,
},
{
name: "unvalidated block",
startHeight: 15,
endHash: branch1Nodes[2].hash,
maxResults: 10,
expectError: true,
},
}
for _, test := range tests {
hashes, err := chain.HeightToHashRange(test.startHeight, &test.endHash,
test.maxResults)
if err != nil {
if !test.expectError {
t.Errorf("%s: unexpected error: %v", test.name, err)
}
continue
}
if !reflect.DeepEqual(hashes, test.hashes) {
t.Errorf("%s: unxpected hashes -- got %v, want %v",
test.name, hashes, test.hashes)
}
}
}