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