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.
12 "github.com/btcsuite/btcd/chaincfg"
13 "github.com/btcsuite/btcd/chaincfg/chainhash"
14 "github.com/btcsuite/btcd/wire"
15 "github.com/btcsuite/btcutil"
18 // TestHaveBlock tests the HaveBlock API to ensure proper functionality.
19 func TestHaveBlock(t *testing.T) {
20 // Load up blocks such that there is a side chain.
21 // (genesis block) -> 1 -> 2 -> 3 -> 4
23 testFiles := []string{
28 var blocks []*btcutil.Block
29 for _, file := range testFiles {
30 blockTmp, err := loadBlocks(file)
32 t.Errorf("Error loading file: %v\n", err)
35 blocks = append(blocks, blockTmp...)
38 // Create a new database and chain instance to run tests against.
39 chain, teardownFunc, err := chainSetup("haveblock",
40 &chaincfg.MainNetParams)
42 t.Errorf("Failed to setup chain instance: %v", err)
47 // Since we're not dealing with the real block chain, set the coinbase
49 chain.TstSetCoinbaseMaturity(1)
51 for i := 1; i < len(blocks); i++ {
52 _, isOrphan, err := chain.ProcessBlock(blocks[i], BFNone)
54 t.Errorf("ProcessBlock fail on block %v: %v\n", i, err)
58 t.Errorf("ProcessBlock incorrectly returned block %v "+
64 // Insert an orphan block.
65 _, isOrphan, err := chain.ProcessBlock(btcutil.NewBlock(&Block100000),
68 t.Errorf("Unable to process block: %v", err)
72 t.Errorf("ProcessBlock indicated block is an not orphan when " +
81 // Genesis block should be present (in the main chain).
82 {hash: chaincfg.MainNetParams.GenesisHash.String(), want: true},
84 // Block 3a should be present (on a side chain).
85 {hash: "00000000474284d20067a4d33f6a02284e6ef70764a3a26d6a5b9df52ef663dd", want: true},
87 // Block 100000 should be present (as an orphan).
88 {hash: "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506", want: true},
90 // Random hashes should not be available.
91 {hash: "123", want: false},
94 for i, test := range tests {
95 hash, err := chainhash.NewHashFromStr(test.hash)
97 t.Errorf("NewHashFromStr: %v", err)
101 result, err := chain.HaveBlock(hash)
103 t.Errorf("HaveBlock #%d unexpected error: %v", i, err)
106 if result != test.want {
107 t.Errorf("HaveBlock #%d got %v want %v", i, result,
114 // TestCalcSequenceLock tests the LockTimeToSequence function, and the
115 // CalcSequenceLock method of a Chain instance. The tests exercise several
116 // combinations of inputs to the CalcSequenceLock function in order to ensure
117 // the returned SequenceLocks are correct for each test instance.
118 func TestCalcSequenceLock(t *testing.T) {
119 netParams := &chaincfg.SimNetParams
121 // We need to activate CSV in order to test the processing logic, so
122 // manually craft the block version that's used to signal the soft-fork
124 csvBit := netParams.Deployments[chaincfg.DeploymentCSV].BitNumber
125 blockVersion := int32(0x20000000 | (uint32(1) << csvBit))
127 // Generate enough synthetic blocks to activate CSV.
128 chain := newFakeChain(netParams)
129 node := chain.bestChain.Tip()
130 blockTime := node.Header().Timestamp
131 numBlocksToActivate := (netParams.MinerConfirmationWindow * 3)
132 for i := uint32(0); i < numBlocksToActivate; i++ {
133 blockTime = blockTime.Add(time.Second)
134 node = newFakeNode(node, blockVersion, 0, blockTime)
135 chain.index.AddNode(node)
136 chain.bestChain.SetTip(node)
139 // Create a utxo view with a fake utxo for the inputs used in the
140 // transactions created below. This utxo is added such that it has an
142 targetTx := btcutil.NewTx(&wire.MsgTx{
143 TxOut: []*wire.TxOut{{
148 utxoView := NewUtxoViewpoint()
149 utxoView.AddTxOuts(targetTx, int32(numBlocksToActivate)-4)
150 utxoView.SetBestHash(&node.hash)
152 // Create a utxo that spends the fake utxo created above for use in the
153 // transactions created in the tests. It has an age of 4 blocks. Note
154 // that the sequence lock heights are always calculated from the same
155 // point of view that they were originally calculated from for a given
156 // utxo. That is to say, the height prior to it.
157 utxo := wire.OutPoint{
158 Hash: *targetTx.Hash(),
161 prevUtxoHeight := int32(numBlocksToActivate) - 4
163 // Obtain the median time past from the PoV of the input created above.
164 // The MTP for the input is the MTP from the PoV of the block *prior*
165 // to the one that included it.
166 medianTime := node.RelativeAncestor(5).CalcPastMedianTime().Unix()
168 // The median time calculated from the PoV of the best block in the
169 // test chain. For unconfirmed inputs, this value will be used since
170 // the MTP will be calculated from the PoV of the yet-to-be-mined
172 nextMedianTime := node.CalcPastMedianTime().Unix()
173 nextBlockHeight := int32(numBlocksToActivate) + 1
175 // Add an additional transaction which will serve as our unconfirmed
177 unConfTx := &wire.MsgTx{
178 TxOut: []*wire.TxOut{{
183 unConfUtxo := wire.OutPoint{
184 Hash: unConfTx.TxHash(),
188 // Adding a utxo with a height of 0x7fffffff indicates that the output
189 // is currently unmined.
190 utxoView.AddTxOuts(btcutil.NewTx(unConfTx), 0x7fffffff)
198 // A transaction of version one should disable sequence locks
199 // as the new sequence number semantics only apply to
200 // transactions version 2 or higher.
205 PreviousOutPoint: utxo,
206 Sequence: LockTimeToSequence(false, 3),
215 // A transaction with a single input with max sequence number.
216 // This sequence number has the high bit set, so sequence locks
217 // should be disabled.
222 PreviousOutPoint: utxo,
223 Sequence: wire.MaxTxInSequenceNum,
232 // A transaction with a single input whose lock time is
233 // expressed in seconds. However, the specified lock time is
234 // below the required floor for time based lock times since
235 // they have time granularity of 512 seconds. As a result, the
236 // seconds lock-time should be just before the median time of
237 // the targeted block.
242 PreviousOutPoint: utxo,
243 Sequence: LockTimeToSequence(true, 2),
248 Seconds: medianTime - 1,
252 // A transaction with a single input whose lock time is
253 // expressed in seconds. The number of seconds should be 1023
254 // seconds after the median past time of the last block in the
260 PreviousOutPoint: utxo,
261 Sequence: LockTimeToSequence(true, 1024),
266 Seconds: medianTime + 1023,
270 // A transaction with multiple inputs. The first input has a
271 // lock time expressed in seconds. The second input has a
272 // sequence lock in blocks with a value of 4. The last input
273 // has a sequence number with a value of 5, but has the disable
274 // bit set. So the first lock should be selected as it's the
275 // latest lock that isn't disabled.
280 PreviousOutPoint: utxo,
281 Sequence: LockTimeToSequence(true, 2560),
283 PreviousOutPoint: utxo,
284 Sequence: LockTimeToSequence(false, 4),
286 PreviousOutPoint: utxo,
287 Sequence: LockTimeToSequence(false, 5) |
288 wire.SequenceLockTimeDisabled,
293 Seconds: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1,
294 BlockHeight: prevUtxoHeight + 3,
297 // Transaction with a single input. The input's sequence number
298 // encodes a relative lock-time in blocks (3 blocks). The
299 // sequence lock should have a value of -1 for seconds, but a
300 // height of 2 meaning it can be included at height 3.
305 PreviousOutPoint: utxo,
306 Sequence: LockTimeToSequence(false, 3),
312 BlockHeight: prevUtxoHeight + 2,
315 // A transaction with two inputs with lock times expressed in
316 // seconds. The selected sequence lock value for seconds should
317 // be the time further in the future.
322 PreviousOutPoint: utxo,
323 Sequence: LockTimeToSequence(true, 5120),
325 PreviousOutPoint: utxo,
326 Sequence: LockTimeToSequence(true, 2560),
331 Seconds: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1,
335 // A transaction with two inputs with lock times expressed in
336 // blocks. The selected sequence lock value for blocks should
337 // be the height further in the future, so a height of 10
338 // indicating it can be included at height 11.
343 PreviousOutPoint: utxo,
344 Sequence: LockTimeToSequence(false, 1),
346 PreviousOutPoint: utxo,
347 Sequence: LockTimeToSequence(false, 11),
353 BlockHeight: prevUtxoHeight + 10,
356 // A transaction with multiple inputs. Two inputs are time
357 // based, and the other two are block based. The lock lying
358 // further into the future for both inputs should be chosen.
363 PreviousOutPoint: utxo,
364 Sequence: LockTimeToSequence(true, 2560),
366 PreviousOutPoint: utxo,
367 Sequence: LockTimeToSequence(true, 6656),
369 PreviousOutPoint: utxo,
370 Sequence: LockTimeToSequence(false, 3),
372 PreviousOutPoint: utxo,
373 Sequence: LockTimeToSequence(false, 9),
378 Seconds: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1,
379 BlockHeight: prevUtxoHeight + 8,
382 // A transaction with a single unconfirmed input. As the input
383 // is confirmed, the height of the input should be interpreted
384 // as the height of the *next* block. So, a 2 block relative
385 // lock means the sequence lock should be for 1 block after the
386 // *next* block height, indicating it can be included 2 blocks
392 PreviousOutPoint: unConfUtxo,
393 Sequence: LockTimeToSequence(false, 2),
400 BlockHeight: nextBlockHeight + 1,
403 // A transaction with a single unconfirmed input. The input has
404 // a time based lock, so the lock time should be based off the
405 // MTP of the *next* block.
410 PreviousOutPoint: unConfUtxo,
411 Sequence: LockTimeToSequence(true, 1024),
417 Seconds: nextMedianTime + 1023,
423 t.Logf("Running %v SequenceLock tests", len(tests))
424 for i, test := range tests {
425 utilTx := btcutil.NewTx(test.tx)
426 seqLock, err := chain.CalcSequenceLock(utilTx, test.view, test.mempool)
428 t.Fatalf("test #%d, unable to calc sequence lock: %v", i, err)
431 if seqLock.Seconds != test.want.Seconds {
432 t.Fatalf("test #%d got %v seconds want %v seconds",
433 i, seqLock.Seconds, test.want.Seconds)
435 if seqLock.BlockHeight != test.want.BlockHeight {
436 t.Fatalf("test #%d got height of %v want height of %v ",
437 i, seqLock.BlockHeight, test.want.BlockHeight)
442 // nodeHashes is a convenience function that returns the hashes for all of the
443 // passed indexes of the provided nodes. It is used to construct expected hash
444 // slices in the tests.
445 func nodeHashes(nodes []*blockNode, indexes ...int) []chainhash.Hash {
446 hashes := make([]chainhash.Hash, 0, len(indexes))
447 for _, idx := range indexes {
448 hashes = append(hashes, nodes[idx].hash)
453 // nodeHeaders is a convenience function that returns the headers for all of
454 // the passed indexes of the provided nodes. It is used to construct expected
455 // located headers in the tests.
456 func nodeHeaders(nodes []*blockNode, indexes ...int) []wire.BlockHeader {
457 headers := make([]wire.BlockHeader, 0, len(indexes))
458 for _, idx := range indexes {
459 headers = append(headers, nodes[idx].Header())
464 // TestLocateInventory ensures that locating inventory via the LocateHeaders and
465 // LocateBlocks functions behaves as expected.
466 func TestLocateInventory(t *testing.T) {
467 // Construct a synthetic block chain with a block index consisting of
468 // the following structure.
469 // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
472 chain := newFakeChain(&chaincfg.MainNetParams)
473 branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
474 branch1Nodes := chainedNodes(branch0Nodes[14], 2)
475 for _, node := range branch0Nodes {
476 chain.index.AddNode(node)
478 for _, node := range branch1Nodes {
479 chain.index.AddNode(node)
481 chain.bestChain.SetTip(tip(branch0Nodes))
483 // Create chain views for different branches of the overall chain to
484 // simulate a local and remote node on different parts of the chain.
485 localView := newChainView(tip(branch0Nodes))
486 remoteView := newChainView(tip(branch1Nodes))
488 // Create a chain view for a completely unrelated block chain to
489 // simulate a remote node on a totally different chain.
490 unrelatedBranchNodes := chainedNodes(nil, 5)
491 unrelatedView := newChainView(tip(unrelatedBranchNodes))
495 locator BlockLocator // locator for requested inventory
496 hashStop chainhash.Hash // stop hash for locator
497 maxAllowed uint32 // max to locate, 0 = wire const
498 headers []wire.BlockHeader // expected located headers
499 hashes []chainhash.Hash // expected located hashes
502 // Empty block locators and unknown stop hash. No
503 // inventory should be located.
504 name: "no locators, no stop",
506 hashStop: chainhash.Hash{},
511 // Empty block locators and stop hash in side chain.
512 // The expected result is the requested block.
513 name: "no locators, stop in side",
515 hashStop: tip(branch1Nodes).hash,
516 headers: nodeHeaders(branch1Nodes, 1),
517 hashes: nodeHashes(branch1Nodes, 1),
520 // Empty block locators and stop hash in main chain.
521 // The expected result is the requested block.
522 name: "no locators, stop in main",
524 hashStop: branch0Nodes[12].hash,
525 headers: nodeHeaders(branch0Nodes, 12),
526 hashes: nodeHashes(branch0Nodes, 12),
529 // Locators based on remote being on side chain and a
530 // stop hash local node doesn't know about. The
531 // expected result is the blocks after the fork point in
532 // the main chain and the stop hash has no effect.
533 name: "remote side chain, unknown stop",
534 locator: remoteView.BlockLocator(nil),
535 hashStop: chainhash.Hash{0x01},
536 headers: nodeHeaders(branch0Nodes, 15, 16, 17),
537 hashes: nodeHashes(branch0Nodes, 15, 16, 17),
540 // Locators based on remote being on side chain and a
541 // stop hash in side chain. The expected result is the
542 // blocks after the fork point in the main chain and the
543 // stop hash has no effect.
544 name: "remote side chain, stop in side",
545 locator: remoteView.BlockLocator(nil),
546 hashStop: tip(branch1Nodes).hash,
547 headers: nodeHeaders(branch0Nodes, 15, 16, 17),
548 hashes: nodeHashes(branch0Nodes, 15, 16, 17),
551 // Locators based on remote being on side chain and a
552 // stop hash in main chain, but before fork point. The
553 // expected result is the blocks after the fork point in
554 // the main chain and the stop hash has no effect.
555 name: "remote side chain, stop in main before",
556 locator: remoteView.BlockLocator(nil),
557 hashStop: branch0Nodes[13].hash,
558 headers: nodeHeaders(branch0Nodes, 15, 16, 17),
559 hashes: nodeHashes(branch0Nodes, 15, 16, 17),
562 // Locators based on remote being on side chain and a
563 // stop hash in main chain, but exactly at the fork
564 // point. The expected result is the blocks after the
565 // fork point in the main chain and the stop hash has no
567 name: "remote side chain, stop in main exact",
568 locator: remoteView.BlockLocator(nil),
569 hashStop: branch0Nodes[14].hash,
570 headers: nodeHeaders(branch0Nodes, 15, 16, 17),
571 hashes: nodeHashes(branch0Nodes, 15, 16, 17),
574 // Locators based on remote being on side chain and a
575 // stop hash in main chain just after the fork point.
576 // The expected result is the blocks after the fork
577 // point in the main chain up to and including the stop
579 name: "remote side chain, stop in main after",
580 locator: remoteView.BlockLocator(nil),
581 hashStop: branch0Nodes[15].hash,
582 headers: nodeHeaders(branch0Nodes, 15),
583 hashes: nodeHashes(branch0Nodes, 15),
586 // Locators based on remote being on side chain and a
587 // stop hash in main chain some time after the fork
588 // point. The expected result is the blocks after the
589 // fork point in the main chain up to and including the
591 name: "remote side chain, stop in main after more",
592 locator: remoteView.BlockLocator(nil),
593 hashStop: branch0Nodes[16].hash,
594 headers: nodeHeaders(branch0Nodes, 15, 16),
595 hashes: nodeHashes(branch0Nodes, 15, 16),
598 // Locators based on remote being on main chain in the
599 // past and a stop hash local node doesn't know about.
600 // The expected result is the blocks after the known
601 // point in the main chain and the stop hash has no
603 name: "remote main chain past, unknown stop",
604 locator: localView.BlockLocator(branch0Nodes[12]),
605 hashStop: chainhash.Hash{0x01},
606 headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
607 hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
610 // Locators based on remote being on main chain in the
611 // past and a stop hash in a side chain. The expected
612 // result is the blocks after the known point in the
613 // main chain and the stop hash has no effect.
614 name: "remote main chain past, stop in side",
615 locator: localView.BlockLocator(branch0Nodes[12]),
616 hashStop: tip(branch1Nodes).hash,
617 headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
618 hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
621 // Locators based on remote being on main chain in the
622 // past and a stop hash in the main chain before that
623 // point. The expected result is the blocks after the
624 // known point in the main chain and the stop hash has
626 name: "remote main chain past, stop in main before",
627 locator: localView.BlockLocator(branch0Nodes[12]),
628 hashStop: branch0Nodes[11].hash,
629 headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
630 hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
633 // Locators based on remote being on main chain in the
634 // past and a stop hash in the main chain exactly at that
635 // point. The expected result is the blocks after the
636 // known point in the main chain and the stop hash has
638 name: "remote main chain past, stop in main exact",
639 locator: localView.BlockLocator(branch0Nodes[12]),
640 hashStop: branch0Nodes[12].hash,
641 headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
642 hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
645 // Locators based on remote being on main chain in the
646 // past and a stop hash in the main chain just after
647 // that point. The expected result is the blocks after
648 // the known point in the main chain and the stop hash
650 name: "remote main chain past, stop in main after",
651 locator: localView.BlockLocator(branch0Nodes[12]),
652 hashStop: branch0Nodes[13].hash,
653 headers: nodeHeaders(branch0Nodes, 13),
654 hashes: nodeHashes(branch0Nodes, 13),
657 // Locators based on remote being on main chain in the
658 // past and a stop hash in the main chain some time
659 // after that point. The expected result is the blocks
660 // after the known point in the main chain and the stop
661 // hash has no effect.
662 name: "remote main chain past, stop in main after more",
663 locator: localView.BlockLocator(branch0Nodes[12]),
664 hashStop: branch0Nodes[15].hash,
665 headers: nodeHeaders(branch0Nodes, 13, 14, 15),
666 hashes: nodeHashes(branch0Nodes, 13, 14, 15),
669 // Locators based on remote being at exactly the same
670 // point in the main chain and a stop hash local node
671 // doesn't know about. The expected result is no
672 // located inventory.
673 name: "remote main chain same, unknown stop",
674 locator: localView.BlockLocator(nil),
675 hashStop: chainhash.Hash{0x01},
680 // Locators based on remote being at exactly the same
681 // point in the main chain and a stop hash at exactly
682 // the same point. The expected result is no located
684 name: "remote main chain same, stop same point",
685 locator: localView.BlockLocator(nil),
686 hashStop: tip(branch0Nodes).hash,
691 // Locators from remote that don't include any blocks
692 // the local node knows. This would happen if the
693 // remote node is on a completely separate chain that
694 // isn't rooted with the same genesis block. The
695 // expected result is the blocks after the genesis
697 name: "remote unrelated chain",
698 locator: unrelatedView.BlockLocator(nil),
699 hashStop: chainhash.Hash{},
700 headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
701 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
702 hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
703 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
706 // Locators from remote for second block in main chain
707 // and no stop hash, but with an overridden max limit.
708 // The expected result is the blocks after the second
709 // block limited by the max.
710 name: "remote genesis",
711 locator: locatorHashes(branch0Nodes, 0),
712 hashStop: chainhash.Hash{},
714 headers: nodeHeaders(branch0Nodes, 1, 2, 3),
715 hashes: nodeHashes(branch0Nodes, 1, 2, 3),
718 // Poorly formed locator.
720 // Locator from remote that only includes a single
721 // block on a side chain the local node knows. The
722 // expected result is the blocks after the genesis
723 // block since even though the block is known, it is on
724 // a side chain and there are no more locators to find
726 name: "weak locator, single known side block",
727 locator: locatorHashes(branch1Nodes, 1),
728 hashStop: chainhash.Hash{},
729 headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
730 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
731 hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
732 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
735 // Poorly formed locator.
737 // Locator from remote that only includes multiple
738 // blocks on a side chain the local node knows however
739 // none in the main chain. The expected result is the
740 // blocks after the genesis block since even though the
741 // blocks are known, they are all on a side chain and
742 // there are no more locators to find the fork point.
743 name: "weak locator, multiple known side blocks",
744 locator: locatorHashes(branch1Nodes, 1),
745 hashStop: chainhash.Hash{},
746 headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
747 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
748 hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
749 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
752 // Poorly formed locator.
754 // Locator from remote that only includes multiple
755 // blocks on a side chain the local node knows however
756 // none in the main chain but includes a stop hash in
757 // the main chain. The expected result is the blocks
758 // after the genesis block up to the stop hash since
759 // even though the blocks are known, they are all on a
760 // side chain and there are no more locators to find the
762 name: "weak locator, multiple known side blocks, stop in main",
763 locator: locatorHashes(branch1Nodes, 1),
764 hashStop: branch0Nodes[5].hash,
765 headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5),
766 hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5),
769 for _, test := range tests {
770 // Ensure the expected headers are located.
771 var headers []wire.BlockHeader
772 if test.maxAllowed != 0 {
773 // Need to use the unexported function to override the
774 // max allowed for headers.
775 chain.chainLock.RLock()
776 headers = chain.locateHeaders(test.locator,
777 &test.hashStop, test.maxAllowed)
778 chain.chainLock.RUnlock()
780 headers = chain.LocateHeaders(test.locator,
783 if !reflect.DeepEqual(headers, test.headers) {
784 t.Errorf("%s: unxpected headers -- got %v, want %v",
785 test.name, headers, test.headers)
789 // Ensure the expected block hashes are located.
790 maxAllowed := uint32(wire.MaxBlocksPerMsg)
791 if test.maxAllowed != 0 {
792 maxAllowed = test.maxAllowed
794 hashes := chain.LocateBlocks(test.locator, &test.hashStop,
796 if !reflect.DeepEqual(hashes, test.hashes) {
797 t.Errorf("%s: unxpected hashes -- got %v, want %v",
798 test.name, hashes, test.hashes)