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);
            }
        }
示例#2
0
        /// <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"));
        }
示例#3
0
        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);
        }
示例#4
0
        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);
        }
示例#5
0
        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);
        }
示例#8
0
        /// <inheritdoc />
        public async Task <UnspentOutputs> GetUnspentTransactionAsync(uint256 trxid)
        {
            FetchCoinsResponse response = this.coinView.FetchCoins(new[] { trxid });

            UnspentOutputs unspentOutputs = response.UnspentOutputs.FirstOrDefault();

            return(unspentOutputs);
        }
示例#9
0
        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);
        }
示例#10
0
        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);
        }
示例#11
0
        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);
        }
示例#12
0
 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);
         }
     }
 }
示例#13
0
        /// <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);
        }
示例#16
0
        /// <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));
        }
示例#17
0
        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));
        }
示例#19
0
        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);
        }
示例#20
0
        /// <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);
        }
示例#22
0
        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);
        }
示例#23
0
        /// <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);
            }
        }
示例#25
0
        /// <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);
        }
示例#27
0
        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);
        }
示例#29
0
        /// <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());
        }
示例#30
0
        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);
        }