Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

blockchain: Faster chain view block locator. #1338

Merged
merged 1 commit into from
Jul 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions blockchain/chainview.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,85 @@ func (c *chainView) FindFork(node *blockNode) *blockNode {
c.mtx.Unlock()
return fork
}

// blockLocator returns a block locator for the passed block node. The passed
// node can be nil in which case the block locator for the current tip
// associated with the view will be returned. This only differs from the
// exported version in that it is up to the caller to ensure the lock is held.
//
// See the exported BlockLocator function comments for more details.
//
// This function MUST be called with the view mutex locked (for reads).
func (c *chainView) blockLocator(node *blockNode) BlockLocator {
// Use the current tip if requested.
if node == nil {
node = c.tip()
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.
var maxEntries uint8
if node.height <= 12 {
maxEntries = uint8(node.height) + 1
} else {
// Requested hash itself + previous 10 entries + genesis block.
// Then floor(log2(height-10)) entries for the skip portion.
adjustedHeight := uint32(node.height) - 10
maxEntries = 12 + fastLog2Floor(adjustedHeight)
}
locator := make(BlockLocator, 0, maxEntries)

step := int64(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
}

// When the node is in the current chain view, all of its
// ancestors must be too, so use a much faster O(1) lookup in
// that case. Otherwise, fall back to walking backwards through
// the nodes of the other chain to the correct ancestor.
if c.contains(node) {
node = c.nodes[height]
} else {
node = node.Ancestor(height)
}

// Once 11 entries have been included, start doubling the
// distance between included hashes.
if len(locator) > 10 {
step *= 2
}
}

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 current tip
// associated with the view will be returned.
//
// See the BlockLocator type for details on the algorithm used to create a block
// locator.
//
// This function is safe for concurrent access.
func (c *chainView) BlockLocator(node *blockNode) BlockLocator {
c.mtx.Lock()
locator := c.blockLocator(node)
c.mtx.Unlock()
return locator
}
48 changes: 48 additions & 0 deletions blockchain/chainview_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package blockchain

import (
"fmt"
"reflect"
"testing"
)

Expand All @@ -15,6 +16,16 @@ func (node blockNode) String() string {
return fmt.Sprintf("%s(%d)", node.hash, node.height)
}

// zipLocators is a convenience function that returns a single block locator
// given a variable number of them and is used in the tests.
func zipLocators(locators ...BlockLocator) BlockLocator {
var hashes BlockLocator
for _, locator := range locators {
hashes = append(hashes, locator...)
}
return hashes
}

// TestChainView ensures all of the exported functionality of chain views works
// as intended with the expection of some special cases which are handled in
// other tests.
Expand All @@ -40,6 +51,7 @@ func TestChainView(t *testing.T) {
noContains []*blockNode // expected nodes NOT in active view
equal *chainView // view expected equal to active view
unequal *chainView // view expected NOT equal to active
locator BlockLocator // expected locator for active view tip
}{
{
// Create a view for branch 0 as the active chain and
Expand All @@ -55,6 +67,7 @@ func TestChainView(t *testing.T) {
noContains: branch1Nodes,
equal: newChainView(branchTip(branch0Nodes)),
unequal: newChainView(branchTip(branch1Nodes)),
locator: locatorHashes(branch0Nodes, 4, 3, 2, 1, 0),
},
{
// Create a view for branch 1 as the active chain and
Expand All @@ -70,6 +83,10 @@ func TestChainView(t *testing.T) {
noContains: branch2Nodes,
equal: newChainView(branchTip(branch1Nodes)),
unequal: newChainView(branchTip(branch2Nodes)),
locator: zipLocators(
locatorHashes(branch1Nodes, 24, 23, 22, 21, 20,
19, 18, 17, 16, 15, 14, 13, 11, 7),
locatorHashes(branch0Nodes, 1, 0)),
},
{
// Create a view for branch 2 as the active chain and
Expand All @@ -85,6 +102,10 @@ func TestChainView(t *testing.T) {
noContains: branch0Nodes[2:],
equal: newChainView(branchTip(branch2Nodes)),
unequal: newChainView(branchTip(branch0Nodes)),
locator: zipLocators(
locatorHashes(branch2Nodes, 2, 1, 0),
locatorHashes(branch1Nodes, 0),
locatorHashes(branch0Nodes, 1, 0)),
},
}
testLoop:
Expand Down Expand Up @@ -225,6 +246,15 @@ testLoop:
continue testLoop
}
}

// Ensure the block locator for the tip of the active view
// consists of the expected hashes.
locator := test.view.BlockLocator(test.view.tip())
if !reflect.DeepEqual(locator, test.locator) {
t.Errorf("%s: unexpected locator -- got %v, want %v",
test.name, locator, test.locator)
continue
}
}
}

Expand Down Expand Up @@ -395,4 +425,22 @@ func TestChainViewNil(t *testing.T) {
if fork := view.FindFork(nil); fork != nil {
t.Fatalf("FindFork: unexpected fork -- got %v, want nil", fork)
}

// Ensure attempting to get a block locator for the tip doesn't produce
// one since the tip is nil.
if locator := view.BlockLocator(nil); locator != nil {
t.Fatalf("BlockLocator: unexpected locator -- got %v, want nil",
locator)
}

// Ensure attempting to get a block locator for a node that exists still
// works as intended.
branchNodes := chainedFakeNodes(nil, 50)
wantLocator := locatorHashes(branchNodes, 49, 48, 47, 46, 45, 44, 43,
42, 41, 40, 39, 38, 36, 32, 24, 8, 0)
locator := view.BlockLocator(branchTip(branchNodes))
if !reflect.DeepEqual(locator, wantLocator) {
t.Fatalf("BlockLocator: unexpected locator -- got %v, want %v",
locator, wantLocator)
}
}