github.com/lbryio/lbcd@v0.22.119/blockchain/chain_test.go (about) 1 // Copyright (c) 2013-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 "reflect" 9 "testing" 10 "time" 11 12 "github.com/lbryio/lbcd/chaincfg" 13 "github.com/lbryio/lbcd/chaincfg/chainhash" 14 "github.com/lbryio/lbcd/wire" 15 btcutil "github.com/lbryio/lbcutil" 16 ) 17 18 // TestCalcSequenceLock tests the LockTimeToSequence function, and the 19 // CalcSequenceLock method of a Chain instance. The tests exercise several 20 // combinations of inputs to the CalcSequenceLock function in order to ensure 21 // the returned SequenceLocks are correct for each test instance. 22 func TestCalcSequenceLock(t *testing.T) { 23 netParams := &chaincfg.SimNetParams 24 25 // We need to activate CSV in order to test the processing logic, so 26 // manually craft the block version that's used to signal the soft-fork 27 // activation. 28 csvBit := netParams.Deployments[chaincfg.DeploymentCSV].BitNumber 29 blockVersion := int32(0x20000000 | (uint32(1) << csvBit)) 30 31 // Generate enough synthetic blocks to activate CSV. 32 chain := newFakeChain(netParams) 33 node := chain.bestChain.Tip() 34 blockTime := node.Header().Timestamp 35 numBlocksToActivate := (netParams.MinerConfirmationWindow * 3) 36 for i := uint32(0); i < numBlocksToActivate; i++ { 37 blockTime = blockTime.Add(time.Second) 38 node = newFakeNode(node, blockVersion, 0, blockTime) 39 chain.index.AddNode(node) 40 chain.bestChain.SetTip(node) 41 } 42 43 // Create a utxo view with a fake utxo for the inputs used in the 44 // transactions created below. This utxo is added such that it has an 45 // age of 4 blocks. 46 targetTx := btcutil.NewTx(&wire.MsgTx{ 47 TxOut: []*wire.TxOut{{ 48 PkScript: nil, 49 Value: 10, 50 }}, 51 }) 52 utxoView := NewUtxoViewpoint() 53 utxoView.AddTxOuts(targetTx, int32(numBlocksToActivate)-4) 54 utxoView.SetBestHash(&node.hash) 55 56 // Create a utxo that spends the fake utxo created above for use in the 57 // transactions created in the tests. It has an age of 4 blocks. Note 58 // that the sequence lock heights are always calculated from the same 59 // point of view that they were originally calculated from for a given 60 // utxo. That is to say, the height prior to it. 61 utxo := wire.OutPoint{ 62 Hash: *targetTx.Hash(), 63 Index: 0, 64 } 65 prevUtxoHeight := int32(numBlocksToActivate) - 4 66 67 // Obtain the median time past from the PoV of the input created above. 68 // The MTP for the input is the MTP from the PoV of the block *prior* 69 // to the one that included it. 70 medianTime := node.RelativeAncestor(5).CalcPastMedianTime().Unix() 71 72 // The median time calculated from the PoV of the best block in the 73 // test chain. For unconfirmed inputs, this value will be used since 74 // the MTP will be calculated from the PoV of the yet-to-be-mined 75 // block. 76 nextMedianTime := node.CalcPastMedianTime().Unix() 77 nextBlockHeight := int32(numBlocksToActivate) + 1 78 79 // Add an additional transaction which will serve as our unconfirmed 80 // output. 81 unConfTx := &wire.MsgTx{ 82 TxOut: []*wire.TxOut{{ 83 PkScript: nil, 84 Value: 5, 85 }}, 86 } 87 unConfUtxo := wire.OutPoint{ 88 Hash: unConfTx.TxHash(), 89 Index: 0, 90 } 91 92 // Adding a utxo with a height of 0x7fffffff indicates that the output 93 // is currently unmined. 94 utxoView.AddTxOuts(btcutil.NewTx(unConfTx), 0x7fffffff) 95 96 tests := []struct { 97 tx *wire.MsgTx 98 view *UtxoViewpoint 99 mempool bool 100 want *SequenceLock 101 }{ 102 // A transaction of version one should disable sequence locks 103 // as the new sequence number semantics only apply to 104 // transactions version 2 or higher. 105 { 106 tx: &wire.MsgTx{ 107 Version: 1, 108 TxIn: []*wire.TxIn{{ 109 PreviousOutPoint: utxo, 110 Sequence: LockTimeToSequence(false, 3), 111 }}, 112 }, 113 view: utxoView, 114 want: &SequenceLock{ 115 Seconds: -1, 116 BlockHeight: -1, 117 }, 118 }, 119 // A transaction with a single input with max sequence number. 120 // This sequence number has the high bit set, so sequence locks 121 // should be disabled. 122 { 123 tx: &wire.MsgTx{ 124 Version: 2, 125 TxIn: []*wire.TxIn{{ 126 PreviousOutPoint: utxo, 127 Sequence: wire.MaxTxInSequenceNum, 128 }}, 129 }, 130 view: utxoView, 131 want: &SequenceLock{ 132 Seconds: -1, 133 BlockHeight: -1, 134 }, 135 }, 136 // A transaction with a single input whose lock time is 137 // expressed in seconds. However, the specified lock time is 138 // below the required floor for time based lock times since 139 // they have time granularity of 512 seconds. As a result, the 140 // seconds lock-time should be just before the median time of 141 // the targeted block. 142 { 143 tx: &wire.MsgTx{ 144 Version: 2, 145 TxIn: []*wire.TxIn{{ 146 PreviousOutPoint: utxo, 147 Sequence: LockTimeToSequence(true, 2), 148 }}, 149 }, 150 view: utxoView, 151 want: &SequenceLock{ 152 Seconds: medianTime - 1, 153 BlockHeight: -1, 154 }, 155 }, 156 // A transaction with a single input whose lock time is 157 // expressed in seconds. The number of seconds should be 1023 158 // seconds after the median past time of the last block in the 159 // chain. 160 { 161 tx: &wire.MsgTx{ 162 Version: 2, 163 TxIn: []*wire.TxIn{{ 164 PreviousOutPoint: utxo, 165 Sequence: LockTimeToSequence(true, 1024), 166 }}, 167 }, 168 view: utxoView, 169 want: &SequenceLock{ 170 Seconds: medianTime + 1023, 171 BlockHeight: -1, 172 }, 173 }, 174 // A transaction with multiple inputs. The first input has a 175 // lock time expressed in seconds. The second input has a 176 // sequence lock in blocks with a value of 4. The last input 177 // has a sequence number with a value of 5, but has the disable 178 // bit set. So the first lock should be selected as it's the 179 // latest lock that isn't disabled. 180 { 181 tx: &wire.MsgTx{ 182 Version: 2, 183 TxIn: []*wire.TxIn{{ 184 PreviousOutPoint: utxo, 185 Sequence: LockTimeToSequence(true, 2560), 186 }, { 187 PreviousOutPoint: utxo, 188 Sequence: LockTimeToSequence(false, 4), 189 }, { 190 PreviousOutPoint: utxo, 191 Sequence: LockTimeToSequence(false, 5) | 192 wire.SequenceLockTimeDisabled, 193 }}, 194 }, 195 view: utxoView, 196 want: &SequenceLock{ 197 Seconds: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1, 198 BlockHeight: prevUtxoHeight + 3, 199 }, 200 }, 201 // Transaction with a single input. The input's sequence number 202 // encodes a relative lock-time in blocks (3 blocks). The 203 // sequence lock should have a value of -1 for seconds, but a 204 // height of 2 meaning it can be included at height 3. 205 { 206 tx: &wire.MsgTx{ 207 Version: 2, 208 TxIn: []*wire.TxIn{{ 209 PreviousOutPoint: utxo, 210 Sequence: LockTimeToSequence(false, 3), 211 }}, 212 }, 213 view: utxoView, 214 want: &SequenceLock{ 215 Seconds: -1, 216 BlockHeight: prevUtxoHeight + 2, 217 }, 218 }, 219 // A transaction with two inputs with lock times expressed in 220 // seconds. The selected sequence lock value for seconds should 221 // be the time further in the future. 222 { 223 tx: &wire.MsgTx{ 224 Version: 2, 225 TxIn: []*wire.TxIn{{ 226 PreviousOutPoint: utxo, 227 Sequence: LockTimeToSequence(true, 5120), 228 }, { 229 PreviousOutPoint: utxo, 230 Sequence: LockTimeToSequence(true, 2560), 231 }}, 232 }, 233 view: utxoView, 234 want: &SequenceLock{ 235 Seconds: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1, 236 BlockHeight: -1, 237 }, 238 }, 239 // A transaction with two inputs with lock times expressed in 240 // blocks. The selected sequence lock value for blocks should 241 // be the height further in the future, so a height of 10 242 // indicating it can be included at height 11. 243 { 244 tx: &wire.MsgTx{ 245 Version: 2, 246 TxIn: []*wire.TxIn{{ 247 PreviousOutPoint: utxo, 248 Sequence: LockTimeToSequence(false, 1), 249 }, { 250 PreviousOutPoint: utxo, 251 Sequence: LockTimeToSequence(false, 11), 252 }}, 253 }, 254 view: utxoView, 255 want: &SequenceLock{ 256 Seconds: -1, 257 BlockHeight: prevUtxoHeight + 10, 258 }, 259 }, 260 // A transaction with multiple inputs. Two inputs are time 261 // based, and the other two are block based. The lock lying 262 // further into the future for both inputs should be chosen. 263 { 264 tx: &wire.MsgTx{ 265 Version: 2, 266 TxIn: []*wire.TxIn{{ 267 PreviousOutPoint: utxo, 268 Sequence: LockTimeToSequence(true, 2560), 269 }, { 270 PreviousOutPoint: utxo, 271 Sequence: LockTimeToSequence(true, 6656), 272 }, { 273 PreviousOutPoint: utxo, 274 Sequence: LockTimeToSequence(false, 3), 275 }, { 276 PreviousOutPoint: utxo, 277 Sequence: LockTimeToSequence(false, 9), 278 }}, 279 }, 280 view: utxoView, 281 want: &SequenceLock{ 282 Seconds: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1, 283 BlockHeight: prevUtxoHeight + 8, 284 }, 285 }, 286 // A transaction with a single unconfirmed input. As the input 287 // is confirmed, the height of the input should be interpreted 288 // as the height of the *next* block. So, a 2 block relative 289 // lock means the sequence lock should be for 1 block after the 290 // *next* block height, indicating it can be included 2 blocks 291 // after that. 292 { 293 tx: &wire.MsgTx{ 294 Version: 2, 295 TxIn: []*wire.TxIn{{ 296 PreviousOutPoint: unConfUtxo, 297 Sequence: LockTimeToSequence(false, 2), 298 }}, 299 }, 300 view: utxoView, 301 mempool: true, 302 want: &SequenceLock{ 303 Seconds: -1, 304 BlockHeight: nextBlockHeight + 1, 305 }, 306 }, 307 // A transaction with a single unconfirmed input. The input has 308 // a time based lock, so the lock time should be based off the 309 // MTP of the *next* block. 310 { 311 tx: &wire.MsgTx{ 312 Version: 2, 313 TxIn: []*wire.TxIn{{ 314 PreviousOutPoint: unConfUtxo, 315 Sequence: LockTimeToSequence(true, 1024), 316 }}, 317 }, 318 view: utxoView, 319 mempool: true, 320 want: &SequenceLock{ 321 Seconds: nextMedianTime + 1023, 322 BlockHeight: -1, 323 }, 324 }, 325 } 326 327 t.Logf("Running %v SequenceLock tests", len(tests)) 328 for i, test := range tests { 329 utilTx := btcutil.NewTx(test.tx) 330 seqLock, err := chain.CalcSequenceLock(utilTx, test.view, test.mempool) 331 if err != nil { 332 t.Fatalf("test #%d, unable to calc sequence lock: %v", i, err) 333 } 334 335 if seqLock.Seconds != test.want.Seconds { 336 t.Fatalf("test #%d got %v seconds want %v seconds", 337 i, seqLock.Seconds, test.want.Seconds) 338 } 339 if seqLock.BlockHeight != test.want.BlockHeight { 340 t.Fatalf("test #%d got height of %v want height of %v ", 341 i, seqLock.BlockHeight, test.want.BlockHeight) 342 } 343 } 344 } 345 346 // nodeHashes is a convenience function that returns the hashes for all of the 347 // passed indexes of the provided nodes. It is used to construct expected hash 348 // slices in the tests. 349 func nodeHashes(nodes []*blockNode, indexes ...int) []chainhash.Hash { 350 hashes := make([]chainhash.Hash, 0, len(indexes)) 351 for _, idx := range indexes { 352 hashes = append(hashes, nodes[idx].hash) 353 } 354 return hashes 355 } 356 357 // nodeHeaders is a convenience function that returns the headers for all of 358 // the passed indexes of the provided nodes. It is used to construct expected 359 // located headers in the tests. 360 func nodeHeaders(nodes []*blockNode, indexes ...int) []wire.BlockHeader { 361 headers := make([]wire.BlockHeader, 0, len(indexes)) 362 for _, idx := range indexes { 363 headers = append(headers, nodes[idx].Header()) 364 } 365 return headers 366 } 367 368 // TestLocateInventory ensures that locating inventory via the LocateHeaders and 369 // LocateBlocks functions behaves as expected. 370 func TestLocateInventory(t *testing.T) { 371 // Construct a synthetic block chain with a block index consisting of 372 // the following structure. 373 // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 374 // \-> 16a -> 17a 375 tip := tstTip 376 chain := newFakeChain(&chaincfg.MainNetParams) 377 branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18) 378 branch1Nodes := chainedNodes(branch0Nodes[14], 2) 379 for _, node := range branch0Nodes { 380 chain.index.AddNode(node) 381 } 382 for _, node := range branch1Nodes { 383 chain.index.AddNode(node) 384 } 385 chain.bestChain.SetTip(tip(branch0Nodes)) 386 387 // Create chain views for different branches of the overall chain to 388 // simulate a local and remote node on different parts of the chain. 389 localView := newChainView(tip(branch0Nodes)) 390 remoteView := newChainView(tip(branch1Nodes)) 391 392 // Create a chain view for a completely unrelated block chain to 393 // simulate a remote node on a totally different chain. 394 unrelatedBranchNodes := chainedNodes(nil, 5) 395 unrelatedView := newChainView(tip(unrelatedBranchNodes)) 396 397 tests := []struct { 398 name string 399 locator BlockLocator // locator for requested inventory 400 hashStop chainhash.Hash // stop hash for locator 401 maxAllowed uint32 // max to locate, 0 = wire const 402 headers []wire.BlockHeader // expected located headers 403 hashes []chainhash.Hash // expected located hashes 404 }{ 405 { 406 // Empty block locators and unknown stop hash. No 407 // inventory should be located. 408 name: "no locators, no stop", 409 locator: nil, 410 hashStop: chainhash.Hash{}, 411 headers: nil, 412 hashes: nil, 413 }, 414 { 415 // Empty block locators and stop hash in side chain. 416 // The expected result is the requested block. 417 name: "no locators, stop in side", 418 locator: nil, 419 hashStop: tip(branch1Nodes).hash, 420 headers: nodeHeaders(branch1Nodes, 1), 421 hashes: nodeHashes(branch1Nodes, 1), 422 }, 423 { 424 // Empty block locators and stop hash in main chain. 425 // The expected result is the requested block. 426 name: "no locators, stop in main", 427 locator: nil, 428 hashStop: branch0Nodes[12].hash, 429 headers: nodeHeaders(branch0Nodes, 12), 430 hashes: nodeHashes(branch0Nodes, 12), 431 }, 432 { 433 // Locators based on remote being on side chain and a 434 // stop hash local node doesn't know about. The 435 // expected result is the blocks after the fork point in 436 // the main chain and the stop hash has no effect. 437 name: "remote side chain, unknown stop", 438 locator: remoteView.BlockLocator(nil), 439 hashStop: chainhash.Hash{0x01}, 440 headers: nodeHeaders(branch0Nodes, 15, 16, 17), 441 hashes: nodeHashes(branch0Nodes, 15, 16, 17), 442 }, 443 { 444 // Locators based on remote being on side chain and a 445 // stop hash in side chain. The expected result is the 446 // blocks after the fork point in the main chain and the 447 // stop hash has no effect. 448 name: "remote side chain, stop in side", 449 locator: remoteView.BlockLocator(nil), 450 hashStop: tip(branch1Nodes).hash, 451 headers: nodeHeaders(branch0Nodes, 15, 16, 17), 452 hashes: nodeHashes(branch0Nodes, 15, 16, 17), 453 }, 454 { 455 // Locators based on remote being on side chain and a 456 // stop hash in main chain, but before fork point. The 457 // expected result is the blocks after the fork point in 458 // the main chain and the stop hash has no effect. 459 name: "remote side chain, stop in main before", 460 locator: remoteView.BlockLocator(nil), 461 hashStop: branch0Nodes[13].hash, 462 headers: nodeHeaders(branch0Nodes, 15, 16, 17), 463 hashes: nodeHashes(branch0Nodes, 15, 16, 17), 464 }, 465 { 466 // Locators based on remote being on side chain and a 467 // stop hash in main chain, but exactly at the fork 468 // point. The expected result is the blocks after the 469 // fork point in the main chain and the stop hash has no 470 // effect. 471 name: "remote side chain, stop in main exact", 472 locator: remoteView.BlockLocator(nil), 473 hashStop: branch0Nodes[14].hash, 474 headers: nodeHeaders(branch0Nodes, 15, 16, 17), 475 hashes: nodeHashes(branch0Nodes, 15, 16, 17), 476 }, 477 { 478 // Locators based on remote being on side chain and a 479 // stop hash in main chain just after the fork point. 480 // The expected result is the blocks after the fork 481 // point in the main chain up to and including the stop 482 // hash. 483 name: "remote side chain, stop in main after", 484 locator: remoteView.BlockLocator(nil), 485 hashStop: branch0Nodes[15].hash, 486 headers: nodeHeaders(branch0Nodes, 15), 487 hashes: nodeHashes(branch0Nodes, 15), 488 }, 489 { 490 // Locators based on remote being on side chain and a 491 // stop hash in main chain some time after the fork 492 // point. The expected result is the blocks after the 493 // fork point in the main chain up to and including the 494 // stop hash. 495 name: "remote side chain, stop in main after more", 496 locator: remoteView.BlockLocator(nil), 497 hashStop: branch0Nodes[16].hash, 498 headers: nodeHeaders(branch0Nodes, 15, 16), 499 hashes: nodeHashes(branch0Nodes, 15, 16), 500 }, 501 { 502 // Locators based on remote being on main chain in the 503 // past and a stop hash local node doesn't know about. 504 // The expected result is the blocks after the known 505 // point in the main chain and the stop hash has no 506 // effect. 507 name: "remote main chain past, unknown stop", 508 locator: localView.BlockLocator(branch0Nodes[12]), 509 hashStop: chainhash.Hash{0x01}, 510 headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17), 511 hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17), 512 }, 513 { 514 // Locators based on remote being on main chain in the 515 // past and a stop hash in a side chain. The expected 516 // result is the blocks after the known point in the 517 // main chain and the stop hash has no effect. 518 name: "remote main chain past, stop in side", 519 locator: localView.BlockLocator(branch0Nodes[12]), 520 hashStop: tip(branch1Nodes).hash, 521 headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17), 522 hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17), 523 }, 524 { 525 // Locators based on remote being on main chain in the 526 // past and a stop hash in the main chain before that 527 // point. The expected result is the blocks after the 528 // known point in the main chain and the stop hash has 529 // no effect. 530 name: "remote main chain past, stop in main before", 531 locator: localView.BlockLocator(branch0Nodes[12]), 532 hashStop: branch0Nodes[11].hash, 533 headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17), 534 hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17), 535 }, 536 { 537 // Locators based on remote being on main chain in the 538 // past and a stop hash in the main chain exactly at that 539 // point. The expected result is the blocks after the 540 // known point in the main chain and the stop hash has 541 // no effect. 542 name: "remote main chain past, stop in main exact", 543 locator: localView.BlockLocator(branch0Nodes[12]), 544 hashStop: branch0Nodes[12].hash, 545 headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17), 546 hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17), 547 }, 548 { 549 // Locators based on remote being on main chain in the 550 // past and a stop hash in the main chain just after 551 // that point. The expected result is the blocks after 552 // the known point in the main chain and the stop hash 553 // has no effect. 554 name: "remote main chain past, stop in main after", 555 locator: localView.BlockLocator(branch0Nodes[12]), 556 hashStop: branch0Nodes[13].hash, 557 headers: nodeHeaders(branch0Nodes, 13), 558 hashes: nodeHashes(branch0Nodes, 13), 559 }, 560 { 561 // Locators based on remote being on main chain in the 562 // past and a stop hash in the main chain some time 563 // after that point. The expected result is the blocks 564 // after the known point in the main chain and the stop 565 // hash has no effect. 566 name: "remote main chain past, stop in main after more", 567 locator: localView.BlockLocator(branch0Nodes[12]), 568 hashStop: branch0Nodes[15].hash, 569 headers: nodeHeaders(branch0Nodes, 13, 14, 15), 570 hashes: nodeHashes(branch0Nodes, 13, 14, 15), 571 }, 572 { 573 // Locators based on remote being at exactly the same 574 // point in the main chain and a stop hash local node 575 // doesn't know about. The expected result is no 576 // located inventory. 577 name: "remote main chain same, unknown stop", 578 locator: localView.BlockLocator(nil), 579 hashStop: chainhash.Hash{0x01}, 580 headers: nil, 581 hashes: nil, 582 }, 583 { 584 // Locators based on remote being at exactly the same 585 // point in the main chain and a stop hash at exactly 586 // the same point. The expected result is no located 587 // inventory. 588 name: "remote main chain same, stop same point", 589 locator: localView.BlockLocator(nil), 590 hashStop: tip(branch0Nodes).hash, 591 headers: nil, 592 hashes: nil, 593 }, 594 { 595 // Locators from remote that don't include any blocks 596 // the local node knows. This would happen if the 597 // remote node is on a completely separate chain that 598 // isn't rooted with the same genesis block. The 599 // expected result is the blocks after the genesis 600 // block. 601 name: "remote unrelated chain", 602 locator: unrelatedView.BlockLocator(nil), 603 hashStop: chainhash.Hash{}, 604 headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, 605 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), 606 hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, 607 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), 608 }, 609 { 610 // Locators from remote for second block in main chain 611 // and no stop hash, but with an overridden max limit. 612 // The expected result is the blocks after the second 613 // block limited by the max. 614 name: "remote genesis", 615 locator: locatorHashes(branch0Nodes, 0), 616 hashStop: chainhash.Hash{}, 617 maxAllowed: 3, 618 headers: nodeHeaders(branch0Nodes, 1, 2, 3), 619 hashes: nodeHashes(branch0Nodes, 1, 2, 3), 620 }, 621 { 622 // Poorly formed locator. 623 // 624 // Locator from remote that only includes a single 625 // block on a side chain the local node knows. The 626 // expected result is the blocks after the genesis 627 // block since even though the block is known, it is on 628 // a side chain and there are no more locators to find 629 // the fork point. 630 name: "weak locator, single known side block", 631 locator: locatorHashes(branch1Nodes, 1), 632 hashStop: chainhash.Hash{}, 633 headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, 634 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), 635 hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, 636 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), 637 }, 638 { 639 // Poorly formed locator. 640 // 641 // Locator from remote that only includes multiple 642 // blocks on a side chain the local node knows however 643 // none in the main chain. The expected result is the 644 // blocks after the genesis block since even though the 645 // blocks are known, they are all on a side chain and 646 // there are no more locators to find the fork point. 647 name: "weak locator, multiple known side blocks", 648 locator: locatorHashes(branch1Nodes, 1), 649 hashStop: chainhash.Hash{}, 650 headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, 651 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), 652 hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, 653 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), 654 }, 655 { 656 // Poorly formed locator. 657 // 658 // Locator from remote that only includes multiple 659 // blocks on a side chain the local node knows however 660 // none in the main chain but includes a stop hash in 661 // the main chain. The expected result is the blocks 662 // after the genesis block up to the stop hash since 663 // even though the blocks are known, they are all on a 664 // side chain and there are no more locators to find the 665 // fork point. 666 name: "weak locator, multiple known side blocks, stop in main", 667 locator: locatorHashes(branch1Nodes, 1), 668 hashStop: branch0Nodes[5].hash, 669 headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5), 670 hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5), 671 }, 672 } 673 for _, test := range tests { 674 // Ensure the expected headers are located. 675 var headers []wire.BlockHeader 676 if test.maxAllowed != 0 { 677 // Need to use the unexported function to override the 678 // max allowed for headers. 679 chain.chainLock.RLock() 680 headers = chain.locateHeaders(test.locator, 681 &test.hashStop, test.maxAllowed) 682 chain.chainLock.RUnlock() 683 } else { 684 headers = chain.LocateHeaders(test.locator, 685 &test.hashStop) 686 } 687 if !reflect.DeepEqual(headers, test.headers) { 688 t.Errorf("%s: unxpected headers -- got %v, want %v", 689 test.name, headers, test.headers) 690 continue 691 } 692 693 // Ensure the expected block hashes are located. 694 maxAllowed := uint32(wire.MaxBlocksPerMsg) 695 if test.maxAllowed != 0 { 696 maxAllowed = test.maxAllowed 697 } 698 hashes := chain.LocateBlocks(test.locator, &test.hashStop, 699 maxAllowed) 700 if !reflect.DeepEqual(hashes, test.hashes) { 701 t.Errorf("%s: unxpected hashes -- got %v, want %v", 702 test.name, hashes, test.hashes) 703 continue 704 } 705 } 706 } 707 708 // TestHeightToHashRange ensures that fetching a range of block hashes by start 709 // height and end hash works as expected. 710 func TestHeightToHashRange(t *testing.T) { 711 // Construct a synthetic block chain with a block index consisting of 712 // the following structure. 713 // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 714 // \-> 16a -> 17a -> 18a (unvalidated) 715 tip := tstTip 716 chain := newFakeChain(&chaincfg.MainNetParams) 717 branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18) 718 branch1Nodes := chainedNodes(branch0Nodes[14], 3) 719 for _, node := range branch0Nodes { 720 chain.index.SetStatusFlags(node, statusValid) 721 chain.index.AddNode(node) 722 } 723 for _, node := range branch1Nodes { 724 if node.height < 18 { 725 chain.index.SetStatusFlags(node, statusValid) 726 } 727 chain.index.AddNode(node) 728 } 729 chain.bestChain.SetTip(tip(branch0Nodes)) 730 731 tests := []struct { 732 name string 733 startHeight int32 // locator for requested inventory 734 endHash chainhash.Hash // stop hash for locator 735 maxResults int // max to locate, 0 = wire const 736 hashes []chainhash.Hash // expected located hashes 737 expectError bool 738 }{ 739 { 740 name: "blocks below tip", 741 startHeight: 11, 742 endHash: branch0Nodes[14].hash, 743 maxResults: 10, 744 hashes: nodeHashes(branch0Nodes, 10, 11, 12, 13, 14), 745 }, 746 { 747 name: "blocks on main chain", 748 startHeight: 15, 749 endHash: branch0Nodes[17].hash, 750 maxResults: 10, 751 hashes: nodeHashes(branch0Nodes, 14, 15, 16, 17), 752 }, 753 { 754 name: "blocks on stale chain", 755 startHeight: 15, 756 endHash: branch1Nodes[1].hash, 757 maxResults: 10, 758 hashes: append(nodeHashes(branch0Nodes, 14), 759 nodeHashes(branch1Nodes, 0, 1)...), 760 }, 761 { 762 name: "invalid start height", 763 startHeight: 19, 764 endHash: branch0Nodes[17].hash, 765 maxResults: 10, 766 expectError: true, 767 }, 768 { 769 name: "too many results", 770 startHeight: 1, 771 endHash: branch0Nodes[17].hash, 772 maxResults: 10, 773 expectError: true, 774 }, 775 { 776 name: "unvalidated block", 777 startHeight: 15, 778 endHash: branch1Nodes[2].hash, 779 maxResults: 10, 780 expectError: true, 781 }, 782 } 783 for _, test := range tests { 784 hashes, err := chain.HeightToHashRange(test.startHeight, &test.endHash, 785 test.maxResults) 786 if err != nil { 787 if !test.expectError { 788 t.Errorf("%s: unexpected error: %v", test.name, err) 789 } 790 continue 791 } 792 793 if !reflect.DeepEqual(hashes, test.hashes) { 794 t.Errorf("%s: unxpected hashes -- got %v, want %v", 795 test.name, hashes, test.hashes) 796 } 797 } 798 } 799 800 // TestIntervalBlockHashes ensures that fetching block hashes at specified 801 // intervals by end hash works as expected. 802 func TestIntervalBlockHashes(t *testing.T) { 803 // Construct a synthetic block chain with a block index consisting of 804 // the following structure. 805 // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 806 // \-> 16a -> 17a -> 18a (unvalidated) 807 tip := tstTip 808 chain := newFakeChain(&chaincfg.MainNetParams) 809 branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18) 810 branch1Nodes := chainedNodes(branch0Nodes[14], 3) 811 for _, node := range branch0Nodes { 812 chain.index.SetStatusFlags(node, statusValid) 813 chain.index.AddNode(node) 814 } 815 for _, node := range branch1Nodes { 816 if node.height < 18 { 817 chain.index.SetStatusFlags(node, statusValid) 818 } 819 chain.index.AddNode(node) 820 } 821 chain.bestChain.SetTip(tip(branch0Nodes)) 822 823 tests := []struct { 824 name string 825 endHash chainhash.Hash 826 interval int 827 hashes []chainhash.Hash 828 expectError bool 829 }{ 830 { 831 name: "blocks on main chain", 832 endHash: branch0Nodes[17].hash, 833 interval: 8, 834 hashes: nodeHashes(branch0Nodes, 7, 15), 835 }, 836 { 837 name: "blocks on stale chain", 838 endHash: branch1Nodes[1].hash, 839 interval: 8, 840 hashes: append(nodeHashes(branch0Nodes, 7), 841 nodeHashes(branch1Nodes, 0)...), 842 }, 843 { 844 name: "no results", 845 endHash: branch0Nodes[17].hash, 846 interval: 20, 847 hashes: []chainhash.Hash{}, 848 }, 849 { 850 name: "unvalidated block", 851 endHash: branch1Nodes[2].hash, 852 interval: 8, 853 expectError: true, 854 }, 855 } 856 for _, test := range tests { 857 hashes, err := chain.IntervalBlockHashes(&test.endHash, test.interval) 858 if err != nil { 859 if !test.expectError { 860 t.Errorf("%s: unexpected error: %v", test.name, err) 861 } 862 continue 863 } 864 865 if !reflect.DeepEqual(hashes, test.hashes) { 866 t.Errorf("%s: unxpected hashes -- got %v, want %v", 867 test.name, hashes, test.hashes) 868 } 869 } 870 }