public void Spend(Coin c) { UnspentOutputs coin = this.pendingCoins.FirstOrDefault(u => u.TransactionId == c.Outpoint.Hash); if (coin == null) { FetchCoinsResponse result = this.coinView.FetchCoins(new[] { c.Outpoint.Hash }); if (result.BlockHash != this.hash) { throw new InvalidOperationException("Unexepected hash"); } if (result.UnspentOutputs[0] == null) { throw new InvalidOperationException("Coin unavailable"); } if (!result.UnspentOutputs[0].Spend(c.Outpoint.N)) { throw new InvalidOperationException("Coin unspendable"); } this.pendingCoins.Add(result.UnspentOutputs[0]); } else { if (!coin.Spend(c.Outpoint.N)) { throw new InvalidOperationException("Coin unspendable"); } } }
public async Task TestRewindAsync() { uint256 tip = await this.cachedCoinView.GetTipHashAsync(); Assert.Equal(this.concurrentChain.Genesis.HashBlock, tip); int currentHeight = 0; // Create a lot of new coins. List <UnspentOutputs> outputsList = this.CreateOutputsList(currentHeight + 1, 100); await this.SaveChangesAsync(outputsList, new List <TxOut[]>(), currentHeight + 1); currentHeight++; await this.cachedCoinView.FlushAsync(true); uint256 tipAfterOriginalCoinsCreation = await this.cachedCoinView.GetTipHashAsync(); // Collection that will be used as a coinview that we will update in parallel. Needed to verify that actual coinview is ok. List <OutPoint> outPoints = this.ConvertToListOfOutputPoints(outputsList); // Copy of current state to later rewind and verify against it. List <OutPoint> copyOfOriginalOutPoints = new List <OutPoint>(outPoints); List <OutPoint> copyAfterHalfOfAdditions = new List <OutPoint>(); uint256 coinviewTipAfterHalf = null; int addChangesTimes = 500; // Spend some coins in the next N saves. for (int i = 0; i < addChangesTimes; ++i) { uint256 txId = outPoints[this.random.Next(0, outPoints.Count)].Hash; List <OutPoint> txPoints = outPoints.Where(x => x.Hash == txId).ToList(); this.Shuffle(txPoints); List <OutPoint> txPointsToSpend = txPoints.Take(txPoints.Count / 2).ToList(); // First spend in cached coinview FetchCoinsResponse response = await this.cachedCoinView.FetchCoinsAsync(new[] { txId }); Assert.Single(response.UnspentOutputs); UnspentOutputs coins = response.UnspentOutputs[0]; UnspentOutputs unchangedClone = coins.Clone(); foreach (OutPoint outPointToSpend in txPointsToSpend) { coins.Spend(outPointToSpend.N); } // Spend from outPoints. outPoints.RemoveAll(x => txPointsToSpend.Contains(x)); // Save coinview await this.SaveChangesAsync(new List <UnspentOutputs>() { coins }, new List <TxOut[]>() { unchangedClone.Outputs }, currentHeight + 1); currentHeight++; if (i == addChangesTimes / 2) { copyAfterHalfOfAdditions = new List <OutPoint>(outPoints); coinviewTipAfterHalf = await this.cachedCoinView.GetTipHashAsync(); } } await this.ValidateCoinviewIntegrityAsync(outPoints); for (int i = 0; i < addChangesTimes; i++) { await this.cachedCoinView.RewindAsync(); uint256 currentTip = await this.cachedCoinView.GetTipHashAsync(); if (currentTip == coinviewTipAfterHalf) { await this.ValidateCoinviewIntegrityAsync(copyAfterHalfOfAdditions); } } Assert.Equal(tipAfterOriginalCoinsCreation, await this.cachedCoinView.GetTipHashAsync()); await this.ValidateCoinviewIntegrityAsync(copyOfOriginalOutPoints); }