private async Task ValidateCoinviewIntegrityAsync(List<OutPoint> expectedAvailableOutPoints) { foreach (IGrouping<uint256, OutPoint> outPointsGroup in expectedAvailableOutPoints.GroupBy(x => x.Hash)) { uint256 txId = outPointsGroup.Key; List<uint> availableIndexes = outPointsGroup.Select(x => x.N).ToList(); FetchCoinsResponse result = this.cachedCoinView.FetchCoins(new[] { txId }); TxOut[] outputsArray = result.UnspentOutputs[0].Outputs; // Check expected coins are present. foreach (uint availableIndex in availableIndexes) { Assert.NotNull(outputsArray[availableIndex]); } // Check unexpected coins are not present. Assert.Equal(availableIndexes.Count, outputsArray.Count(x => x != null)); } // Verify that snapshot is equal to current state of coinview. uint256[] allTxIds = expectedAvailableOutPoints.Select(x => x.Hash).Distinct().ToArray(); FetchCoinsResponse result2 = this.cachedCoinView.FetchCoins(allTxIds); List<OutPoint> availableOutPoints = this.ConvertToListOfOutputPoints(result2.UnspentOutputs.ToList()); Assert.Equal(expectedAvailableOutPoints.Count, availableOutPoints.Count); foreach (OutPoint referenceOutPoint in expectedAvailableOutPoints) { Assert.Contains(referenceOutPoint, availableOutPoints); } }
/// <inheritdoc /> public GetSenderResult GetSender(Transaction tx, ICoinView coinView, IList <Transaction> blockTxs) { OutPoint prevOut = tx.Inputs[0].PrevOut; // Check the txes in this block first if (blockTxs != null && blockTxs.Count > 0) { foreach (Transaction btx in blockTxs) { if (btx.GetHash() == prevOut.Hash) { Script script = btx.Outputs[prevOut.N].ScriptPubKey; return(GetAddressFromScript(script)); } } } // Check the utxoset for the p2pk of the unspent output for this transaction if (coinView != null) { FetchCoinsResponse fetchCoinResult = coinView.FetchCoinsAsync(new uint256[] { prevOut.Hash }).Result; UnspentOutputs unspentOutputs = fetchCoinResult.UnspentOutputs.FirstOrDefault(); if (unspentOutputs == null) { throw new Exception("Unspent outputs to smart contract transaction are not present in coinview"); } Script script = unspentOutputs.Outputs[prevOut.N].ScriptPubKey; return(GetAddressFromScript(script)); } return(GetSenderResult.CreateFailure("Unable to get the sender of the transaction")); }
public async Task GenerateBlocksAsync_does_not_use_small_coins() { var walletSecret = new WalletSecret() { WalletName = "wallet", WalletPassword = "******" }; var wallet = new Wallet.Wallet() { Network = this.network }; var milliseconds550MinutesAgo = (uint)Math.Max(this.chain.Tip.Header.Time - TimeSpan.FromMinutes(550).Milliseconds, 0); this.AddAccountWithSpendableOutputs(wallet); var spendableTransactions = wallet.GetAllSpendableTransactions(CoinType.Stratis, this.chain.Tip.Height, 0).ToList(); this.walletManager.Setup(w => w.GetSpendableTransactionsInWalletForStaking(It.IsAny <string>(), It.IsAny <int>())) .Returns(spendableTransactions); var fetchedUtxos = spendableTransactions .Select(t => new UnspentOutputs(t.Transaction.Id, new Coins() { CoinBase = false, CoinStake = false, Height = 0, Outputs = { new TxOut(t.Transaction.Amount ?? Money.Zero, t.Address.ScriptPubKey) }, Time = milliseconds550MinutesAgo, Version = 1 })) .ToArray(); var fetchCoinsResponse = new FetchCoinsResponse(fetchedUtxos, this.chain.Tip.HashBlock); fetchCoinsResponse.UnspentOutputs .Where(u => u.Outputs.Any(o => o.Value < this.posMinting.MinimumStakingCoinValue)).Should() .NotBeEmpty("otherwise we are not sure the code actually excludes them"); fetchCoinsResponse.UnspentOutputs .Where(u => u.Outputs.Any(o => o.Value >= this.posMinting.MinimumStakingCoinValue)).Should() .NotBeEmpty("otherwise we are not sure the code actually includes them"); this.coinView.Setup(c => c.FetchCoins(It.IsAny <uint256[]>(), It.IsAny <CancellationToken>())) .Returns(fetchCoinsResponse); this.consensusManager.Setup(c => c.Tip).Returns(this.chain.Tip); this.dateTimeProvider.Setup(c => c.GetAdjustedTimeAsUnixTimestamp()) .Returns(this.chain.Tip.Header.Time + 16); var ct = CancellationToken.None; var utxoStakeDescriptions = await this.posMinting.GetUtxoStakeDescriptionsAsync(walletSecret, ct); utxoStakeDescriptions.Select(d => d.TxOut.Value).Where(v => v < this.posMinting.MinimumStakingCoinValue) .Should().BeEmpty("small coins should not be included"); utxoStakeDescriptions.Select(d => d.TxOut.Value).Where(v => v >= this.posMinting.MinimumStakingCoinValue) .Should().NotBeEmpty("big enough coins should be included"); var expectedAmounts = spendableTransactions.Select(s => s.Transaction.Amount) .Where(a => a >= this.posMinting.MinimumStakingCoinValue).ToArray(); utxoStakeDescriptions.Count.Should().Be(expectedAmounts.Length); utxoStakeDescriptions.Select(d => d.TxOut.Value).Should().Contain(expectedAmounts); }
public void SpentOutput_Returns_False() { // Construct transaction. Transaction transaction = this.network.CreateTransaction(); transaction.Inputs.Add(new TxIn(new OutPoint(uint256.One, 0))); // Setup coinview to return as if the PrevOut is spent (i.e. Coins is null). var unspentOutput = new UnspentOutput(transaction.Inputs[0].PrevOut, null); var unspentOutputArray = new UnspentOutput[] { unspentOutput }; var fetchResponse = new FetchCoinsResponse(); fetchResponse.UnspentOutputs.Add(unspentOutputArray[0].OutPoint, unspentOutput); this.coinView.Setup(x => x.FetchCoins(It.IsAny <OutPoint[]>())) .Returns(fetchResponse); var blockTxs = new List <Transaction>(); // Retriever fails but doesn't throw exception GetSenderResult result = this.senderRetriever.GetSender(transaction, this.coinView.Object, blockTxs); Assert.False(result.Success); Assert.Equal(SenderRetriever.OutputAlreadySpent, result.Error); }
public FetchCoinsResponse FetchCoins(OutPoint[] utxos) { FetchCoinsResponse res = new FetchCoinsResponse(); using (DBreeze.Transactions.Transaction transaction = this.CreateTransaction()) { transaction.SynchronizeTables("BlockHash", "Coins"); transaction.ValuesLazyLoadingIsOn = false; using (new StopwatchDisposable(o => this.performanceCounter.AddQueryTime(o))) { this.performanceCounter.AddQueriedEntities(utxos.Length); foreach (OutPoint outPoint in utxos) { Row <byte[], byte[]> row = transaction.Select <byte[], byte[]>("Coins", outPoint.ToBytes()); Coins outputs = row.Exists ? this.dataStoreSerializer.Deserialize <Coins>(row.Value) : null; this.logger.LogDebug("Outputs for '{0}' were {1}.", outPoint, outputs == null ? "NOT loaded" : "loaded"); res.UnspentOutputs.Add(outPoint, new UnspentOutput(outPoint, outputs)); } } } return(res); }
/// <inheritdoc /> public override async Task RunAsync(RuleContext context) { var utxoRuleContext = context as UtxoRuleContext; // Check that the current block has not been reorged. // Catching a reorg at this point will not require a rewind. if (context.ValidationContext.Block.Header.HashPrevBlock != context.ConsensusTip.HashBlock) { this.Logger.LogTrace("Reorganization detected."); ConsensusErrors.InvalidPrevTip.Throw(); } // Load the UTXO set of the current block. UTXO may be loaded from cache or from disk. // The UTXO set is stored in the context. this.Logger.LogTrace("Loading UTXO set of the new block."); utxoRuleContext.UnspentOutputSet = new UnspentOutputSet(); using (new StopwatchDisposable(o => this.Parent.PerformanceCounter.AddUTXOFetchingTime(o))) { uint256[] ids = this.GetIdsToFetch(context.ValidationContext.Block, context.Flags.EnforceBIP30); FetchCoinsResponse coins = await this.PowParent.UtxoSet.FetchCoinsAsync(ids).ConfigureAwait(false); utxoRuleContext.UnspentOutputSet.SetCoins(coins.UnspentOutputs); } // Attempt to load into the cache the next set of UTXO to be validated. // The task is not awaited so will not stall main validation process. this.TryPrefetchAsync(context.Flags); }
public void GenerateBlocks_does_not_use_small_coins() { var walletSecret = new WalletSecret() { WalletName = "wallet", WalletPassword = "******" }; var wallet = new Wallet.Wallet(this.network); var milliseconds550MinutesAgo = (uint)Math.Max(this.chainIndexer.Tip.Header.Time - TimeSpan.FromMinutes(550).Milliseconds, 0); this.AddAccountWithSpendableOutputs(wallet); var spendableTransactions = wallet.GetAllSpendableTransactions(this.chainIndexer.Tip.Height, 0).ToList(); this.walletManager.Setup(w => w.GetSpendableTransactionsInWalletForStaking(It.IsAny <string>(), It.IsAny <int>())) .Returns(spendableTransactions); var fetchedUtxos = spendableTransactions .Select(t => new UnspentOutput( new OutPoint(t.Transaction.Id, 0), new Utilities.Coins(0, new TxOut(t.Transaction.Amount ?? Money.Zero, t.Address.ScriptPubKey), false, false))) .ToArray(); var fetchCoinsResponse = new FetchCoinsResponse(); foreach (var fetch in fetchedUtxos) { fetchCoinsResponse.UnspentOutputs.Add(fetch.OutPoint, fetch); } fetchCoinsResponse.UnspentOutputs .Where(u => u.Value.Coins.TxOut.Value < this.posMinting.MinimumStakingCoinValue).Should() .NotBeEmpty("otherwise we are not sure the code actually excludes them"); fetchCoinsResponse.UnspentOutputs .Where(u => u.Value.Coins.TxOut.Value >= this.posMinting.MinimumStakingCoinValue).Should() .NotBeEmpty("otherwise we are not sure the code actually includes them"); this.coinView.Setup(c => c.FetchCoins(It.IsAny <OutPoint[]>())) .Returns(fetchCoinsResponse); this.consensusManager.Setup(c => c.Tip).Returns(this.chainIndexer.Tip); this.dateTimeProvider.Setup(c => c.GetAdjustedTimeAsUnixTimestamp()) .Returns(this.chainIndexer.Tip.Header.Time + 16); var ct = CancellationToken.None; var utxoStakeDescriptions = this.posMinting.GetUtxoStakeDescriptions(walletSecret, ct); utxoStakeDescriptions.Select(d => d.TxOut.Value).Where(v => v < this.posMinting.MinimumStakingCoinValue) .Should().BeEmpty("small coins should not be included"); utxoStakeDescriptions.Select(d => d.TxOut.Value).Where(v => v >= this.posMinting.MinimumStakingCoinValue) .Should().NotBeEmpty("big enough coins should be included"); var expectedAmounts = spendableTransactions.Select(s => s.Transaction.Amount) .Where(a => a >= this.posMinting.MinimumStakingCoinValue).ToArray(); utxoStakeDescriptions.Count.Should().Be(expectedAmounts.Length); utxoStakeDescriptions.Select(d => d.TxOut.Value).Should().Contain(expectedAmounts); }
/// <inheritdoc /> public async Task <UnspentOutputs> GetUnspentTransactionAsync(uint256 trxid) { FetchCoinsResponse response = this.coinView.FetchCoins(new[] { trxid }); UnspentOutputs unspentOutputs = response.UnspentOutputs.FirstOrDefault(); return(unspentOutputs); }
public void RunRule_ProvenHeadersActive_And_NullPreviousStake_InvalidPreviousProvenHeaderStakeModifierErrorIsThrown() { // Setup previous chained header. PosBlock prevPosBlock = new PosBlockBuilder(this.network).Build(); ProvenBlockHeader prevProvenBlockHeader = new ProvenBlockHeaderBuilder(prevPosBlock, this.network).Build(); prevProvenBlockHeader.StakeModifierV2 = null; // Forcing previous stake modifier to null. var previousChainedHeader = new ChainedHeader(prevProvenBlockHeader, prevProvenBlockHeader.GetHash(), null); previousChainedHeader.SetPrivatePropertyValue("Height", this.provenHeadersActivationHeight + 1); // Setup proven header with valid coinstake. PosBlock posBlock = new PosBlockBuilder(this.network).Build(); ProvenBlockHeader provenBlockHeader = new ProvenBlockHeaderBuilder(posBlock, this.network).Build(); provenBlockHeader.PosBlockHeader.HashPrevBlock = prevProvenBlockHeader.GetHash(); if (provenBlockHeader.Coinstake is IPosTransactionWithTime posTrx) { posTrx.Time = provenBlockHeader.Time; } // Setup chained header and move it to the height higher than proven header activation height. this.ruleContext.ValidationContext.ChainedHeaderToValidate = new ChainedHeader(provenBlockHeader, provenBlockHeader.GetHash(), previousChainedHeader); this.ruleContext.ValidationContext.ChainedHeaderToValidate.SetPrivatePropertyValue("Height", this.provenHeadersActivationHeight + 2); // Ensure that coinview returns a UTXO with valid outputs. var utxoOneTransaction = this.network.CreateTransaction(); utxoOneTransaction.AddOutput(new TxOut()); var utxoOne = new UnspentOutput(new OutPoint(utxoOneTransaction, 0), new Coins((uint)this.provenHeadersActivationHeight + 10, utxoOneTransaction.Outputs.First(), false)); // Setup coinstake transaction with a valid stake age. var res = new FetchCoinsResponse(); res.UnspentOutputs.Add(utxoOne.OutPoint, utxoOne); this.coinView .Setup(m => m.FetchCoins(It.IsAny <OutPoint[]>())) .Returns(res); // Setup stake validator to pass stake age check. this.stakeValidator .Setup(m => m.IsConfirmedInNPrevBlocks(It.IsAny <UnspentOutput>(), It.IsAny <ChainedHeader>(), It.IsAny <long>())) .Returns(false); // Setup stake validator to pass signature validation. this.stakeValidator .Setup(m => m.VerifySignature(It.IsAny <UnspentOutput>(), It.IsAny <Transaction>(), It.IsAny <int>(), It.IsAny <ScriptVerify>())) .Returns(true); // When we run the validation rule, we should hit previous stake null error. Action ruleValidation = () => this.consensusRules.RegisterRule <ProvenHeaderCoinstakeRule>().Run(this.ruleContext); ruleValidation.Should().Throw <ConsensusErrorException>() .And.ConsensusError .Should().Be(ConsensusErrors.InvalidPreviousProvenHeaderStakeModifier); }
public void InvalidStakeKernelHash_CoinstakeVerifySignatureErrorIsThrown() { // Setup previous chained header. PosBlock prevPosBlock = new PosBlockBuilder(this.network).Build(); ProvenBlockHeader prevProvenBlockHeader = new ProvenBlockHeaderBuilder(prevPosBlock, this.network).Build(); var previousChainedHeader = new ChainedHeader(prevProvenBlockHeader, prevProvenBlockHeader.GetHash(), null); previousChainedHeader.SetPrivatePropertyValue("Height", this.provenHeadersActivationHeight + 1); // Setup proven header with valid coinstake. PosBlock posBlock = new PosBlockBuilder(this.network).Build(); ProvenBlockHeader provenBlockHeader = new ProvenBlockHeaderBuilder(posBlock, this.network).Build(prevProvenBlockHeader); provenBlockHeader.HashPrevBlock = prevProvenBlockHeader.GetHash(); // Setup chained header and move it to the height higher than proven header activation height. this.ruleContext.ValidationContext.ChainedHeaderToValidate = new ChainedHeader(provenBlockHeader, provenBlockHeader.GetHash(), previousChainedHeader); this.ruleContext.ValidationContext.ChainedHeaderToValidate.SetPrivatePropertyValue("Height", this.provenHeadersActivationHeight + 2); // Ensure that coinview returns a UTXO with valid outputs. var utxoOneTransaction = this.network.CreateTransaction(); utxoOneTransaction.AddOutput(new TxOut()); var utxoOne = new UnspentOutput(new OutPoint(utxoOneTransaction, 0), new Coins((uint)this.provenHeadersActivationHeight + 10, utxoOneTransaction.Outputs.First(), false)); // Setup coinstake transaction with a valid stake age. var res = new FetchCoinsResponse(); res.UnspentOutputs.Add(utxoOne.OutPoint, utxoOne); this.coinView .Setup(m => m.FetchCoins(It.IsAny <OutPoint[]>())) .Returns(res); // Setup stake validator to pass stake age check. this.stakeValidator .Setup(m => m.IsConfirmedInNPrevBlocks(It.IsAny <UnspentOutput>(), It.IsAny <ChainedHeader>(), It.IsAny <long>())) .Returns(false); // Setup stake validator to pass signature validation. this.stakeValidator .Setup(m => m.VerifySignature(It.IsAny <UnspentOutput>(), It.IsAny <Transaction>(), It.IsAny <int>(), It.IsAny <ScriptVerify>())) .Returns(true); // Setup stake validator to fail stake kernel hash validation. this.stakeChain.Setup(m => m.Get(It.IsAny <uint256>())).Returns(new BlockStake()); this.stakeValidator .Setup(m => m.CheckStakeKernelHash(It.IsAny <PosRuleContext>(), It.IsAny <uint>(), It.IsAny <uint256>(), It.IsAny <UnspentOutput>(), It.IsAny <OutPoint>(), It.IsAny <uint>())) .Throws(new ConsensusErrorException(ConsensusErrors.StakeHashInvalidTarget)); // When we run the validation rule, we should hit stake hash invalid target error. Action ruleValidation = () => this.consensusRules.RegisterRule <ProvenHeaderCoinstakeRule>().Run(this.ruleContext); ruleValidation.Should().Throw <ConsensusErrorException>() .And.ConsensusError .Should().Be(ConsensusErrors.StakeHashInvalidTarget); }
public void InvalidMerkleProof_BadMerkleProofErrorIsThrown() { // Setup previous chained header. PosBlock prevPosBlock = new PosBlockBuilder(this.network).Build(); ProvenBlockHeader prevProvenBlockHeader = new ProvenBlockHeaderBuilder(prevPosBlock, this.network).Build(); var previousChainedHeader = new ChainedHeader(prevProvenBlockHeader, prevProvenBlockHeader.GetHash(), null); previousChainedHeader.SetPrivatePropertyValue("Height", this.provenHeadersActivationHeight + 1); // Setup proven header with valid coinstake. PosBlock posBlock = new PosBlockBuilder(this.network).Build(); ProvenBlockHeader provenBlockHeader = new ProvenBlockHeaderBuilder(posBlock, this.network).Build(prevProvenBlockHeader); provenBlockHeader.HashPrevBlock = prevProvenBlockHeader.GetHash(); // Corrupt merkle proof. provenBlockHeader.SetPrivateVariableValue("merkleProof", new PartialMerkleTree(new[] { new uint256(1234) }, new[] { false })); // Setup chained header and move it to the height higher than proven header activation height. this.ruleContext.ValidationContext.ChainedHeaderToValidate = new ChainedHeader(provenBlockHeader, provenBlockHeader.GetHash(), previousChainedHeader); this.ruleContext.ValidationContext.ChainedHeaderToValidate.SetPrivatePropertyValue("Height", this.provenHeadersActivationHeight + 2); // Ensure that coinview returns a UTXO with valid outputs. var utxoOne = new UnspentOutput(prevPosBlock.Transactions[1].Inputs[0].PrevOut, new Coins((uint)previousChainedHeader.Height, new TxOut(), false, true)); // Setup coinstake transaction with a valid stake age. var res = new FetchCoinsResponse(); res.UnspentOutputs.Add(utxoOne.OutPoint, utxoOne); this.coinView .Setup(m => m.FetchCoins(It.IsAny <OutPoint[]>())) .Returns(res); // Setup stake validator to pass stake age check. this.stakeValidator .Setup(m => m.IsConfirmedInNPrevBlocks(It.IsAny <UnspentOutput>(), It.IsAny <ChainedHeader>(), It.IsAny <long>())) .Returns(false); // Setup stake validator to pass signature validation. this.stakeValidator .Setup(m => m.VerifySignature(It.IsAny <UnspentOutput>(), It.IsAny <Transaction>(), It.IsAny <int>(), It.IsAny <ScriptVerify>())) .Returns(true); // Setup stake validator to pass stake kernel hash validation. this.stakeChain.Setup(m => m.Get(It.IsAny <uint256>())).Returns(new BlockStake()); this.stakeValidator .Setup(m => m.CheckStakeKernelHash(It.IsAny <PosRuleContext>(), It.IsAny <uint>(), It.IsAny <uint256>(), It.IsAny <UnspentOutput>(), It.IsAny <OutPoint>(), It.IsAny <uint>())).Returns(true); // When we run the validation rule, we should hit bad merkle proof error. Action ruleValidation = () => this.consensusRules.RegisterRule <ProvenHeaderCoinstakeRule>().Run(this.ruleContext); ruleValidation.Should().Throw <ConsensusErrorException>() .And.ConsensusError .Should().Be(ConsensusErrors.BadMerkleRoot); }
public void TrySetCoins(FetchCoinsResponse coins) { this._Unspents = new Dictionary <uint256, UnspentOutputs>(coins.UnspentOutputs.Length); foreach (var coin in coins.UnspentOutputs) { if (coin != null) { this._Unspents.TryAdd(coin.TransactionId, coin); } } }
/// <inheritdoc /> public async Task <UnspentOutputs> GetUnspentTransactionAsync(uint256 trxid) { this.logger.LogTrace("({0}:'{1}')", nameof(trxid), trxid); FetchCoinsResponse response = await this.coinView.FetchCoinsAsync(new[] { trxid }).ConfigureAwait(false); UnspentOutputs unspentOutputs = response.UnspentOutputs.FirstOrDefault(); this.logger.LogTrace("(-):{0}", unspentOutputs); return(unspentOutputs); }
/// <summary> /// Gets and validates unspent outputs based of coins fetched from coin view. /// </summary> /// <param name="coins">The coins.</param> /// <exception cref="ConsensusException"> /// Throws exception with error <see cref="ConsensusErrors.ReadTxPrevFailed" /> if check fails. /// </exception> private UnspentOutputs GetAndValidatePreviousUtxo(FetchCoinsResponse coins) { UnspentOutputs prevUtxo = coins.UnspentOutputs[0]; if (prevUtxo == null) { this.Logger.LogTrace("(-)[PREV_UTXO_IS_NULL]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } return(prevUtxo); }
/// <summary> /// Fetches and validates coins from coins view. /// </summary> /// <param name="header">The header.</param> /// <exception cref="ConsensusException"> /// Throws exception with error <see cref="ConsensusErrors.ReadTxPrevFailed" /> if check fails. /// </exception> private FetchCoinsResponse GetAndValidateCoins(ProvenBlockHeader header) { // First try finding the previous transaction in database. TxIn txIn = header.Coinstake.Inputs[0]; FetchCoinsResponse coins = this.PosParent.UtxoSet.FetchCoinsAsync(new[] { txIn.PrevOut.Hash }).GetAwaiter().GetResult(); if ((coins == null) || (coins.UnspentOutputs.Length != 1)) { ConsensusErrors.ReadTxPrevFailed.Throw(); } return(coins); }
/// <inheritdoc /> public GetSenderResult GetSender(Transaction tx, ICoinView coinView, IList <Transaction> blockTxs) { OutPoint prevOut = tx.Inputs[0].PrevOut; // Check the txes in this block first if (blockTxs != null && blockTxs.Count > 0) { foreach (Transaction btx in blockTxs) { if (btx.GetHash() == prevOut.Hash) { if (prevOut.N >= btx.Outputs.Count) { return(GetSenderResult.CreateFailure(InvalidOutputIndex)); } Script script = btx.Outputs[prevOut.N].ScriptPubKey; return(this.GetAddressFromScript(script)); } } } // Check the utxoset for the p2pk of the unspent output for this transaction if (coinView != null) { FetchCoinsResponse fetchCoinResult = coinView.FetchCoins(new OutPoint[] { prevOut }); // The result from the coinview should never be null, so we do not check for that condition here. // It will simply not contain the requested outputs in the dictionary if they did not exist in the coindb. if (fetchCoinResult.UnspentOutputs.All(o => o.Key != prevOut)) { return(GetSenderResult.CreateFailure(OutputsNotInCoinView)); } UnspentOutput unspentOutputs = fetchCoinResult.UnspentOutputs.First(o => o.Key == prevOut).Value; // Since we now fetch a specific UTXO from the coindb instead of an entire transaction, it is no longer meaningful to check // (for the coindb at least - block transactions are handled separately above) whether the prevOut index is within bounds. // So that check has been removed from here and we proceed directly to checking spent-ness. if (unspentOutputs.Coins == null) { return(GetSenderResult.CreateFailure(OutputAlreadySpent)); } TxOut senderOutput = unspentOutputs.Coins.TxOut; return(this.GetAddressFromScript(senderOutput.ScriptPubKey)); } return(GetSenderResult.CreateFailure(UnableToGetSender)); }
public bool Exists(Coin c) { FetchCoinsResponse result = this.coinView.FetchCoins(new[] { c.Outpoint.Hash }); if (result.BlockHash != this.hash) { throw new InvalidOperationException("Unexepected hash"); } if (result.UnspentOutputs[0] == null) { return(false); } return(result.UnspentOutputs[0].IsAvailable(c.Outpoint.N)); }
/// <inheritdoc /> public GetSenderResult GetSender(Transaction tx, ICoinView coinView, IList <Transaction> blockTxs) { OutPoint prevOut = tx.Inputs[0].PrevOut; // Check the txes in this block first if (blockTxs != null && blockTxs.Count > 0) { foreach (Transaction btx in blockTxs) { if (btx.GetHash() == prevOut.Hash) { if (prevOut.N >= btx.Outputs.Count) { return(GetSenderResult.CreateFailure(InvalidOutputIndex)); } Script script = btx.Outputs[prevOut.N].ScriptPubKey; return(this.GetAddressFromScript(script)); } } } // Check the utxoset for the p2pk of the unspent output for this transaction if (coinView != null) { FetchCoinsResponse fetchCoinResult = coinView.FetchCoinsAsync(new uint256[] { prevOut.Hash }).Result; UnspentOutputs unspentOutputs = fetchCoinResult.UnspentOutputs.FirstOrDefault(); if (unspentOutputs == null) { return(GetSenderResult.CreateFailure(OutputsNotInCoinView)); } if (prevOut.N >= unspentOutputs.Outputs.Length) { return(GetSenderResult.CreateFailure(InvalidOutputIndex)); } TxOut senderOutput = unspentOutputs.Outputs[prevOut.N]; if (senderOutput == null) { return(GetSenderResult.CreateFailure(OutputAlreadySpent)); } return(this.GetAddressFromScript(senderOutput.ScriptPubKey)); } return(GetSenderResult.CreateFailure(UnableToGetSender)); }
public void Constructor_InitializesClass() { var blockHash = new uint256(124); var unspentOutputs = new UnspentOutputs[] { new UnspentOutputs(1, new Transaction()), new UnspentOutputs(2, new Transaction()) }; var fetchCoinsResponse = new FetchCoinsResponse(unspentOutputs, blockHash); Assert.Equal(2, fetchCoinsResponse.UnspentOutputs.Length); Assert.Equal((uint)1, fetchCoinsResponse.UnspentOutputs[0].Height); Assert.Equal((uint)2, fetchCoinsResponse.UnspentOutputs[1].Height); Assert.Equal(blockHash, fetchCoinsResponse.BlockHash); }
/// <inheritdoc/> public void CheckProofOfStake(ContextStakeInformation context, ChainedHeader prevChainedHeader, BlockStake prevBlockStake, Transaction transaction, uint headerBits) { this.logger.LogTrace("({0}:'{1}',{2}.{3}:'{4}',{5}:0x{6:X})", nameof(prevChainedHeader), prevChainedHeader.HashBlock, nameof(prevBlockStake), nameof(prevBlockStake.HashProof), prevBlockStake.HashProof, nameof(headerBits), headerBits); if (!transaction.IsCoinStake) { this.logger.LogTrace("(-)[NO_COINSTAKE]"); ConsensusErrors.NonCoinstake.Throw(); } TxIn txIn = transaction.Inputs[0]; // First try finding the previous transaction in database. FetchCoinsResponse coins = this.coinView.FetchCoinsAsync(new[] { txIn.PrevOut.Hash }).GetAwaiter().GetResult(); if ((coins == null) || (coins.UnspentOutputs.Length != 1)) { ConsensusErrors.ReadTxPrevFailed.Throw(); } UnspentOutputs prevUtxo = coins.UnspentOutputs[0]; if (prevUtxo == null) { this.logger.LogTrace("(-)[PREV_UTXO_IS_NULL]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } // Verify signature. if (!this.VerifySignature(prevUtxo, transaction, 0, ScriptVerify.None)) { this.logger.LogTrace("(-)[BAD_SIGNATURE]"); ConsensusErrors.CoinstakeVerifySignatureFailed.Throw(); } // Min age requirement. if (this.IsConfirmedInNPrevBlocks(prevUtxo, prevChainedHeader, this.consensusOptions.GetStakeMinConfirmations(prevChainedHeader.Height + 1, this.network) - 1)) { this.logger.LogTrace("(-)[BAD_STAKE_DEPTH]"); ConsensusErrors.InvalidStakeDepth.Throw(); } context.CoinAge = this.GetCoinAge(transaction, prevChainedHeader); this.CheckStakeKernelHash(context, headerBits, prevBlockStake, prevUtxo, txIn.PrevOut, transaction.Time); this.logger.LogTrace("(-)[OK]"); }
public void Constructor_InitializesClass() { var unspentOutputs = new UnspentOutput[] { new UnspentOutput(new OutPoint(new Transaction(), 0), new Coins(1, new TxOut(), false)), new UnspentOutput(new OutPoint(new Transaction(), 1), new Coins(2, new TxOut(), false)) }; var fetchCoinsResponse = new FetchCoinsResponse(); fetchCoinsResponse.UnspentOutputs.Add(unspentOutputs[0].OutPoint, unspentOutputs[0]); fetchCoinsResponse.UnspentOutputs.Add(unspentOutputs[1].OutPoint, unspentOutputs[1]); Assert.Equal(2, fetchCoinsResponse.UnspentOutputs.Count); Assert.Equal((uint)1, fetchCoinsResponse.UnspentOutputs.ToList()[0].Value.Coins.Height); Assert.Equal((uint)2, fetchCoinsResponse.UnspentOutputs.ToList()[1].Value.Coins.Height); }
public void RunRule_ProvenHeadersActive_And_InvalidStakeDepth_StakeDepthErrorIsThrown() { // Setup previous chained header. PosBlock prevPosBlock = new PosBlockBuilder(this.network).Build(); ProvenBlockHeader prevProvenBlockHeader = new ProvenBlockHeaderBuilder(prevPosBlock, this.network).Build(); var previousChainedHeader = new ChainedHeader(prevProvenBlockHeader, prevProvenBlockHeader.GetHash(), null); previousChainedHeader.SetPrivatePropertyValue("Height", this.provenHeadersActivationHeight + 1); // Setup proven header with valid coinstake. PosBlock posBlock = new PosBlockBuilder(this.network).Build(); ProvenBlockHeader provenBlockHeader = new ProvenBlockHeaderBuilder(posBlock, this.network).Build(); provenBlockHeader.PosBlockHeader.HashPrevBlock = prevProvenBlockHeader.GetHash(); if (provenBlockHeader.Coinstake is IPosTransactionWithTime posTrx) { posTrx.Time = provenBlockHeader.Time; } // Setup chained header and move it to the height higher than proven header activation height. this.ruleContext.ValidationContext.ChainedHeaderToValidate = new ChainedHeader(provenBlockHeader, provenBlockHeader.GetHash(), previousChainedHeader); this.ruleContext.ValidationContext.ChainedHeaderToValidate.SetPrivatePropertyValue("Height", this.provenHeadersActivationHeight + 2); // Ensure that coinview returns a UTXO with valid outputs. var utxoOne = new UnspentOutput(prevPosBlock.Transactions[1].Inputs[0].PrevOut, new Coins((uint)previousChainedHeader.Height, new TxOut(), false, true)); // Setup coinstake transaction with an invalid stake age. var res = new FetchCoinsResponse(); res.UnspentOutputs.Add(utxoOne.OutPoint, utxoOne); this.coinView .Setup(m => m.FetchCoins(It.IsAny <OutPoint[]>())) .Returns(res); // Setup stake validator to fail stake age check. this.stakeValidator .Setup(m => m.IsConfirmedInNPrevBlocks(It.IsAny <UnspentOutput>(), It.IsAny <ChainedHeader>(), It.IsAny <long>())) .Returns(true); // When we run the validation rule, we should hit coinstake depth error. Action ruleValidation = () => this.consensusRules.RegisterRule <ProvenHeaderCoinstakeRule>().Run(this.ruleContext); ruleValidation.Should().Throw <ConsensusErrorException>() .And.ConsensusError .Should().Be(ConsensusErrors.InvalidStakeDepth); }
/// <inheritdoc/> public bool CheckKernel(PosRuleContext context, ChainedHeader prevChainedHeader, uint headerBits, long transactionTime, OutPoint prevout) { Guard.NotNull(context, nameof(context)); Guard.NotNull(prevout, nameof(prevout)); Guard.NotNull(prevChainedHeader, nameof(prevChainedHeader)); FetchCoinsResponse coins = this.coinView.FetchCoins(new[] { prevout.Hash }); if ((coins == null) || (coins.UnspentOutputs.Length != 1)) { this.logger.LogTrace("(-)[READ_PREV_TX_FAILED]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } ChainedHeader prevBlock = this.chainIndexer.GetHeader(coins.BlockHash); if (prevBlock == null) { this.logger.LogTrace("(-)[REORG]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } UnspentOutputs prevUtxo = coins.UnspentOutputs[0]; if (prevUtxo == null) { this.logger.LogTrace("(-)[PREV_UTXO_IS_NULL]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } if (this.IsConfirmedInNPrevBlocks(prevUtxo, prevChainedHeader, this.GetTargetDepthRequired(prevChainedHeader))) { this.logger.LogTrace("(-)[LOW_COIN_AGE]"); ConsensusErrors.InvalidStakeDepth.Throw(); } BlockStake prevBlockStake = this.stakeChain.Get(prevChainedHeader.HashBlock); if (prevBlockStake == null) { this.logger.LogTrace("(-)[BAD_STAKE_BLOCK]"); ConsensusErrors.BadStakeBlock.Throw(); } return(this.CheckStakeKernelHash(context, headerBits, prevBlockStake.StakeModifierV2, prevUtxo, prevout, (uint)transactionTime)); }
/// <inheritdoc /> public FetchCoinsResponse FetchCoins(OutPoint[] txIds) { Guard.NotNull(txIds, nameof(txIds)); using (this.lockobj.LockRead()) { var result = new FetchCoinsResponse(); for (int i = 0; i < txIds.Length; i++) { var output = this.unspents.TryGet(txIds[i]); result.UnspentOutputs.Add(output.OutPoint, output); } return(result); } }
/// <inheritdoc /> public override async Task RunAsync(RuleContext context) { // Load the UTXO set of the current block. UTXO may be loaded from cache or from disk. // The UTXO set is stored in the context. this.Logger.LogTrace("Loading UTXO set of the new block."); context.Set = new UnspentOutputSet(); using (new StopwatchDisposable(o => this.Parent.PerformanceCounter.AddUTXOFetchingTime(o))) { uint256[] ids = this.GetIdsToFetch(context.BlockValidationContext.Block, context.Flags.EnforceBIP30); FetchCoinsResponse coins = await this.PowParent.UtxoSet.FetchCoinsAsync(ids).ConfigureAwait(false); context.Set.SetCoins(coins.UnspentOutputs); } // Attempt to load into the cache the next set of UTXO to be validated. // The task is not awaited so will not stall main validation process. this.TryPrefetchAsync(context.Flags); }
public void CheckKernel(ContextInformation context, ChainedBlock pindexPrev, uint nBits, long nTime, OutPoint prevout, ref long pBlockTime) { this.logger.LogTrace("({0}:'{1}',{2}:0x{3:X},{4}:{5},{6}:'{7}.{8}')", nameof(pindexPrev), pindexPrev, nameof(nBits), nBits, nameof(nTime), nTime, nameof(prevout), prevout.Hash, prevout.N); // TODO: https://github.com/stratisproject/StratisBitcoinFullNode/issues/397 FetchCoinsResponse coins = this.coinView.FetchCoinsAsync(new[] { prevout.Hash }).GetAwaiter().GetResult(); if ((coins == null) || (coins.UnspentOutputs.Length != 1)) { this.logger.LogTrace("(-)[READ_PREV_TX_FAILED]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } ChainedBlock prevBlock = this.chain.GetBlock(coins.BlockHash); if (prevBlock == null) { this.logger.LogTrace("(-)[REORG]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } UnspentOutputs prevUtxo = coins.UnspentOutputs[0]; if (this.IsConfirmedInNPrevBlocks(prevUtxo, pindexPrev, this.consensusOptions.StakeMinConfirmations - 1)) { this.logger.LogTrace("(-)[LOW_COIN_AGE]"); ConsensusErrors.InvalidStakeDepth.Throw(); } BlockStake prevBlockStake = this.stakeChain.Get(pindexPrev.HashBlock); if (prevBlockStake == null) { this.logger.LogTrace("(-)[BAD_STAKE_BLOCK]"); ConsensusErrors.BadStakeBlock.Throw(); } pBlockTime = prevBlock.Header.Time; this.CheckStakeKernelHash(context, pindexPrev, nBits, prevBlock.Header.Time, prevBlockStake, prevUtxo, prevout, (uint)nTime); this.logger.LogTrace("(-):{0}={1}", nameof(pBlockTime), pBlockTime); }
public void CheckProofOfStake(ContextInformation context, ChainedBlock pindexPrev, BlockStake prevBlockStake, Transaction tx, uint headerBits) { this.logger.LogTrace("({0}:'{1}',{2}.{3}:'{4}',{5}:0x{6:X})", nameof(pindexPrev), pindexPrev.HashBlock, nameof(prevBlockStake), nameof(prevBlockStake.HashProof), prevBlockStake.HashProof, nameof(headerBits), headerBits); if (!tx.IsCoinStake) { this.logger.LogTrace("(-)[NO_COINSTAKE]"); ConsensusErrors.NonCoinstake.Throw(); } // Kernel (input 0) must match the stake hash target per coin age (nBits). TxIn txIn = tx.Inputs[0]; // First try finding the previous transaction in database. FetchCoinsResponse coins = this.coinView.FetchCoinsAsync(new[] { txIn.PrevOut.Hash }).GetAwaiter().GetResult(); if ((coins == null) || (coins.UnspentOutputs.Length != 1)) { ConsensusErrors.ReadTxPrevFailed.Throw(); } ChainedBlock prevBlock = this.chain.GetBlock(coins.BlockHash); UnspentOutputs prevUtxo = coins.UnspentOutputs[0]; // Verify signature. if (!this.VerifySignature(prevUtxo, tx, 0, ScriptVerify.None)) { this.logger.LogTrace("(-)[BAD_SIGNATURE]"); ConsensusErrors.CoinstakeVerifySignatureFailed.Throw(); } // Min age requirement. if (this.IsConfirmedInNPrevBlocks(prevUtxo, pindexPrev, this.consensusOptions.StakeMinConfirmations - 1)) { this.logger.LogTrace("(-)[BAD_STAKE_DEPTH]"); ConsensusErrors.InvalidStakeDepth.Throw(); } this.CheckStakeKernelHash(context, pindexPrev, headerBits, prevBlock.Header.Time, prevBlockStake, prevUtxo, txIn.PrevOut, tx.Time); this.logger.LogTrace("(-)[OK]"); }
/// <summary> /// Load the coin view for a memory pool transaction. This should only be called /// inside the memory pool lock. /// </summary> /// <param name="trx">Memory pool transaction.</param> public void LoadViewLocked(Transaction trx) { // lookup all ids (duplicate ids are ignored in case a trx spends outputs from the same parent). List <uint256> ids = trx.Inputs.Select(n => n.PrevOut.Hash).Distinct().Concat(new[] { trx.GetHash() }).ToList(); FetchCoinsResponse coins = this.Inner.FetchCoins(ids.ToArray()); // find coins currently in the mempool List <Transaction> mempoolcoins = this.memPool.MapTx.Values.Where(t => ids.Contains(t.TransactionHash)).Select(s => s.Transaction).ToList(); IEnumerable <UnspentOutputs> memOutputs = mempoolcoins.Select(s => new UnspentOutputs(TxMempool.MempoolHeight, s)); coins = new FetchCoinsResponse(coins.UnspentOutputs.Concat(memOutputs).ToArray(), coins.BlockHash); // the UTXO set might have been updated with a recently received block // but the block has not yet arrived to the mempool and remove the pending trx // from the pool (a race condition), block validation doesn't lock the mempool. // its safe to ignore duplicats on the UTXO set as duplicates mean a trx is in // a block and the block will soon remove the trx from the pool. this.Set.TrySetCoins(coins.UnspentOutputs); }
/// <summary> /// Load the coin view for a memory pool transaction. This should only be called /// inside the memory pool lock. /// </summary> /// <param name="trx">Memory pool transaction.</param> public void LoadViewLocked(Transaction trx) { // fetch outputs forn disk OutPoint[] outputs = trx.Inputs.Select(n => n.PrevOut).ToArray(); FetchCoinsResponse coins = this.Inner.FetchCoins(outputs); // lookup all ids (duplicate ids are ignored in case a trx spends outputs from the same parent). List <uint256> ids = trx.Inputs.Select(n => n.PrevOut.Hash).Distinct().Concat(new[] { trx.GetHash() }).ToList(); // find coins currently in the mempool foreach (uint256 trxid in ids) { if (this.memPool.MapTx.TryGetValue(trxid, out TxMempoolEntry entry)) { foreach (IndexedTxOut txOut in entry.Transaction.Outputs.AsIndexedOutputs()) { // If an output was fetched form disk with empty coins but it // was found mempool then override the output with whats in mempool var outpoint = new OutPoint(trxid, txOut.N); var found = coins.UnspentOutputs.TryGetValue(outpoint, out UnspentOutput unspentOutput); if (!found || unspentOutput?.Coins == null) { if (unspentOutput?.Coins == null) { coins.UnspentOutputs.Remove(outpoint); } coins.UnspentOutputs.Add(outpoint, new UnspentOutput(outpoint, new Coins(TxMempool.MempoolHeight, txOut.TxOut, entry.Transaction.IsCoinBase, this.network.Consensus.IsProofOfStake ? entry.Transaction.IsCoinStake : false))); } } } } // the UTXO set might have been updated with a recently received block // but the block has not yet arrived to the mempool and remove the pending trx // from the pool (a race condition), block validation doesn't lock the mempool. // its safe to ignore duplicats on the UTXO set as duplicates mean a trx is in // a block and the block will soon remove the trx from the pool. this.Set.TrySetCoins(coins.UnspentOutputs.Values.ToArray()); }
public FetchCoinsResponse FetchCoins(OutPoint[] utxos) { FetchCoinsResponse res = new FetchCoinsResponse(); using (new StopwatchDisposable(o => this.performanceCounter.AddQueryTime(o))) { this.performanceCounter.AddQueriedEntities(utxos.Length); foreach (OutPoint outPoint in utxos) { byte[] row = this.leveldb.Get(new byte[] { coinsTable }.Concat(outPoint.ToBytes()).ToArray()); Coins outputs = row != null?this.dataStoreSerializer.Deserialize <Coins>(row) : null; this.logger.LogDebug("Outputs for '{0}' were {1}.", outPoint, outputs == null ? "NOT loaded" : "loaded"); res.UnspentOutputs.Add(outPoint, new UnspentOutput(outPoint, outputs)); } } return(res); }