public void CheckProofOfStake(ContextInformation context, ChainedBlock pindexPrev, BlockStake prevBlockStake,
                                      Transaction tx, uint nBits)
        {
            if (!tx.IsCoinStake)
            {
                ConsensusErrors.NonCoinstake.Throw();
            }

            // Kernel (input 0) must match the stake hash target per coin age (nBits)
            var txIn = tx.Inputs[0];

            // First try finding the previous transaction in database
            var coins = coinView.FetchCoinsAsync(new[] { txIn.PrevOut.Hash }).GetAwaiter().GetResult();

            if (coins == null || coins.UnspentOutputs.Length != 1)
            {
                ConsensusErrors.ReadTxPrevFailed.Throw();
            }

            var prevBlock = chain.GetBlock(coins.BlockHash);
            var prevUtxo  = coins.UnspentOutputs[0];

            // Verify signature
            if (!this.VerifySignature(prevUtxo, tx, 0, ScriptVerify.None))
            {
                ConsensusErrors.CoinstakeVerifySignatureFailed.Throw();
            }

            // Min age requirement
            if (IsProtocolV3((int)tx.Time))
            {
                if (IsConfirmedInNPrevBlocks(prevUtxo, pindexPrev, this.consensusOptions.StakeMinConfirmations - 1))
                {
                    ConsensusErrors.InvalidStakeDepth.Throw();
                }
            }
            else
            {
                var nTimeBlockFrom = prevBlock.Header.Time;
                if (nTimeBlockFrom + this.consensusOptions.StakeMinAge > tx.Time)
                {
                    ConsensusErrors.MinAgeViolation.Throw();
                }
            }

            this.CheckStakeKernelHash(context, pindexPrev, nBits, prevBlock, prevUtxo, prevBlockStake, txIn.PrevOut, tx.Time);
        }
        // ppcoin: total coin age spent in transaction, in the unit of coin-days.
        // Only those coins meeting minimum age requirement counts. As those
        // transactions not in main chain are not currently indexed so we
        // might not find out about their coin age. Older transactions are
        // guaranteed to be in main chain by sync-checkpoint. This rule is
        // introduced to help nodes establish a consistent view of the coin
        // age (trust score) of competing branches.
        public bool GetCoinAge(ConcurrentChain chain, CoinView coinView,
                               Transaction trx, ChainedBlock pindexPrev, out ulong nCoinAge)
        {
            BigInteger bnCentSecond = BigInteger.Zero;              // coin age in the unit of cent-seconds

            nCoinAge = 0;

            if (trx.IsCoinBase)
            {
                return(true);
            }

            foreach (var txin in trx.Inputs)
            {
                var coins = coinView.FetchCoinsAsync(new[] { txin.PrevOut.Hash }).GetAwaiter().GetResult();
                if (coins == null || coins.UnspentOutputs.Length != 1)
                {
                    continue;
                }

                var prevBlock = chain.GetBlock(coins.BlockHash);
                var prevUtxo  = coins.UnspentOutputs[0];

                // First try finding the previous transaction in database
                //Transaction txPrev = trasnactionStore.Get(txin.PrevOut.Hash);
                //if (txPrev == null)
                //	continue;  // previous transaction not in main chain
                if (trx.Time < prevUtxo.Time)
                {
                    return(false);                     // Transaction timestamp violation
                }
                if (IsProtocolV3((int)trx.Time))
                {
                    if (IsConfirmedInNPrevBlocks(prevUtxo, pindexPrev, this.consensusOptions.StakeMinConfirmations - 1))
                    {
                        //LogPrint("coinage", "coin age skip nSpendDepth=%d\n", nSpendDepth + 1);
                        continue;                         // only count coins meeting min confirmations requirement
                    }
                }
                else
                {
                    // Read block header
                    //var block = blockStore.GetBlock(txPrev.GetHash());
                    //if (block == null)
                    //	return false; // unable to read block of previous transaction
                    if (prevBlock.Header.Time + this.consensusOptions.StakeMinAge > trx.Time)
                    {
                        continue;                         // only count coins meeting min age requirement
                    }
                }

                long nValueIn   = prevUtxo._Outputs[txin.PrevOut.N].Value;
                var  multiplier = BigInteger.ValueOf((trx.Time - prevUtxo.Time) / Money.CENT);
                bnCentSecond = bnCentSecond.Add(BigInteger.ValueOf(nValueIn).Multiply(multiplier));
                //bnCentSecond += new BigInteger(nValueIn) * (trx.Time - txPrev.Time) / CENT;


                //LogPrint("coinage", "coin age nValueIn=%d nTimeDiff=%d bnCentSecond=%s\n", nValueIn, nTime - txPrev.nTime, bnCentSecond.ToString());
            }

            BigInteger bnCoinDay = bnCentSecond.Multiply(BigInteger.ValueOf(Money.CENT / Money.COIN / (24 * 60 * 60)));

            //BigInteger bnCoinDay = bnCentSecond * CENT / COIN / (24 * 60 * 60);

            //LogPrint("coinage", "coin age bnCoinDay=%s\n", bnCoinDay.ToString());
            nCoinAge = new Target(bnCoinDay).ToCompact();

            return(true);
        }
        public override async Task <FetchCoinsResponse> FetchCoinsAsync(uint256[] txIds)
        {
            Guard.NotNull(txIds, nameof(txIds));

            FetchCoinsResponse result         = null;
            uint256            innerBlockHash = null;

            UnspentOutputs[] outputs     = new UnspentOutputs[txIds.Length];
            List <int>       miss        = new List <int>();
            List <uint256>   missedTxIds = new List <uint256>();

            using (_Lock.LockRead())
            {
                WaitOngoingTasks();
                for (int i = 0; i < txIds.Length; i++)
                {
                    CacheItem cache;
                    if (!_Unspents.TryGetValue(txIds[i], out cache))
                    {
                        miss.Add(i);
                        missedTxIds.Add(txIds[i]);
                    }
                    else
                    {
                        outputs[i] = cache.UnspentOutputs == null ? null :
                                     cache.UnspentOutputs.IsPrunable ? null :
                                     cache.UnspentOutputs.Clone();
                    }
                }
                PerformanceCounter.AddMissCount(miss.Count);
                PerformanceCounter.AddHitCount(txIds.Length - miss.Count);
            }
            var fetchedCoins = await Inner.FetchCoinsAsync(missedTxIds.ToArray()).ConfigureAwait(false);

            using (_Lock.LockWrite())
            {
                _Flushing.Wait();
                innerBlockHash = fetchedCoins.BlockHash;
                if (_BlockHash == null)
                {
                    Debug.Assert(_Unspents.Count == 0);
                    _InnerBlockHash = innerBlockHash;
                    _BlockHash      = innerBlockHash;
                }
                for (int i = 0; i < miss.Count; i++)
                {
                    var index   = miss[i];
                    var unspent = fetchedCoins.UnspentOutputs[i];
                    outputs[index] = unspent;
                    CacheItem cache = new CacheItem();
                    cache.ExistInInner    = unspent != null;
                    cache.IsDirty         = false;
                    cache.UnspentOutputs  = unspent;
                    cache.OriginalOutputs = unspent?._Outputs.ToArray();
                    _Unspents.TryAdd(txIds[index], cache);
                }
                result = new FetchCoinsResponse(outputs, _BlockHash);
            }

            if (CacheEntryCount > MaxItems)
            {
                Evict();
                if (CacheEntryCount > MaxItems)
                {
                    await FlushAsync().ConfigureAwait(false);

                    Evict();
                }
            }

            return(result);
        }