/// <see cref="IStakeValidator.CheckStakeKernelHash" />
        /// <exception cref="ConsensusException">
        ///     Throws exception with error <see cref="ConsensusErrors.PrevStakeNull" /> if check fails.
        /// </exception>
        void CheckStakeKernelHash(PosRuleContext context, UnspentOutputs stakingCoins, ProvenBlockHeader header,
                                  ChainedHeader chainedHeader)
        {
            var prevOut         = GetPreviousOut(header);
            var transactionTime = header.Time;
            var headerBits      = chainedHeader.Header.Bits.ToCompact();

            var previousStakeModifier = GetPreviousStakeModifier(chainedHeader);

            if (header.Coinstake.IsCoinStake)
            {
                this.Logger.LogDebug("Found coinstake checking kernal hash.");

                var validKernel = this.stakeValidator.CheckStakeKernelHash(context, headerBits, previousStakeModifier,
                                                                           stakingCoins, prevOut, transactionTime);

                if (!validKernel)
                {
                    this.Logger.LogTrace("(-)[INVALID_STAKE_HASH_TARGET]");
                    ConsensusErrors.StakeHashInvalidTarget.Throw();
                }
            }

            ComputeNextStakeModifier(header, chainedHeader, previousStakeModifier);
        }
Beispiel #2
0
        /// <summary>
        /// Gets and validates unspent outputs based of coins fetched from coin view.
        /// </summary>
        /// <param name="header">The header.</param>
        /// <param name="context">Rule context.</param>
        /// <returns>The validated previous <see cref="UnspentOutput"/></returns>
        private UnspentOutput GetAndValidatePreviousUtxo(ProvenBlockHeader header, PosRuleContext context)
        {
            // First try and find the previous trx in the database.
            TxIn txIn = header.Coinstake.Inputs[0];

            UnspentOutput prevUtxo = null;

            FetchCoinsResponse coins = this.PosParent.UtxoSet.FetchCoins(new[] { txIn.PrevOut });

            prevUtxo = coins.UnspentOutputs[txIn.PrevOut];
            if (prevUtxo?.Coins == null)
            {
                // We did not find the previous trx in the database, look in rewind data.
                prevUtxo = this.CheckIfCoinstakeIsSpentOnAnotherChain(header, context);
            }
            else
            {
                // The trx was found now check if the UTXO is spent.
                TxOut utxo = prevUtxo.Coins.TxOut;

                if (utxo == null)
                {
                    // UTXO is spent so find it in rewind data.
                    prevUtxo = this.CheckIfCoinstakeIsSpentOnAnotherChain(header, context);
                }
            }

            return(prevUtxo);
        }
        /// <see cref="IStakeValidator.CheckStakeKernelHash"/>
        /// <exception cref="ConsensusException">
        /// Throws exception with error <see cref="ConsensusErrors.PrevStakeNull" /> if check fails.
        /// </exception>
        private void CheckStakeKernelHash(PosRuleContext context, UnspentOutputs stakingCoins, ProvenBlockHeader header, ChainedHeader chainedHeader)
        {
            OutPoint prevOut         = this.GetPreviousOut(header);
            uint     transactionTime = header.Coinstake.Time;
            uint     headerBits      = chainedHeader.Header.Bits.ToCompact();

            uint256 previousStakeModifier;

            if (chainedHeader.Height == this.ProvenHeadersActivationHeight)
            {
                // If we are validating the first Proven Header, we don't have a StakeModifierV2 available and I should compute it but I can't.
                // Proposed solution is to store the StakeModifierV2 value of the header at the height of ProvenHeaderActivation in a consensus variable
                // called ProvenHeaderActivationStakeModifier and so I can return it
                previousStakeModifier = uint256.Zero; //TODO to be changed after ProvenHeaderActivationStakeModifier has been created.
            }
            else
            {
                ProvenBlockHeader previousProvenHeader = chainedHeader.Previous.Header as ProvenBlockHeader;
                previousStakeModifier = previousProvenHeader.StakeModifierV2;
            }

            this.stakeValidator.CheckStakeKernelHash(context, headerBits, previousStakeModifier, stakingCoins, prevOut, transactionTime);

            // Computes the stake modifier and sets the value to the current validating proven header, to retain it for next header validation as previousStakeModifier.
            uint256 currentStakeModifier = this.stakeValidator.ComputeStakeModifierV2(chainedHeader.Previous, previousStakeModifier, header.Coinstake.Inputs[0].PrevOut.Hash);

            header.StakeModifierV2 = currentStakeModifier;
        }
        /// <inheritdoc/>
        public bool CheckStakeKernelHash(PosRuleContext context, uint headerBits, uint256 prevStakeModifier, UnspentOutput stakingCoins, OutPoint prevout, uint transactionTime)
        {
            Guard.NotNull(context, nameof(context));
            Guard.NotNull(prevout, nameof(prevout));
            Guard.NotNull(stakingCoins, nameof(stakingCoins));

            if (transactionTime < stakingCoins.Coins.Time)
            {
                this.logger.LogDebug("Coinstake transaction timestamp {0} is lower than it's own UTXO timestamp {1}.", transactionTime, stakingCoins.Coins.Time);
                this.logger.LogTrace("(-)[BAD_STAKE_TIME]");
                ConsensusErrors.StakeTimeViolation.Throw();
            }

            // Base target.
            BigInteger target = new Target(headerBits).ToBigInteger();

            // Weighted target.
            long       valueIn        = stakingCoins.Coins.TxOut.Value.Satoshi;
            BigInteger weight         = BigInteger.ValueOf(valueIn);
            BigInteger weightedTarget = target.Multiply(weight);

            context.TargetProofOfStake = this.ToUInt256(weightedTarget);
            this.logger.LogDebug("POS target is '{0}', weighted target for {1} coins is '{2}'.", this.ToUInt256(target), valueIn, context.TargetProofOfStake);

            // Calculate hash.
            using (var ms = new MemoryStream())
            {
                var serializer = new BitcoinStream(ms, true);
                serializer.ReadWrite(prevStakeModifier);
                if (this.network.Consensus.PosUseTimeFieldInKernalHash) // old posv3 time field
                {
                    serializer.ReadWrite(stakingCoins.Coins.Time);
                }
                serializer.ReadWrite(prevout.Hash);
                serializer.ReadWrite(prevout.N);
                serializer.ReadWrite(transactionTime);

                context.HashProofOfStake = Hashes.Hash256(ms.ToArray());
            }

            this.logger.LogDebug("Stake modifier V2 is '{0}', hash POS is '{1}'.", prevStakeModifier, context.HashProofOfStake);

            // Now check if proof-of-stake hash meets target protocol.
            var hashProofOfStakeTarget = new BigInteger(1, context.HashProofOfStake.ToBytes(false));

            if (hashProofOfStakeTarget.CompareTo(weightedTarget) > 0)
            {
                this.logger.LogTrace("(-)[TARGET_MISSED]");
                return(false);
            }

            return(true);
        }
        /// <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 });

            if ((coins == null) || (coins.UnspentOutputs.Count != 1))
            {
                this.logger.LogTrace("(-)[READ_PREV_TX_FAILED]");
                ConsensusErrors.ReadTxPrevFailed.Throw();
            }

            ChainedHeader prevBlock = this.chainIndexer.GetHeader(this.coinView.GetTipHash().Hash);

            if (prevBlock == null)
            {
                this.logger.LogTrace("(-)[REORG]");
                ConsensusErrors.ReadTxPrevFailed.Throw();
            }

            UnspentOutput prevUtxo = coins.UnspentOutputs.Single().Value;

            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 />
        protected override void ProcessRule(PosRuleContext context, ChainedHeader chainedHeader,
                                            ProvenBlockHeader header)
        {
            CheckCoinstakeIsNotNull(header);

            // In case we are in PoW era there might be no coinstake tx.
            // We have no way of telling if the block was supposed to be PoW or PoS so attacker can trick us into thinking that all of them are PoW so no PH is required.
            if (!header.Coinstake.IsCoinStake)
            {
                // If the header represents a POW block we don't do any validation of stake.
                // We verify the header is not passed the last pow height.
                if (chainedHeader.Height > this.Parent.Network.Consensus.LastPOWBlock)
                {
                    this.Logger.LogTrace("(-)[POW_TOO_HIGH]");
                    ConsensusErrors.ProofOfWorkTooHigh.Throw();
                }

                if (!header.CheckProofOfWork())
                {
                    this.Logger.LogTrace("(-)[HIGH_HASH]");
                    ConsensusErrors.HighHash.Throw();
                }

                ComputeNextStakeModifier(header, chainedHeader);

                this.Logger.LogTrace("(-)[POW_ERA]");
                return;
            }

            CheckIfCoinstakeIsTrue(header);

            CheckHeaderAndCoinstakeTimes(header);

            var prevUtxo = GetAndValidatePreviousUtxo(header, context);

            CheckCoinstakeAgeRequirement(chainedHeader, prevUtxo);

            CheckSignature(header, prevUtxo);

            CheckStakeKernelHash(context, prevUtxo, header, chainedHeader);

            CheckCoinstakeMerkleProof(header);

            CheckHeaderSignatureWithCoinstakeKernel(header);
        }
        /// <inheritdoc/>
        public void CheckProofOfStake(PosRuleContext context, ChainedHeader prevChainedHeader, BlockStake prevBlockStake, Transaction transaction, uint headerBits)
        {
            Guard.NotNull(context, nameof(context));
            Guard.NotNull(prevChainedHeader, nameof(prevChainedHeader));
            Guard.NotNull(prevBlockStake, nameof(prevBlockStake));
            Guard.NotNull(transaction, nameof(transaction));

            if (!transaction.IsCoinStake)
            {
                this.logger.LogTrace("(-)[NO_COINSTAKE]");
                ConsensusErrors.NonCoinstake.Throw();
            }

            TxIn txIn = transaction.Inputs[0];

            UnspentOutput prevUtxo = context.UnspentOutputSet.AccessCoins(txIn.PrevOut);

            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.GetTargetDepthRequired(prevChainedHeader)))
            {
                this.logger.LogTrace("(-)[BAD_STAKE_DEPTH]");
                ConsensusErrors.InvalidStakeDepth.Throw();
            }

            if (!this.CheckStakeKernelHash(context, headerBits, prevBlockStake.StakeModifierV2, prevUtxo, txIn.PrevOut, context.ValidationContext.ChainedHeaderToValidate.Header.Time))
            {
                this.logger.LogTrace("(-)[INVALID_STAKE_HASH_TARGET]");
                ConsensusErrors.StakeHashInvalidTarget.Throw();
            }
        }
Beispiel #8
0
        /// <inheritdoc />
        protected override void ProcessRule(PosRuleContext context, ChainedHeader chainedHeader, ProvenBlockHeader header)
        {
            if ((header.MerkleProofSize == null) || (header.MerkleProofSize > PosConsensusOptions.MaxMerkleProofSerializedSize))
            {
                this.Logger.LogTrace("(-)[PROVEN_HEADER_INVALID_MERKLE_PROOF_SIZE]");
                ConsensusErrors.BadProvenHeaderMerkleProofSize.Throw();
            }

            if ((header.CoinstakeSize == null) || (header.CoinstakeSize > PosConsensusOptions.MaxCoinstakeSerializedSize))
            {
                this.Logger.LogTrace("(-)[PROVEN_HEADER_INVALID_COINSTAKE_SIZE]");
                ConsensusErrors.BadProvenHeaderCoinstakeSize.Throw();
            }

            if ((header.SignatureSize == null) || (header.SignatureSize > PosConsensusOptions.MaxBlockSignatureSerializedSize))
            {
                this.Logger.LogTrace("(-)[PROVEN_HEADER_INVALID_SIGNATURE_SIZE]");
                ConsensusErrors.BadProvenHeaderSignatureSize.Throw();
            }
        }
        /// <inheritdoc />
        protected override void ProcessRule(PosRuleContext context, ChainedHeader chainedHeader, ProvenBlockHeader header)
        {
            this.CheckCoinstakeIsNotNull(header);

            this.CheckIfCoinstakeIsTrue(header);

            this.CheckHeaderAndCoinstakeTimes(header);

            FetchCoinsResponse coins    = this.GetAndValidateCoins(header);
            UnspentOutputs     prevUtxo = this.GetAndValidatePreviousUtxo(coins);

            this.CheckCoinstakeAgeRequirement(chainedHeader, prevUtxo);

            this.CheckSignature(header, prevUtxo);

            this.CheckStakeKernelHash(context, prevUtxo, header, chainedHeader);

            this.CheckCoinstakeMerkleProof(header);

            this.CheckHeaderSignatureWithCoinstakeKernel(header, prevUtxo);
        }
        /// <see cref="IStakeValidator.CheckStakeKernelHash"/>
        /// <exception cref="ConsensusException">
        /// Throws exception with error <see cref="ConsensusErrors.PrevStakeNull" /> if check fails.
        /// </exception>
        private void CheckStakeKernelHash(PosRuleContext context, UnspentOutputs stakingCoins, ProvenBlockHeader header, ChainedHeader chainedHeader)
        {
            OutPoint prevOut         = this.GetPreviousOut(header);
            uint     transactionTime = header.Coinstake.Time;
            uint     headerBits      = chainedHeader.Header.Bits.ToCompact();

            uint256 previousStakeModifier = this.GetPreviousStakeModifier(chainedHeader);

            if (previousStakeModifier == null)
            {
                this.Logger.LogTrace("(-)[MODIF_IS_NULL]");
                ConsensusErrors.InvalidPreviousProvenHeaderStakeModifier.Throw();
            }

            if (header.Coinstake.IsCoinStake)
            {
                this.Logger.LogTrace("Found coinstake checking kernal hash.");
                this.stakeValidator.CheckStakeKernelHash(context, headerBits, previousStakeModifier, stakingCoins, prevOut, transactionTime);
            }

            this.ComputeNextStakeModifier(header, chainedHeader, previousStakeModifier);
        }
        /// <summary>
        /// Gets and validates unspent outputs based of coins fetched from coin view.
        /// </summary>
        /// <param name="header">The header.</param>
        /// <param name="context">Rule context.</param>
        /// <returns>The validated previous <see cref="UnspentOutputs"/></returns>
        private UnspentOutputs GetAndValidatePreviousUtxo(ProvenBlockHeader header, PosRuleContext context)
        {
            // First try and find the previous trx in the database.
            TxIn txIn = header.Coinstake.Inputs[0];

            UnspentOutputs prevUtxo = null;

            FetchCoinsResponse coins = this.PosParent.UtxoSet.FetchCoinsAsync(new[] { txIn.PrevOut.Hash }).GetAwaiter().GetResult();

            if (coins.UnspentOutputs[0] == null)
            {
                // We did not find the previous trx in the database, look in rewind data.
                prevUtxo = this.CheckIfCoinstakeIsSpentOnAnotherChain(header, context);
            }
            else
            {
                // The trx was found now check if the UTXO is spent.
                prevUtxo = coins.UnspentOutputs[0];

                TxOut utxo = null;
                if (txIn.PrevOut.N < prevUtxo.Outputs.Length)
                {
                    // Check that the size of the outs collection is the same as the expected position of the UTXO
                    // Note the collection will not always represent the original size of the transaction unspent
                    // outputs because when we store outputs do disk the last spent items are removed from the collection.
                    utxo = prevUtxo.Outputs[txIn.PrevOut.N];
                }

                if (utxo == null)
                {
                    // UTXO is spent so find it in rewind data.
                    prevUtxo = this.CheckIfCoinstakeIsSpentOnAnotherChain(header, context);
                }
            }

            return(prevUtxo);
        }
Beispiel #12
0
        /// <summary>
        /// Checks if coinstake is spent on another chain.
        /// </summary>
        /// <param name="header">The proven block header.</param>
        /// <param name="context">The POS rule context.</param>
        /// <returns>The <see cref="UnspentOutput"> found in a RewindData</returns>
        private UnspentOutput CheckIfCoinstakeIsSpentOnAnotherChain(ProvenBlockHeader header, PosRuleContext context)
        {
            Transaction coinstake = header.Coinstake;
            TxIn        input     = coinstake.Inputs[0];

            int?rewindDataIndex = this.PosParent.RewindDataIndexCache.Get(input.PrevOut.Hash, (int)input.PrevOut.N);

            if (!rewindDataIndex.HasValue)
            {
                this.Logger.LogTrace("(-)[NO_REWIND_DATA_INDEX_FOR_INPUT_PREVOUT]");
                context.ValidationContext.InsufficientHeaderInformation = true;
                ConsensusErrors.ReadTxPrevFailedInsufficient.Throw();
            }

            RewindData rewindData = this.PosParent.UtxoSet.GetRewindData(rewindDataIndex.Value);

            if (rewindData == null)
            {
                this.Logger.LogTrace("(-)[NO_REWIND_DATA_FOR_INDEX]");
                this.Logger.LogError("Error - Rewind data should always be present");
                throw new ConsensusException("Rewind data should always be present");
            }

            UnspentOutput matchingUnspentUtxo = null;

            foreach (RewindDataOutput rewindDataOutput in rewindData.OutputsToRestore)
            {
                if (rewindDataOutput.OutPoint == input.PrevOut)
                {
                    matchingUnspentUtxo = new UnspentOutput(rewindDataOutput.OutPoint, rewindDataOutput.Coins);
                    break;
                }
            }

            if (matchingUnspentUtxo == null)
            {
                this.Logger.LogTrace("(-)[UTXO_NOT_FOUND_IN_REWIND_DATA]");
                context.ValidationContext.InsufficientHeaderInformation = true;
                ConsensusErrors.UtxoNotFoundInRewindData.Throw();
            }

            return(matchingUnspentUtxo);
        }
Beispiel #13
0
 /// <summary>
 /// Processes the rule.
 /// </summary>
 /// <param name="context">The context.</param>
 /// <param name="chainedHeader">The chained header to be validated.</param>
 /// <param name="header">The Proven Header to be validated.</param>
 protected abstract void ProcessRule(PosRuleContext context, ChainedHeader chainedHeader, ProvenBlockHeader header);
        /// <summary>
        /// Checks if coinstake is spent on another chain.
        /// </summary>
        /// <param name="header">The proven block header.</param>
        /// <param name="context">The POS rule context.</param>
        /// <returns>The <see cref="UnspentOutputs"> found in a RewindData</returns>
        private UnspentOutputs CheckIfCoinstakeIsSpentOnAnotherChain(ProvenBlockHeader header, PosRuleContext context)
        {
            Transaction coinstake = header.Coinstake;
            TxIn        input     = coinstake.Inputs[0];

            int?rewindDataIndex = this.PosParent.RewindDataIndexStore.Get(input.PrevOut.Hash, (int)input.PrevOut.N);

            if (!rewindDataIndex.HasValue)
            {
                this.Logger.LogTrace("(-)[NO_REWIND_DATA_INDEX_FOR_INPUT_PREVOUT]");
                context.ValidationContext.InsufficientHeaderInformation = true;
                ConsensusErrors.ReadTxPrevFailedInsufficient.Throw();
            }

            RewindData rewindData = this.PosParent.UtxoSet.GetRewindData(rewindDataIndex.Value).GetAwaiter().GetResult();

            UnspentOutputs matchingUnspentUtxo = null;

            foreach (UnspentOutputs unspent in rewindData.OutputsToRestore)
            {
                if (unspent.TransactionId == input.PrevOut.Hash)
                {
                    if (input.PrevOut.N < unspent.Outputs.Length)
                    {
                        matchingUnspentUtxo = unspent;
                        break;
                    }
                }
            }

            if (matchingUnspentUtxo == null)
            {
                this.Logger.LogTrace("(-)[UTXO_NOT_FOUND_IN_REWIND_DATA]");
                context.ValidationContext.InsufficientHeaderInformation = true;
                ConsensusErrors.UtxoNotFoundInRewindData.Throw();
            }

            return(matchingUnspentUtxo);
        }