blockchain: Improve and correct chainview set tip.

This modifies the function to set the tip in the new chainview code to
bulk copy existing nodes when it needs to expand the cap rather than
simply creating a new empty slice and allowing the walk code below it to
repopulate it.  This is a nice optimization since, in practice, most of
the time expanding the cap is only required when the active chain is
being extended after having run for a while which means the end result
is that it will be able to bulk copy all the nodes and just add the most
recent one versus having to walk them all and add them back.

Also, while here expand the tests for setting the tip to ensure the
nodes contained in the resulting view are correct after forcing the
resizes and correct a bug they exposed where changing between a
longer-shorter-longer chain where the longer chain is the same chain
could result in not populating the view correctly.

Finally, update the fake nodes generated by the tests to use a
nonce generated by a deterministic prng in order to ensure the hashes of
all fake nodes are unique, but reproducible.
This commit is contained in:
Dave Collins 2017-08-19 04:31:23 -05:00
parent 2804f4cffe
commit 2a4be16b6c
No known key found for this signature in database
GPG key ID: B8904D9D9C93D1F2
2 changed files with 46 additions and 19 deletions

View file

@ -109,9 +109,15 @@ func (c *chainView) setTip(node *blockNode) {
// week.
needed := node.height + 1
if int32(cap(c.nodes)) < needed {
c.nodes = make([]*blockNode, needed, needed+approxNodesPerWeek)
nodes := make([]*blockNode, needed, needed+approxNodesPerWeek)
copy(nodes, c.nodes)
c.nodes = nodes
} else {
prevLen := int32(len(c.nodes))
c.nodes = c.nodes[0:needed]
for i := prevLen; i < needed; i++ {
c.nodes[i] = nil
}
}
for node != nil && c.nodes[node.height] != node {

View file

@ -6,11 +6,16 @@ package blockchain
import (
"fmt"
"math/rand"
"testing"
"github.com/btcsuite/btcd/wire"
)
// testNoncePrng provides a deterministic prng for the nonce in generated fake
// nodes. The ensures that the node have unique hashes.
var testNoncePrng = rand.New(rand.NewSource(0))
// chainedNodes returns the specified number of nodes constructed such that each
// subsequent node points to the previous one to create a chain. The first node
// will point to the passed parent which can be nil if desired.
@ -20,7 +25,7 @@ func chainedNodes(parent *blockNode, numNodes int) []*blockNode {
for i := 0; i < numNodes; i++ {
// This is invalid, but all that is needed is enough to get the
// synthetic tests to work.
header := wire.BlockHeader{}
header := wire.BlockHeader{Nonce: testNoncePrng.Uint32()}
height := int32(0)
if tip != nil {
header.PrevBlock = tip.hash
@ -306,52 +311,68 @@ func TestChainViewSetTip(t *testing.T) {
tip := tstTip
tests := []struct {
name string
view *chainView
tips []*blockNode
name string
view *chainView // active view
tips []*blockNode // tips to set
contains [][]*blockNode // expected nodes in view for each tip
}{
{
// Create an empty view and set the tip to increasingly
// longer chains.
name: "increasing",
view: newChainView(nil),
tips: []*blockNode{tip(branch0Nodes), tip(branch1Nodes)},
name: "increasing",
view: newChainView(nil),
tips: []*blockNode{tip(branch0Nodes), tip(branch1Nodes)},
contains: [][]*blockNode{branch0Nodes, branch1Nodes},
},
{
// Create a view with a longer chain and set the tip to
// increasingly shorter chains.
name: "decreasing",
view: newChainView(tip(branch1Nodes)),
tips: []*blockNode{tip(branch0Nodes), nil},
name: "decreasing",
view: newChainView(tip(branch1Nodes)),
tips: []*blockNode{tip(branch0Nodes), nil},
contains: [][]*blockNode{branch0Nodes, nil},
},
{
// Create a view with a shorter chain and set the tip to
// a longer chain followed by setting it back to the
// shorter chain.
name: "small-large-small",
view: newChainView(tip(branch0Nodes)),
tips: []*blockNode{tip(branch1Nodes), tip(branch0Nodes)},
name: "small-large-small",
view: newChainView(tip(branch0Nodes)),
tips: []*blockNode{tip(branch1Nodes), tip(branch0Nodes)},
contains: [][]*blockNode{branch1Nodes, branch0Nodes},
},
{
// Create a view with a longer chain and set the tip to
// a smaller chain followed by setting it back to the
// longer chain.
name: "large-small-large",
view: newChainView(tip(branch1Nodes)),
tips: []*blockNode{tip(branch0Nodes), tip(branch1Nodes)},
name: "large-small-large",
view: newChainView(tip(branch1Nodes)),
tips: []*blockNode{tip(branch0Nodes), tip(branch1Nodes)},
contains: [][]*blockNode{branch0Nodes, branch1Nodes},
},
}
testLoop:
for _, test := range tests {
for _, tip := range test.tips {
for i, tip := range test.tips {
// Ensure the view tip is the expected node.
test.view.SetTip(tip)
if test.view.Tip() != tip {
t.Errorf("%s: unexpected view tip -- got %v, "+
"want %v", test.name, test.view.Tip(),
tip)
continue
continue testLoop
}
// Ensure all expected nodes are contained in the view.
for _, node := range test.contains[i] {
if !test.view.Contains(node) {
t.Errorf("%s: expected %v in active view",
test.name, node)
continue testLoop
}
}
}
}
}