github.com/lbryio/lbcd@v0.22.119/blockchain/chainview_test.go (about) 1 // Copyright (c) 2017 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package blockchain 6 7 import ( 8 "fmt" 9 "math/rand" 10 "reflect" 11 "testing" 12 13 "github.com/lbryio/lbcd/wire" 14 ) 15 16 // testNoncePrng provides a deterministic prng for the nonce in generated fake 17 // nodes. The ensures that the node have unique hashes. 18 var testNoncePrng = rand.New(rand.NewSource(0)) 19 20 // chainedNodes returns the specified number of nodes constructed such that each 21 // subsequent node points to the previous one to create a chain. The first node 22 // will point to the passed parent which can be nil if desired. 23 func chainedNodes(parent *blockNode, numNodes int) []*blockNode { 24 nodes := make([]*blockNode, numNodes) 25 tip := parent 26 for i := 0; i < numNodes; i++ { 27 // This is invalid, but all that is needed is enough to get the 28 // synthetic tests to work. 29 header := wire.BlockHeader{Nonce: testNoncePrng.Uint32()} 30 if tip != nil { 31 header.PrevBlock = tip.hash 32 } 33 nodes[i] = newBlockNode(&header, tip) 34 tip = nodes[i] 35 } 36 return nodes 37 } 38 39 // String returns the block node as a human-readable name. 40 func (node blockNode) String() string { 41 return fmt.Sprintf("%s(%d)", node.hash, node.height) 42 } 43 44 // tstTip is a convenience function to grab the tip of a chain of block nodes 45 // created via chainedNodes. 46 func tstTip(nodes []*blockNode) *blockNode { 47 return nodes[len(nodes)-1] 48 } 49 50 // locatorHashes is a convenience function that returns the hashes for all of 51 // the passed indexes of the provided nodes. It is used to construct expected 52 // block locators in the tests. 53 func locatorHashes(nodes []*blockNode, indexes ...int) BlockLocator { 54 hashes := make(BlockLocator, 0, len(indexes)) 55 for _, idx := range indexes { 56 hashes = append(hashes, &nodes[idx].hash) 57 } 58 return hashes 59 } 60 61 // zipLocators is a convenience function that returns a single block locator 62 // given a variable number of them and is used in the tests. 63 func zipLocators(locators ...BlockLocator) BlockLocator { 64 var hashes BlockLocator 65 for _, locator := range locators { 66 hashes = append(hashes, locator...) 67 } 68 return hashes 69 } 70 71 // TestChainView ensures all of the exported functionality of chain views works 72 // as intended with the exception of some special cases which are handled in 73 // other tests. 74 func TestChainView(t *testing.T) { 75 // Construct a synthetic block index consisting of the following 76 // structure. 77 // 0 -> 1 -> 2 -> 3 -> 4 78 // \-> 2a -> 3a -> 4a -> 5a -> 6a -> 7a -> ... -> 26a 79 // \-> 3a'-> 4a' -> 5a' 80 branch0Nodes := chainedNodes(nil, 5) 81 branch1Nodes := chainedNodes(branch0Nodes[1], 25) 82 branch2Nodes := chainedNodes(branch1Nodes[0], 3) 83 84 tip := tstTip 85 tests := []struct { 86 name string 87 view *chainView // active view 88 genesis *blockNode // expected genesis block of active view 89 tip *blockNode // expected tip of active view 90 side *chainView // side chain view 91 sideTip *blockNode // expected tip of side chain view 92 fork *blockNode // expected fork node 93 contains []*blockNode // expected nodes in active view 94 noContains []*blockNode // expected nodes NOT in active view 95 equal *chainView // view expected equal to active view 96 unequal *chainView // view expected NOT equal to active 97 locator BlockLocator // expected locator for active view tip 98 }{ 99 { 100 // Create a view for branch 0 as the active chain and 101 // another view for branch 1 as the side chain. 102 name: "chain0-chain1", 103 view: newChainView(tip(branch0Nodes)), 104 genesis: branch0Nodes[0], 105 tip: tip(branch0Nodes), 106 side: newChainView(tip(branch1Nodes)), 107 sideTip: tip(branch1Nodes), 108 fork: branch0Nodes[1], 109 contains: branch0Nodes, 110 noContains: branch1Nodes, 111 equal: newChainView(tip(branch0Nodes)), 112 unequal: newChainView(tip(branch1Nodes)), 113 locator: locatorHashes(branch0Nodes, 4, 3, 2, 1, 0), 114 }, 115 { 116 // Create a view for branch 1 as the active chain and 117 // another view for branch 2 as the side chain. 118 name: "chain1-chain2", 119 view: newChainView(tip(branch1Nodes)), 120 genesis: branch0Nodes[0], 121 tip: tip(branch1Nodes), 122 side: newChainView(tip(branch2Nodes)), 123 sideTip: tip(branch2Nodes), 124 fork: branch1Nodes[0], 125 contains: branch1Nodes, 126 noContains: branch2Nodes, 127 equal: newChainView(tip(branch1Nodes)), 128 unequal: newChainView(tip(branch2Nodes)), 129 locator: zipLocators( 130 locatorHashes(branch1Nodes, 24, 23, 22, 21, 20, 131 19, 18, 17, 16, 15, 14, 13, 11, 7), 132 locatorHashes(branch0Nodes, 1, 0)), 133 }, 134 { 135 // Create a view for branch 2 as the active chain and 136 // another view for branch 0 as the side chain. 137 name: "chain2-chain0", 138 view: newChainView(tip(branch2Nodes)), 139 genesis: branch0Nodes[0], 140 tip: tip(branch2Nodes), 141 side: newChainView(tip(branch0Nodes)), 142 sideTip: tip(branch0Nodes), 143 fork: branch0Nodes[1], 144 contains: branch2Nodes, 145 noContains: branch0Nodes[2:], 146 equal: newChainView(tip(branch2Nodes)), 147 unequal: newChainView(tip(branch0Nodes)), 148 locator: zipLocators( 149 locatorHashes(branch2Nodes, 2, 1, 0), 150 locatorHashes(branch1Nodes, 0), 151 locatorHashes(branch0Nodes, 1, 0)), 152 }, 153 } 154 testLoop: 155 for _, test := range tests { 156 // Ensure the active and side chain heights are the expected 157 // values. 158 if test.view.Height() != test.tip.height { 159 t.Errorf("%s: unexpected active view height -- got "+ 160 "%d, want %d", test.name, test.view.Height(), 161 test.tip.height) 162 continue 163 } 164 if test.side.Height() != test.sideTip.height { 165 t.Errorf("%s: unexpected side view height -- got %d, "+ 166 "want %d", test.name, test.side.Height(), 167 test.sideTip.height) 168 continue 169 } 170 171 // Ensure the active and side chain genesis block is the 172 // expected value. 173 if test.view.Genesis() != test.genesis { 174 t.Errorf("%s: unexpected active view genesis -- got "+ 175 "%v, want %v", test.name, test.view.Genesis(), 176 test.genesis) 177 continue 178 } 179 if test.side.Genesis() != test.genesis { 180 t.Errorf("%s: unexpected side view genesis -- got %v, "+ 181 "want %v", test.name, test.view.Genesis(), 182 test.genesis) 183 continue 184 } 185 186 // Ensure the active and side chain tips are the expected nodes. 187 if test.view.Tip() != test.tip { 188 t.Errorf("%s: unexpected active view tip -- got %v, "+ 189 "want %v", test.name, test.view.Tip(), test.tip) 190 continue 191 } 192 if test.side.Tip() != test.sideTip { 193 t.Errorf("%s: unexpected active view tip -- got %v, "+ 194 "want %v", test.name, test.side.Tip(), 195 test.sideTip) 196 continue 197 } 198 199 // Ensure that regardless of the order the two chains are 200 // compared they both return the expected fork point. 201 forkNode := test.view.FindFork(test.side.Tip()) 202 if forkNode != test.fork { 203 t.Errorf("%s: unexpected fork node (view, side) -- "+ 204 "got %v, want %v", test.name, forkNode, 205 test.fork) 206 continue 207 } 208 forkNode = test.side.FindFork(test.view.Tip()) 209 if forkNode != test.fork { 210 t.Errorf("%s: unexpected fork node (side, view) -- "+ 211 "got %v, want %v", test.name, forkNode, 212 test.fork) 213 continue 214 } 215 216 // Ensure that the fork point for a node that is already part 217 // of the chain view is the node itself. 218 forkNode = test.view.FindFork(test.view.Tip()) 219 if forkNode != test.view.Tip() { 220 t.Errorf("%s: unexpected fork node (view, tip) -- "+ 221 "got %v, want %v", test.name, forkNode, 222 test.view.Tip()) 223 continue 224 } 225 226 // Ensure all expected nodes are contained in the active view. 227 for _, node := range test.contains { 228 if !test.view.Contains(node) { 229 t.Errorf("%s: expected %v in active view", 230 test.name, node) 231 continue testLoop 232 } 233 } 234 235 // Ensure all nodes from side chain view are NOT contained in 236 // the active view. 237 for _, node := range test.noContains { 238 if test.view.Contains(node) { 239 t.Errorf("%s: unexpected %v in active view", 240 test.name, node) 241 continue testLoop 242 } 243 } 244 245 // Ensure equality of different views into the same chain works 246 // as intended. 247 if !test.view.Equals(test.equal) { 248 t.Errorf("%s: unexpected unequal views", test.name) 249 continue 250 } 251 if test.view.Equals(test.unequal) { 252 t.Errorf("%s: unexpected equal views", test.name) 253 continue 254 } 255 256 // Ensure all nodes contained in the view return the expected 257 // next node. 258 for i, node := range test.contains { 259 // Final node expects nil for the next node. 260 var expected *blockNode 261 if i < len(test.contains)-1 { 262 expected = test.contains[i+1] 263 } 264 if next := test.view.Next(node); next != expected { 265 t.Errorf("%s: unexpected next node -- got %v, "+ 266 "want %v", test.name, next, expected) 267 continue testLoop 268 } 269 } 270 271 // Ensure nodes that are not contained in the view do not 272 // produce a successor node. 273 for _, node := range test.noContains { 274 if next := test.view.Next(node); next != nil { 275 t.Errorf("%s: unexpected next node -- got %v, "+ 276 "want nil", test.name, next) 277 continue testLoop 278 } 279 } 280 281 // Ensure all nodes contained in the view can be retrieved by 282 // height. 283 for _, wantNode := range test.contains { 284 node := test.view.NodeByHeight(wantNode.height) 285 if node != wantNode { 286 t.Errorf("%s: unexpected node for height %d -- "+ 287 "got %v, want %v", test.name, 288 wantNode.height, node, wantNode) 289 continue testLoop 290 } 291 } 292 293 // Ensure the block locator for the tip of the active view 294 // consists of the expected hashes. 295 locator := test.view.BlockLocator(test.view.tip()) 296 if !reflect.DeepEqual(locator, test.locator) { 297 t.Errorf("%s: unexpected locator -- got %v, want %v", 298 test.name, locator, test.locator) 299 continue 300 } 301 } 302 } 303 304 // TestChainViewForkCorners ensures that finding the fork between two chains 305 // works in some corner cases such as when the two chains have completely 306 // unrelated histories. 307 func TestChainViewForkCorners(t *testing.T) { 308 // Construct two unrelated single branch synthetic block indexes. 309 branchNodes := chainedNodes(nil, 5) 310 unrelatedBranchNodes := chainedNodes(nil, 7) 311 312 // Create chain views for the two unrelated histories. 313 view1 := newChainView(tstTip(branchNodes)) 314 view2 := newChainView(tstTip(unrelatedBranchNodes)) 315 316 // Ensure attempting to find a fork point with a node that doesn't exist 317 // doesn't produce a node. 318 if fork := view1.FindFork(nil); fork != nil { 319 t.Fatalf("FindFork: unexpected fork -- got %v, want nil", fork) 320 } 321 322 // Ensure attempting to find a fork point in two chain views with 323 // totally unrelated histories doesn't produce a node. 324 for _, node := range branchNodes { 325 if fork := view2.FindFork(node); fork != nil { 326 t.Fatalf("FindFork: unexpected fork -- got %v, want nil", 327 fork) 328 } 329 } 330 for _, node := range unrelatedBranchNodes { 331 if fork := view1.FindFork(node); fork != nil { 332 t.Fatalf("FindFork: unexpected fork -- got %v, want nil", 333 fork) 334 } 335 } 336 } 337 338 // TestChainViewSetTip ensures changing the tip works as intended including 339 // capacity changes. 340 func TestChainViewSetTip(t *testing.T) { 341 // Construct a synthetic block index consisting of the following 342 // structure. 343 // 0 -> 1 -> 2 -> 3 -> 4 344 // \-> 2a -> 3a -> 4a -> 5a -> 6a -> 7a -> ... -> 26a 345 branch0Nodes := chainedNodes(nil, 5) 346 branch1Nodes := chainedNodes(branch0Nodes[1], 25) 347 348 tip := tstTip 349 tests := []struct { 350 name string 351 view *chainView // active view 352 tips []*blockNode // tips to set 353 contains [][]*blockNode // expected nodes in view for each tip 354 }{ 355 { 356 // Create an empty view and set the tip to increasingly 357 // longer chains. 358 name: "increasing", 359 view: newChainView(nil), 360 tips: []*blockNode{tip(branch0Nodes), tip(branch1Nodes)}, 361 contains: [][]*blockNode{branch0Nodes, branch1Nodes}, 362 }, 363 { 364 // Create a view with a longer chain and set the tip to 365 // increasingly shorter chains. 366 name: "decreasing", 367 view: newChainView(tip(branch1Nodes)), 368 tips: []*blockNode{tip(branch0Nodes), nil}, 369 contains: [][]*blockNode{branch0Nodes, nil}, 370 }, 371 { 372 // Create a view with a shorter chain and set the tip to 373 // a longer chain followed by setting it back to the 374 // shorter chain. 375 name: "small-large-small", 376 view: newChainView(tip(branch0Nodes)), 377 tips: []*blockNode{tip(branch1Nodes), tip(branch0Nodes)}, 378 contains: [][]*blockNode{branch1Nodes, branch0Nodes}, 379 }, 380 { 381 // Create a view with a longer chain and set the tip to 382 // a smaller chain followed by setting it back to the 383 // longer chain. 384 name: "large-small-large", 385 view: newChainView(tip(branch1Nodes)), 386 tips: []*blockNode{tip(branch0Nodes), tip(branch1Nodes)}, 387 contains: [][]*blockNode{branch0Nodes, branch1Nodes}, 388 }, 389 } 390 391 testLoop: 392 for _, test := range tests { 393 for i, tip := range test.tips { 394 // Ensure the view tip is the expected node. 395 test.view.SetTip(tip) 396 if test.view.Tip() != tip { 397 t.Errorf("%s: unexpected view tip -- got %v, "+ 398 "want %v", test.name, test.view.Tip(), 399 tip) 400 continue testLoop 401 } 402 403 // Ensure all expected nodes are contained in the view. 404 for _, node := range test.contains[i] { 405 if !test.view.Contains(node) { 406 t.Errorf("%s: expected %v in active view", 407 test.name, node) 408 continue testLoop 409 } 410 } 411 412 } 413 } 414 } 415 416 // TestChainViewNil ensures that creating and accessing a nil chain view behaves 417 // as expected. 418 func TestChainViewNil(t *testing.T) { 419 // Ensure two unininitialized views are considered equal. 420 view := newChainView(nil) 421 if !view.Equals(newChainView(nil)) { 422 t.Fatal("uninitialized nil views unequal") 423 } 424 425 // Ensure the genesis of an uninitialized view does not produce a node. 426 if genesis := view.Genesis(); genesis != nil { 427 t.Fatalf("Genesis: unexpected genesis -- got %v, want nil", 428 genesis) 429 } 430 431 // Ensure the tip of an uninitialized view does not produce a node. 432 if tip := view.Tip(); tip != nil { 433 t.Fatalf("Tip: unexpected tip -- got %v, want nil", tip) 434 } 435 436 // Ensure the height of an uninitialized view is the expected value. 437 if height := view.Height(); height != -1 { 438 t.Fatalf("Height: unexpected height -- got %d, want -1", height) 439 } 440 441 // Ensure attempting to get a node for a height that does not exist does 442 // not produce a node. 443 if node := view.NodeByHeight(10); node != nil { 444 t.Fatalf("NodeByHeight: unexpected node -- got %v, want nil", node) 445 } 446 447 // Ensure an uninitialized view does not report it contains nodes. 448 fakeNode := chainedNodes(nil, 1)[0] 449 if view.Contains(fakeNode) { 450 t.Fatalf("Contains: view claims it contains node %v", fakeNode) 451 } 452 453 // Ensure the next node for a node that does not exist does not produce 454 // a node. 455 if next := view.Next(nil); next != nil { 456 t.Fatalf("Next: unexpected next node -- got %v, want nil", next) 457 } 458 459 // Ensure the next node for a node that exists does not produce a node. 460 if next := view.Next(fakeNode); next != nil { 461 t.Fatalf("Next: unexpected next node -- got %v, want nil", next) 462 } 463 464 // Ensure attempting to find a fork point with a node that doesn't exist 465 // doesn't produce a node. 466 if fork := view.FindFork(nil); fork != nil { 467 t.Fatalf("FindFork: unexpected fork -- got %v, want nil", fork) 468 } 469 470 // Ensure attempting to get a block locator for the tip doesn't produce 471 // one since the tip is nil. 472 if locator := view.BlockLocator(nil); locator != nil { 473 t.Fatalf("BlockLocator: unexpected locator -- got %v, want nil", 474 locator) 475 } 476 477 // Ensure attempting to get a block locator for a node that exists still 478 // works as intended. 479 branchNodes := chainedNodes(nil, 50) 480 wantLocator := locatorHashes(branchNodes, 49, 48, 47, 46, 45, 44, 43, 481 42, 41, 40, 39, 38, 36, 32, 24, 8, 0) 482 locator := view.BlockLocator(tstTip(branchNodes)) 483 if !reflect.DeepEqual(locator, wantLocator) { 484 t.Fatalf("BlockLocator: unexpected locator -- got %v, want %v", 485 locator, wantLocator) 486 } 487 }