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:
parent
2804f4cffe
commit
2a4be16b6c
2 changed files with 46 additions and 19 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue