package blockchain import ( "github.com/p9c/p9/pkg/chaincfg" "github.com/p9c/p9/pkg/chainhash" "github.com/p9c/p9/pkg/wire" "reflect" "testing" ) // // TestHaveBlock tests the HaveBlock API to ensure proper functionality. // func TestHaveBlock(t *testing.T) { // // Load up blocks such that there is a side chain. // // (genesis block) -> 1 -> 2 -> 3 -> 4 // // \-> 3a // testFiles := []string{ // "blk_0_to_4.dat.bz2", // "blk_3A.dat.bz2", // } // var blocks []*util.Block // for _, file := range testFiles { // blockTmp, e := loadBlocks(file) // if e != nil { // t.Errorf("Error loading file: %v\n", e) // return // } // blocks = append(blocks, blockTmp...) // } // // Create a new database and chain instance to run tests against. // chain, teardownFunc, e := chainSetup("haveblock", // &chaincfg.MainNetParams) // if e != nil { // t.Errorf("Failed to setup chain instance: %v", e) // return // } // defer teardownFunc() // // Since we're not dealing with the real block chain, set the coinbase maturity to 1. // chain.TstSetCoinbaseMaturity(1) // for i := 1; i < len(blocks); i++ { // _, isOrphan, e := chain.ProcessBlock(blocks[i], BFNone, blocks[i].Height()) // if e != nil { // t.Errorf("ProcessBlock fail on block %v: %v\n", i, e) // return // } // if isOrphan { // t.Errorf("ProcessBlock incorrectly returned block %v "+ // "is an orphan\n", i) // return // } // } // // Insert an orphan block. // _, isOrphan, e := chain.ProcessBlock(util.NewBlock(&Block100000), // BFNone, 100000) // if e != nil { // t.Errorf("Unable to process block: %v", e) // return // } // if !isOrphan { // t.Errorf("ProcessBlock indicated block is an not orphan when " + // "it should be\n") // return // } // tests := []struct { // hash string // want bool // }{ // // Genesis block should be present (in the main chain). // {hash: chaincfg.MainNetParams.GenesisHash.String(), want: true}, // // Block 3a should be present (on a side chain). // {hash: "00000000474284d20067a4d33f6a02284e6ef70764a3a26d6a5b9df52ef663dd", want: true}, // // Block 100000 should be present (as an orphan). // {hash: "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506", want: true}, // // Random hashes should not be available. // {hash: "123", want: false}, // } // for i, test := range tests { // hash, e := chainhash.NewHashFromStr(test.hash) // if e != nil { // t.Errorf("NewHashFromStr: %v", e) // continue // } // result, e := chain.HaveBlock(hash) // if e != nil { // t.Errorf("HaveBlock #%d unexpected error: %v", i, e) // return // } // if result != test.want { // t.Errorf("HaveBlock #%d got %v want %v", i, result, // test.want) // continue // } // } // } // // TestCalcSequenceLock tests the LockTimeToSequence function, and the CalcSequenceLock method of a Chain instance. // // The tests exercise several combinations of inputs to the CalcSequenceLock function in order to ensure the returned // // SequenceLocks are correct for each test instance. // func TestCalcSequenceLock(t *testing.T) { // netParams := &chaincfg.SimNetParams // // We need to activate CSV in order to test the processing logic, so manually craft the block version that's used to // // signal the soft-fork activation. // csvBit := netParams.Deployments[chaincfg.DeploymentCSV].BitNumber // blockVersion := int32(0x20000000 | (uint32(1) << csvBit)) // // Generate enough synthetic blocks to activate CSV. // chain := newFakeChain(netParams) // node := chain.BestChain.Tip() // blockTime := node.Header().Timestamp // numBlocksToActivate := netParams.MinerConfirmationWindow * 3 // for i := uint32(0); i < numBlocksToActivate; i++ { // blockTime = blockTime.Add(time.Second) // node = newFakeNode(node, blockVersion, 0, blockTime) // chain.Index.AddNode(node) // chain.BestChain.SetTip(node) // } // // Create a utxo view with a fake utxo for the inputs used in the transactions created below. This utxo is added // // such that it has an age of 4 blocks. // targetTx := util.NewTx(&wire.MsgTx{ // TxOut: []*wire.TxOut{{ // PkScript: nil, // Value: 10, // }}, // }) // utxoView := NewUtxoViewpoint() // utxoView.AddTxOuts(targetTx, int32(numBlocksToActivate)-4) // utxoView.SetBestHash(&node.hash) // // Create a utxo that spends the fake utxo created above for use in the transactions created in the tests. It has an // // age of 4 blocks. Note that the sequence lock heights are always calculated from the same point of view that they // // were originally calculated from for a given utxo. That is to say, the height prior to it. // utxo := wire.OutPoint{ // Hash: *targetTx.Hash(), // Index: 0, // } // prevUtxoHeight := int32(numBlocksToActivate) - 4 // // Obtain the median time past from the PoV of the input created above. The MTP for the input is the MTP from the // // PoV of the block *prior* to the one that included it. // medianTime := node.RelativeAncestor(5).CalcPastMedianTime().Unix() // // The median time calculated from the PoV of the best block in the test chain. For unconfirmed inputs, this value // // will be used since the MTP will be calculated from the PoV of the yet-to-be-mined block. // nextMedianTime := node.CalcPastMedianTime().Unix() // nextBlockHeight := int32(numBlocksToActivate) + 1 // // Add an additional transaction which will serve as our unconfirmed output. // unConfTx := &wire.MsgTx{ // TxOut: []*wire.TxOut{{ // PkScript: nil, // Value: 5, // }}, // } // unConfUtxo := wire.OutPoint{ // Hash: unConfTx.TxHash(), // Index: 0, // } // // Adding a utxo with a height of 0x7fffffff indicates that the output is currently unmined. // utxoView.AddTxOuts(util.NewTx(unConfTx), 0x7fffffff) // tests := []struct { // tx *wire.MsgTx // view *UtxoViewpoint // mempool bool // want *SequenceLock // }{ // // A transaction of version one should disable sequence locks as the new sequence number semantics only apply to // // transactions version 2 or higher. // { // tx: &wire.MsgTx{ // Version: 1, // TxIn: []*wire.TxIn{{ // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(false, 3), // }}, // }, // view: utxoView, // want: &SequenceLock{ // Seconds: -1, // BlockHeight: -1, // }, // }, // // A transaction with a single input with max sequence number. This sequence number has the high bit set, so // // sequence locks should be disabled. // { // tx: &wire.MsgTx{ // Version: 2, // TxIn: []*wire.TxIn{{ // PreviousOutPoint: utxo, // Sequence: wire.MaxTxInSequenceNum, // }}, // }, // view: utxoView, // want: &SequenceLock{ // Seconds: -1, // BlockHeight: -1, // }, // }, // // A transaction with a single input whose lock time is expressed in seconds. However, the specified lock time // // is below the required floor for time based lock times since they have time granularity of 512 seconds. As a // // result, the seconds lock-time should be just before the median time of the targeted block. // { // tx: &wire.MsgTx{ // Version: 2, // TxIn: []*wire.TxIn{{ // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(true, 2), // }}, // }, // view: utxoView, // want: &SequenceLock{ // Seconds: medianTime - 1, // BlockHeight: -1, // }, // }, // // A transaction with a single input whose lock time is expressed in seconds. The number of seconds should be // // 1023 seconds after the median past time of the last block in the chain. // { // tx: &wire.MsgTx{ // Version: 2, // TxIn: []*wire.TxIn{{ // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(true, 1024), // }}, // }, // view: utxoView, // want: &SequenceLock{ // Seconds: medianTime + 1023, // BlockHeight: -1, // }, // }, // // A transaction with multiple inputs. The first input has a lock time expressed in seconds. The second input // // has a sequence lock in blocks with a value of 4. The last input has a sequence number with a value of 5, but // // has the disable bit set. So the first lock should be selected as it's the latest lock that isn't disabled. // { // tx: &wire.MsgTx{ // Version: 2, // TxIn: []*wire.TxIn{{ // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(true, 2560), // }, // { // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(false, 4), // }, // { // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(false, 5) | // wire.SequenceLockTimeDisabled, // }}, // }, // view: utxoView, // want: &SequenceLock{ // Seconds: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1, // BlockHeight: prevUtxoHeight + 3, // }, // }, // // Transaction with a single input. The input's sequence number encodes a relative lock-time in blocks (3 // // blocks). The sequence lock should have a value of -1 for seconds, but a height of 2 meaning it can be // // included at height 3. // { // tx: &wire.MsgTx{ // Version: 2, // TxIn: []*wire.TxIn{{ // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(false, 3), // }}, // }, // view: utxoView, // want: &SequenceLock{ // Seconds: -1, // BlockHeight: prevUtxoHeight + 2, // }, // }, // // A transaction with two inputs with lock times expressed in seconds. The selected sequence lock value for // // seconds should be the time further in the future. // { // tx: &wire.MsgTx{ // Version: 2, // TxIn: []*wire.TxIn{{ // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(true, 5120), // }, // { // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(true, 2560), // }}, // }, // view: utxoView, // want: &SequenceLock{ // Seconds: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1, // BlockHeight: -1, // }, // }, // // A transaction with two inputs with lock times expressed in blocks. The selected sequence lock value for // // blocks should be the height further in the future, so a height of 10 indicating it can be included at height // // 11. // { // tx: &wire.MsgTx{ // Version: 2, // TxIn: []*wire.TxIn{{ // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(false, 1), // }, // { // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(false, 11), // }}, // }, // view: utxoView, // want: &SequenceLock{ // Seconds: -1, // BlockHeight: prevUtxoHeight + 10, // }, // }, // // A transaction with multiple inputs. Two inputs are time based, and the other two are block based. The lock // // lying further into the future for both inputs should be chosen. // { // tx: &wire.MsgTx{ // Version: 2, // TxIn: []*wire.TxIn{{ // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(true, 2560), // }, // { // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(true, 6656), // }, // { // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(false, 3), // }, // { // PreviousOutPoint: utxo, // Sequence: LockTimeToSequence(false, 9), // }}, // }, // view: utxoView, // want: &SequenceLock{ // Seconds: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1, // BlockHeight: prevUtxoHeight + 8, // }, // }, // // A transaction with a single unconfirmed input. As the input is confirmed, the height of the input should be // // interpreted as the height of the *next* block. So, a 2 block relative lock means the sequence lock should be // // for 1 block after the *next* block height, indicating it can be included 2 blocks after that. // { // tx: &wire.MsgTx{ // Version: 2, // TxIn: []*wire.TxIn{{ // PreviousOutPoint: unConfUtxo, // Sequence: LockTimeToSequence(false, 2), // }}, // }, // view: utxoView, // mempool: true, // want: &SequenceLock{ // Seconds: -1, // BlockHeight: nextBlockHeight + 1, // }, // }, // // A transaction with a single unconfirmed input. The input has a time based lock, so the lock time should be // // based off the MTP of the *next* block. // { // tx: &wire.MsgTx{ // Version: 2, // TxIn: []*wire.TxIn{{ // PreviousOutPoint: unConfUtxo, // Sequence: LockTimeToSequence(true, 1024), // }}, // }, // view: utxoView, // mempool: true, // want: &SequenceLock{ // Seconds: nextMedianTime + 1023, // BlockHeight: -1, // }, // }, // } // t.Logf("Running %v SequenceLock tests", len(tests)) // for i, test := range tests { // utilTx := util.NewTx(test.tx) // seqLock, e := chain.CalcSequenceLock(utilTx, test.view, test.mempool) // if e != nil { // t.Fatalf("test #%d, unable to calc sequence lock: %v", i, e) // } // if seqLock.Seconds != test.want.Seconds { // t.Fatalf("test #%d got %v seconds want %v seconds", // i, seqLock.Seconds, test.want.Seconds) // } // if seqLock.BlockHeight != test.want.BlockHeight { // t.Fatalf("test #%d got height of %v want height of %v ", // i, seqLock.BlockHeight, test.want.BlockHeight) // } // } // } // nodeHashes is a convenience function that returns the hashes for all of the passed indexes of the provided nodes. It // is used to construct expected hash slices in the tests. func nodeHashes(nodes []*BlockNode, indexes ...int) []chainhash.Hash { hashes := make([]chainhash.Hash, 0, len(indexes)) for _, idx := range indexes { hashes = append(hashes, nodes[idx].hash) } return hashes } // nodeHeaders is a convenience function that returns the headers for all of the passed indexes of the provided nodes. // It is used to construct expected located headers in the tests. func nodeHeaders(nodes []*BlockNode, indexes ...int) []wire.BlockHeader { headers := make([]wire.BlockHeader, 0, len(indexes)) for _, idx := range indexes { headers = append(headers, nodes[idx].Header()) } return headers } // TestLocateInventory ensures that locating inventory via the LocateHeaders and LocateBlocks functions behaves as // expected. func TestLocateInventory(t *testing.T) { // Construct a synthetic block chain with a block index consisting of the following structure. // // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 // \-> 16a -> 17a tip := tstTip chain := newFakeChain(&chaincfg.MainNetParams) branch0Nodes := chainedNodes(chain.BestChain.Genesis(), 18) branch1Nodes := chainedNodes(branch0Nodes[14], 2) for _, node := range branch0Nodes { chain.Index.AddNode(node) } for _, node := range branch1Nodes { chain.Index.AddNode(node) } chain.BestChain.SetTip(tip(branch0Nodes)) // Create chain views for different branches of the overall chain to simulate a local and remote node on different // parts of the chain. localView := newChainView(tip(branch0Nodes)) remoteView := newChainView(tip(branch1Nodes)) // Create a chain view for a completely unrelated block chain to simulate a remote node on a totally different // chain. unrelatedBranchNodes := chainedNodes(nil, 5) unrelatedView := newChainView(tip(unrelatedBranchNodes)) tests := []struct { name string // locator for requested inventory locator BlockLocator // stop hash for locator hashStop chainhash.Hash // max to locate, 0 = wire const maxAllowed uint32 // expected located headers headers []wire.BlockHeader // expected located hashes hashes []chainhash.Hash }{ { // Empty block locators and unknown stop hash. No inventory should be located. name: "no locators, no stop", locator: nil, hashStop: chainhash.Hash{}, headers: nil, hashes: nil, }, { // Empty block locators and stop hash in side chain. The expected result is the requested block. name: "no locators, stop in side", locator: nil, hashStop: tip(branch1Nodes).hash, headers: nodeHeaders(branch1Nodes, 1), hashes: nodeHashes(branch1Nodes, 1), }, { // Empty block locators and stop hash in main chain. The expected result is the requested block. name: "no locators, stop in main", locator: nil, hashStop: branch0Nodes[12].hash, headers: nodeHeaders(branch0Nodes, 12), hashes: nodeHashes(branch0Nodes, 12), }, { // Locators based on remote being on side chain and a stop hash local node doesn't know about. The expected // result is the blocks after the fork point in the main chain and the stop hash has no effect. name: "remote side chain, unknown stop", locator: remoteView.BlockLocator(nil), hashStop: chainhash.Hash{0x01}, headers: nodeHeaders(branch0Nodes, 15, 16, 17), hashes: nodeHashes(branch0Nodes, 15, 16, 17), }, { // Locators based on remote being on side chain and a stop hash in side chain. The expected result is the // blocks after the fork point in the main chain and the stop hash has no effect. name: "remote side chain, stop in side", locator: remoteView.BlockLocator(nil), hashStop: tip(branch1Nodes).hash, headers: nodeHeaders(branch0Nodes, 15, 16, 17), hashes: nodeHashes(branch0Nodes, 15, 16, 17), }, { // Locators based on remote being on side chain and a stop hash in main chain, but before fork point. The // expected result is the blocks after the fork point in the main chain and the stop hash has no effect. name: "remote side chain, stop in main before", locator: remoteView.BlockLocator(nil), hashStop: branch0Nodes[13].hash, headers: nodeHeaders(branch0Nodes, 15, 16, 17), hashes: nodeHashes(branch0Nodes, 15, 16, 17), }, { // Locators based on remote being on side chain and a stop hash in main chain, but exactly at the fork // point. The expected result is the blocks after the fork point in the main chain and the stop hash has no // effect. name: "remote side chain, stop in main exact", locator: remoteView.BlockLocator(nil), hashStop: branch0Nodes[14].hash, headers: nodeHeaders(branch0Nodes, 15, 16, 17), hashes: nodeHashes(branch0Nodes, 15, 16, 17), }, { // Locators based on remote being on side chain and a stop hash in main chain just after the fork point. The // expected result is the blocks after the fork point in the main chain up to and including the stop hash. name: "remote side chain, stop in main after", locator: remoteView.BlockLocator(nil), hashStop: branch0Nodes[15].hash, headers: nodeHeaders(branch0Nodes, 15), hashes: nodeHashes(branch0Nodes, 15), }, { // Locators based on remote being on side chain and a stop hash in main chain some time after the fork // point. The expected result is the blocks after the fork point in the main chain up to and including the // stop hash. name: "remote side chain, stop in main after more", locator: remoteView.BlockLocator(nil), hashStop: branch0Nodes[16].hash, headers: nodeHeaders(branch0Nodes, 15, 16), hashes: nodeHashes(branch0Nodes, 15, 16), }, { // Locators based on remote being on main chain in the past and a stop hash local node doesn't know about. // The expected result is the blocks after the known point in the main chain and the stop hash has no // effect. name: "remote main chain past, unknown stop", locator: localView.BlockLocator(branch0Nodes[12]), hashStop: chainhash.Hash{0x01}, headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17), hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17), }, { // Locators based on remote being on main chain in the past and a stop hash in a side chain. The expected // result is the blocks after the known point in the main chain and the stop hash has no effect. name: "remote main chain past, stop in side", locator: localView.BlockLocator(branch0Nodes[12]), hashStop: tip(branch1Nodes).hash, headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17), hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17), }, { // Locators based on remote being on main chain in the past and a stop hash in the main chain before that // point. The expected result is the blocks after the known point in the main chain and the stop hash has no // effect. name: "remote main chain past, stop in main before", locator: localView.BlockLocator(branch0Nodes[12]), hashStop: branch0Nodes[11].hash, headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17), hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17), }, { // Locators based on remote being on main chain in the past and a stop hash in the main chain exactly at // that point. The expected result is the blocks after the known point in the main chain and the stop hash // has no effect. name: "remote main chain past, stop in main exact", locator: localView.BlockLocator(branch0Nodes[12]), hashStop: branch0Nodes[12].hash, headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17), hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17), }, { // Locators based on remote being on main chain in the past and a stop hash in the main chain just after // that point. The expected result is the blocks after the known point in the main chain and the stop hash // has no effect. name: "remote main chain past, stop in main after", locator: localView.BlockLocator(branch0Nodes[12]), hashStop: branch0Nodes[13].hash, headers: nodeHeaders(branch0Nodes, 13), hashes: nodeHashes(branch0Nodes, 13), }, { // Locators based on remote being on main chain in the past and a stop hash in the main chain some time // after that point. The expected result is the blocks after the known point in the main chain and the stop // hash has no effect. name: "remote main chain past, stop in main after more", locator: localView.BlockLocator(branch0Nodes[12]), hashStop: branch0Nodes[15].hash, headers: nodeHeaders(branch0Nodes, 13, 14, 15), hashes: nodeHashes(branch0Nodes, 13, 14, 15), }, { // Locators based on remote being at exactly the same point in the main chain and a stop hash local node // doesn't know about. The expected result is no located inventory. name: "remote main chain same, unknown stop", locator: localView.BlockLocator(nil), hashStop: chainhash.Hash{0x01}, headers: nil, hashes: nil, }, { // Locators based on remote being at exactly the same point in the main chain and a stop hash at exactly the // same point. The expected result is no located inventory. name: "remote main chain same, stop same point", locator: localView.BlockLocator(nil), hashStop: tip(branch0Nodes).hash, headers: nil, hashes: nil, }, { // Locators from remote that don't include any blocks the local node knows. This would happen if the remote // node is on a completely separate chain that isn't rooted with the same genesis block. The expected result // is the blocks after the genesis block. name: "remote unrelated chain", locator: unrelatedView.BlockLocator(nil), hashStop: chainhash.Hash{}, headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ), hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ), }, { // Locators from remote for second block in main chain and no stop hash, but with an overridden max limit. // The expected result is the blocks after the second block limited by the max. name: "remote genesis", locator: locatorHashes(branch0Nodes, 0), hashStop: chainhash.Hash{}, maxAllowed: 3, headers: nodeHeaders(branch0Nodes, 1, 2, 3), hashes: nodeHashes(branch0Nodes, 1, 2, 3), }, { // Poorly formed locator. Locator from remote that only includes a single block on a side chain the local // node knows. The expected result is the blocks after the genesis block since even though the block is // known, it is on a side chain and there are no more locators to find the fork point. name: "weak locator, single known side block", locator: locatorHashes(branch1Nodes, 1), hashStop: chainhash.Hash{}, headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ), hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ), }, { // Poorly formed locator. Locator from remote that only includes multiple blocks on a side chain the local // node knows however none in the main chain. The expected result is the blocks after the genesis block // since even though the blocks are known, they are all on a side chain and there are no more locators to // find the fork point. name: "weak locator, multiple known side blocks", locator: locatorHashes(branch1Nodes, 1), hashStop: chainhash.Hash{}, headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ), hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ), }, { // Poorly formed locator. Locator from remote that only includes multiple blocks on a side chain the local // node knows however none in the main chain but includes a stop hash in the main chain. The expected result // is the blocks after the genesis block up to the stop hash since even though the blocks are known, they // are all on a side chain and there are no more locators to find the fork point. name: "weak locator, multiple known side blocks, stop in main", locator: locatorHashes(branch1Nodes, 1), hashStop: branch0Nodes[5].hash, headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5), hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5), }, } for _, test := range tests { // Ensure the expected headers are located. var headers []wire.BlockHeader if test.maxAllowed != 0 { // Need to use the unexported function to override the max allowed for headers. chain.ChainLock.RLock() headers = chain.locateHeaders(test.locator, &test.hashStop, test.maxAllowed, ) chain.ChainLock.RUnlock() } else { headers = chain.LocateHeaders(test.locator, &test.hashStop, ) } if !reflect.DeepEqual(headers, test.headers) { t.Errorf("%s: unxpected headers -- got %v, want %v", test.name, headers, test.headers, ) continue } // Ensure the expected block hashes are located. maxAllowed := uint32(wire.MaxBlocksPerMsg) if test.maxAllowed != 0 { maxAllowed = test.maxAllowed } hashes := chain.LocateBlocks(test.locator, &test.hashStop, maxAllowed, ) if !reflect.DeepEqual(hashes, test.hashes) { t.Errorf("%s: unxpected hashes -- got %v, want %v", test.name, hashes, test.hashes, ) continue } } } // TestHeightToHashRange ensures that fetching a range of block hashes by start height and end hash works as expected. func TestHeightToHashRange(t *testing.T) { // Construct a synthetic block chain with a block index consisting of the following structure. // // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 // \-> 16a -> 17a -> 18a (unvalidated) tip := tstTip chain := newFakeChain(&chaincfg.MainNetParams) branch0Nodes := chainedNodes(chain.BestChain.Genesis(), 18) branch1Nodes := chainedNodes(branch0Nodes[14], 3) for _, node := range branch0Nodes { chain.Index.SetStatusFlags(node, statusValid) chain.Index.AddNode(node) } for _, node := range branch1Nodes { if node.height < 18 { chain.Index.SetStatusFlags(node, statusValid) } chain.Index.AddNode(node) } chain.BestChain.SetTip(tip(branch0Nodes)) tests := []struct { name string // locator for requested inventory startHeight int32 // stop hash for locator endHash chainhash.Hash // max to locate, 0 = wire const maxResults int // expected located hashes hashes []chainhash.Hash expectError bool }{ { name: "blocks below tip", startHeight: 11, endHash: branch0Nodes[14].hash, maxResults: 10, hashes: nodeHashes(branch0Nodes, 10, 11, 12, 13, 14), }, { name: "blocks on main chain", startHeight: 15, endHash: branch0Nodes[17].hash, maxResults: 10, hashes: nodeHashes(branch0Nodes, 14, 15, 16, 17), }, { name: "blocks on stale chain", startHeight: 15, endHash: branch1Nodes[1].hash, maxResults: 10, hashes: append(nodeHashes(branch0Nodes, 14), nodeHashes(branch1Nodes, 0, 1)..., ), }, { name: "invalid start height", startHeight: 19, endHash: branch0Nodes[17].hash, maxResults: 10, expectError: true, }, { name: "too many results", startHeight: 1, endHash: branch0Nodes[17].hash, maxResults: 10, expectError: true, }, { name: "unvalidated block", startHeight: 15, endHash: branch1Nodes[2].hash, maxResults: 10, expectError: true, }, } for _, test := range tests { hashes, e := chain.HeightToHashRange(test.startHeight, &test.endHash, test.maxResults, ) if e != nil { if !test.expectError { t.Errorf("%s: unexpected error: %v", test.name, e) } continue } if !reflect.DeepEqual(hashes, test.hashes) { t.Errorf("%s: unxpected hashes -- got %v, want %v", test.name, hashes, test.hashes, ) } } } // TestIntervalBlockHashes ensures that fetching block hashes at specified intervals by end hash works as expected. func TestIntervalBlockHashes(t *testing.T) { // Construct a synthetic block chain with a block index consisting of the following structure. // // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 // \-> 16a -> 17a -> 18a (unvalidated) tip := tstTip chain := newFakeChain(&chaincfg.MainNetParams) branch0Nodes := chainedNodes(chain.BestChain.Genesis(), 18) branch1Nodes := chainedNodes(branch0Nodes[14], 3) for _, node := range branch0Nodes { chain.Index.SetStatusFlags(node, statusValid) chain.Index.AddNode(node) } for _, node := range branch1Nodes { if node.height < 18 { chain.Index.SetStatusFlags(node, statusValid) } chain.Index.AddNode(node) } chain.BestChain.SetTip(tip(branch0Nodes)) tests := []struct { name string endHash chainhash.Hash interval int hashes []chainhash.Hash expectError bool }{ { name: "blocks on main chain", endHash: branch0Nodes[17].hash, interval: 8, hashes: nodeHashes(branch0Nodes, 7, 15), }, { name: "blocks on stale chain", endHash: branch1Nodes[1].hash, interval: 8, hashes: append(nodeHashes(branch0Nodes, 7), nodeHashes(branch1Nodes, 0)..., ), }, { name: "no results", endHash: branch0Nodes[17].hash, interval: 20, hashes: []chainhash.Hash{}, }, { name: "unvalidated block", endHash: branch1Nodes[2].hash, interval: 8, expectError: true, }, } for _, test := range tests { hashes, e := chain.IntervalBlockHashes(&test.endHash, test.interval) if e != nil { if !test.expectError { t.Errorf("%s: unexpected error: %v", test.name, e) } continue } if !reflect.DeepEqual(hashes, test.hashes) { t.Errorf("%s: unxpected hashes -- got %v, want %v", test.name, hashes, test.hashes, ) } } }