/// <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)); var coins = this.coinView.FetchCoins(new[] { prevout.Hash }); if (coins == null || coins.UnspentOutputs.Length != 1) { this.logger.LogTrace("(-)[READ_PREV_TX_FAILED]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } var prevBlock = this.chainIndexer.GetHeader(coins.BlockHash); if (prevBlock == null) { this.logger.LogTrace("(-)[REORG]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } var prevUtxo = coins.UnspentOutputs[0]; if (prevUtxo == null) { this.logger.LogTrace("(-)[PREV_UTXO_IS_NULL]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } if (IsConfirmedInNPrevBlocks(prevUtxo, prevChainedHeader, GetTargetDepthRequired(prevChainedHeader))) { this.logger.LogTrace("(-)[LOW_COIN_AGE]"); ConsensusErrors.InvalidStakeDepth.Throw(); } var prevBlockStake = this.stakeChain.Get(prevChainedHeader.HashBlock); if (prevBlockStake == null) { this.logger.LogTrace("(-)[BAD_STAKE_BLOCK]"); ConsensusErrors.BadStakeBlock.Throw(); } return(CheckStakeKernelHash(context, headerBits, prevBlockStake.StakeModifierV2, prevUtxo, prevout, (uint)transactionTime)); }
/// <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(); } var txIn = transaction.Inputs[0]; var prevUtxo = context.UnspentOutputSet.AccessCoins(txIn.PrevOut.Hash); if (prevUtxo == null) { this.logger.LogTrace("(-)[PREV_UTXO_IS_NULL]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } // Verify signature. if (!VerifySignature(prevUtxo, transaction, 0, ScriptVerify.None)) { this.logger.LogTrace("(-)[BAD_SIGNATURE]"); ConsensusErrors.CoinstakeVerifySignatureFailed.Throw(); } // Min age requirement. if (IsConfirmedInNPrevBlocks(prevUtxo, prevChainedHeader, GetTargetDepthRequired(prevChainedHeader))) { this.logger.LogTrace("(-)[BAD_STAKE_DEPTH]"); ConsensusErrors.InvalidStakeDepth.Throw(); } if (!CheckStakeKernelHash(context, headerBits, prevBlockStake.StakeModifierV2, prevUtxo, txIn.PrevOut, context.ValidationContext.ChainedHeaderToValidate.Header.Time)) { this.logger.LogTrace("(-)[INVALID_STAKE_HASH_TARGET]"); ConsensusErrors.StakeHashInvalidTarget.Throw(); } }
/// <inheritdoc /> public bool CheckStakeKernelHash(PosRuleContext context, uint headerBits, uint256 prevStakeModifier, UnspentOutputs stakingCoins, OutPoint prevout, uint transactionTime) { Guard.NotNull(context, nameof(context)); Guard.NotNull(prevout, nameof(prevout)); Guard.NotNull(stakingCoins, nameof(stakingCoins)); if (transactionTime < stakingCoins.Time) { this.logger.LogDebug("Coinstake transaction timestamp {0} is lower than it's own UTXO timestamp {1}.", transactionTime, stakingCoins.Time); this.logger.LogTrace("(-)[BAD_STAKE_TIME]"); ConsensusErrors.StakeTimeViolation.Throw(); } // Base target. var target = new Target(headerBits).ToBigInteger(); // TODO: Investigate: // The POS protocol should probably put a limit on the max amount that can be staked // not a hard limit but a limit that allow any amount to be staked with a max weight value. // the max weight should not exceed the max uint256 array size (array size = 32). // Weighted target. var valueIn = stakingCoins.Outputs[prevout.N].Value.Satoshi; var weight = BigInteger.ValueOf(valueIn); var weightedTarget = target.Multiply(weight); context.TargetProofOfStake = ToUInt256(weightedTarget); this.logger.LogDebug("POS target is '{0}', weighted target for {1} coins is '{2}'.", ToUInt256(target), valueIn, context.TargetProofOfStake); if (this.legacytimefield == null) { this.legacytimefield = this.network.Consensus.ConsensusFactory.CreateTransaction() is IPosTransactionWithTime; } // Calculate hash. using (var ms = new MemoryStream()) { var serializer = new BitcoinStream(ms, true); serializer.ReadWrite(prevStakeModifier); if (this.legacytimefield == true) // to stay compatible with legacy ODN { serializer.ReadWrite(stakingCoins.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); }