예제 #1
0
        // find last block index up to index
        public static ChainedBlock GetLastBlockIndex(StakeChain stakeChain, ChainedBlock index, bool proofOfStake)
        {
            if (index == null)
            {
                throw new ArgumentNullException(nameof(index));
            }
            var blockStake = stakeChain.Get(index.HashBlock);

            while (index.Previous != null && (blockStake.IsProofOfStake() != proofOfStake))
            {
                index      = index.Previous;
                blockStake = stakeChain.Get(index.HashBlock);
            }

            return(index);
        }
예제 #2
0
        /// <summary>
        /// Check PoW and that the blocks connect correctly
        /// </summary>
        /// <param name="network">The network being used</param>
        /// <returns>True if PoW is correct</returns>
        public static bool Validate(Network network, ChainedBlock chainedBlock, StakeChain stakeChain = null)
        {
            if (network == null)
            {
                throw new ArgumentNullException("network");
            }
            if (chainedBlock.Height != 0 && chainedBlock.Previous == null)
            {
                return(false);
            }
            var heightCorrect   = chainedBlock.Height == 0 || chainedBlock.Height == chainedBlock.Previous.Height + 1;
            var genesisCorrect  = chainedBlock.Height != 0 || chainedBlock.HashBlock == network.GetGenesis().GetHash();
            var hashPrevCorrect = chainedBlock.Height == 0 || chainedBlock.Header.HashPrevBlock == chainedBlock.Previous.HashBlock;
            var hashCorrect     = chainedBlock.HashBlock == chainedBlock.Header.GetHash();

            if (stakeChain == null)
            {
                return(heightCorrect && genesisCorrect && hashPrevCorrect && hashCorrect);
            }

            var workCorrect = stakeChain.CheckPowPosAndTarget(chainedBlock, stakeChain.Get(chainedBlock.HashBlock), network);

            return(heightCorrect && genesisCorrect && hashPrevCorrect && hashCorrect && workCorrect);
        }
예제 #3
0
        // Stake Modifier (hash modifier of proof-of-stake):
        // The purpose of stake modifier is to prevent a txout (coin) owner from
        // computing future proof-of-stake generated by this txout at the time
        // of transaction confirmation. To meet kernel protocol, the txout
        // must hash with a future stake modifier to generate the proof.
        // Stake modifier consists of bits each of which is contributed from a
        // selected block of a given block group in the past.
        // The selection of a block is based on a hash of the block's proof-hash and
        // the previous stake modifier.
        // Stake modifier is recomputed at a fixed time interval instead of every
        // block. This is to make it difficult for an attacker to gain control of
        // additional bits in the stake modifier, even after generating a chain of
        // blocks.
        private static bool ComputeNextStakeModifier(StakeChain stakeChain, ChainBase chainIndex, ChainedBlock pindexPrev, out ulong nStakeModifier,
                                                     out bool fGeneratedStakeModifier)
        {
            nStakeModifier          = 0;
            fGeneratedStakeModifier = false;
            if (pindexPrev == null)
            {
                fGeneratedStakeModifier = true;
                return(true);                // genesis block's modifier is 0
            }

            // First find current stake modifier and its generation block time
            // if it's not old enough, return the same stake modifier
            long nModifierTime = 0;

            if (!GetLastStakeModifier(stakeChain, pindexPrev, out nStakeModifier, out nModifierTime))
            {
                return(false);                //error("ComputeNextStakeModifier: unable to get last modifier");
            }
            //LogPrint("stakemodifier", "ComputeNextStakeModifier: prev modifier=0x%016x time=%s\n", nStakeModifier, DateTimeStrFormat(nModifierTime));
            if (nModifierTime / ModifierInterval >= pindexPrev.Header.Time / ModifierInterval)
            {
                return(true);
            }

            // Sort candidate blocks by timestamp
            var  sortedByTimestamp       = new SortedDictionary <uint, uint256>();
            long nSelectionInterval      = GetStakeModifierSelectionInterval();
            long nSelectionIntervalStart = (pindexPrev.Header.Time / ModifierInterval) * ModifierInterval - nSelectionInterval;
            var  pindex = pindexPrev;

            while (pindex != null && pindex.Header.Time >= nSelectionIntervalStart)
            {
                sortedByTimestamp.Add(pindex.Header.Time, pindex.HashBlock);
                pindex = pindex.Previous;
            }
            int nHeightFirstCandidate = pindex?.Height + 1 ?? 0;

            // Select 64 blocks from candidate blocks to generate stake modifier
            ulong nStakeModifierNew      = 0;
            long  nSelectionIntervalStop = nSelectionIntervalStart;
            var   mapSelectedBlocks      = new Dictionary <uint256, ChainedBlock>();

            for (int nRound = 0; nRound < Math.Min(64, sortedByTimestamp.Count); nRound++)
            {
                // add an interval section to the current selection round
                nSelectionIntervalStop += GetStakeModifierSelectionIntervalSection(nRound);

                // select a block from the candidates of current round
                if (!SelectBlockFromCandidates(stakeChain, pindexPrev, sortedByTimestamp, mapSelectedBlocks, nSelectionIntervalStop, nStakeModifier, out pindex))
                {
                    return(false);                    // error("ComputeNextStakeModifier: unable to select block at round %d", nRound);
                }
                // write the entropy bit of the selected block
                var blockStake = stakeChain.Get(pindex.HashBlock);
                nStakeModifierNew |= ((ulong)blockStake.GetStakeEntropyBit() << nRound);

                // add the selected block from candidates to selected list
                mapSelectedBlocks.Add(pindex.HashBlock, pindex);

                //LogPrint("stakemodifier", "ComputeNextStakeModifier: selected round %d stop=%s height=%d bit=%d\n", nRound, DateTimeStrFormat(nSelectionIntervalStop), pindex->nHeight, pindex->GetStakeEntropyBit());
            }

            //  // Print selection map for visualization of the selected blocks
            //  if (LogAcceptCategory("stakemodifier"))
            //  {
            //      string strSelectionMap = "";
            //      '-' indicates proof-of-work blocks not selected
            //      strSelectionMap.insert(0, pindexPrev->nHeight - nHeightFirstCandidate + 1, '-');
            //      pindex = pindexPrev;
            //      while (pindex && pindex->nHeight >= nHeightFirstCandidate)
            //      {
            //          // '=' indicates proof-of-stake blocks not selected
            //          if (pindex->IsProofOfStake())
            //              strSelectionMap.replace(pindex->nHeight - nHeightFirstCandidate, 1, "=");
            //          pindex = pindex->pprev;
            //      }

            //      BOOST_FOREACH(const PAIRTYPE(uint256, const CBlockIndex*)& item, mapSelectedBlocks)
            //      {
            //          // 'S' indicates selected proof-of-stake blocks
            //          // 'W' indicates selected proof-of-work blocks
            //          strSelectionMap.replace(item.second->nHeight - nHeightFirstCandidate, 1, item.second->IsProofOfStake()? "S" : "W");
            //      }

            //      LogPrintf("ComputeNextStakeModifier: selection height [%d, %d] map %s\n", nHeightFirstCandidate, pindexPrev->nHeight, strSelectionMap);
            //  }

            //LogPrint("stakemodifier", "ComputeNextStakeModifier: new modifier=0x%016x time=%s\n", nStakeModifierNew, DateTimeStrFormat(pindexPrev->GetBlockTime()));

            nStakeModifier          = nStakeModifierNew;
            fGeneratedStakeModifier = true;
            return(true);
        }
예제 #4
0
        // select a block from the candidate blocks in vSortedByTimestamp, excluding
        // already selected blocks in vSelectedBlocks, and with timestamp up to
        // nSelectionIntervalStop.
        private static bool SelectBlockFromCandidates(StakeChain stakeChain, ChainedBlock chainIndex, SortedDictionary <uint, uint256> sortedByTimestamp,
                                                      Dictionary <uint256, ChainedBlock> mapSelectedBlocks,
                                                      long nSelectionIntervalStop, ulong nStakeModifierPrev, out ChainedBlock pindexSelected)
        {
            bool    fSelected = false;
            uint256 hashBest  = 0;

            pindexSelected = null;

            foreach (var item in sortedByTimestamp)
            {
                var pindex = chainIndex.FindAncestorOrSelf(item.Value);
                if (pindex == null)
                {
                    return(false);                    // error("SelectBlockFromCandidates: failed to find block index for candidate block %s", item.second.ToString());
                }
                if (fSelected && pindex.Header.Time > nSelectionIntervalStop)
                {
                    break;
                }

                if (mapSelectedBlocks.Keys.Any(key => key == pindex.HashBlock))
                {
                    continue;
                }

                var blockStake = stakeChain.Get(pindex.HashBlock);

                // compute the selection hash by hashing its proof-hash and the
                // previous proof-of-stake modifier
                uint256 hashSelection;
                using (var ms = new MemoryStream())
                {
                    var serializer = new BitcoinStream(ms, true);
                    serializer.ReadWrite(blockStake.HashProof);
                    serializer.ReadWrite(nStakeModifierPrev);

                    hashSelection = Hashes.Hash256(ms.ToArray());
                }

                // the selection hash is divided by 2**32 so that proof-of-stake block
                // is always favored over proof-of-work block. this is to preserve
                // the energy efficiency property
                if (blockStake.IsProofOfStake())
                {
                    hashSelection >>= 32;
                }

                if (fSelected && hashSelection < hashBest)
                {
                    hashBest       = hashSelection;
                    pindexSelected = pindex;
                }
                else if (!fSelected)
                {
                    fSelected      = true;
                    hashBest       = hashSelection;
                    pindexSelected = pindex;
                }
            }

            //LogPrint("stakemodifier", "SelectBlockFromCandidates: selection hash=%s\n", hashBest.ToString());
            return(fSelected);
        }
예제 #5
0
        public static bool ComputeStakeModifier(ChainBase chainIndex, ChainedBlock pindex, BlockStake blockStake, StakeChain stakeChain)
        {
            var pindexPrev     = pindex.Previous;
            var blockStakePrev = pindexPrev == null ? null : stakeChain.Get(pindexPrev.HashBlock);

            // compute stake modifier
            ulong nStakeModifier;
            bool  fGeneratedStakeModifier;

            if (!ComputeNextStakeModifier(stakeChain, chainIndex, pindexPrev, out nStakeModifier, out fGeneratedStakeModifier))
            {
                return(false);                //error("AddToBlockIndex() : ComputeNextStakeModifier() failed");
            }
            blockStake.SetStakeModifier(nStakeModifier, fGeneratedStakeModifier);
            blockStake.StakeModifierV2 = ComputeStakeModifierV2(
                pindexPrev, blockStakePrev, blockStake.IsProofOfWork() ? pindex.HashBlock : blockStake.PrevoutStake.Hash);

            return(true);
        }
예제 #6
0
        public const uint ModifierInterval     = 10 * 60; // time to elapse before new modifier is computed

        public static bool CheckAndComputeStake(IBlockRepository blockStore, ITransactionRepository trasnactionStore, IBlockTransactionMapStore mapStore, StakeChain stakeChain,
                                                ChainBase chainIndex, ChainedBlock pindex, Block block, out BlockStake blockStake)
        {
            if (block.GetHash() != pindex.HashBlock)
            {
                throw new ArgumentException();
            }

            blockStake = new BlockStake(block);

            uint256 hashProof = null;

            // Verify hash target and signature of coinstake tx
            if (BlockStake.IsProofOfStake(block))
            {
                var pindexPrev = pindex.Previous;

                var prevBlockStake = stakeChain.Get(pindexPrev.HashBlock);
                if (prevBlockStake == null)
                {
                    return(false);                    // the stake proof of the previous block is not set
                }
                uint256 targetProofOfStake;
                if (!CheckProofOfStake(blockStore, trasnactionStore, mapStore, pindexPrev, prevBlockStake, block.Transactions[1],
                                       pindex.Header.Bits.ToCompact(), out hashProof, out targetProofOfStake))
                {
                    return(false);                    // error("AcceptBlock() : check proof-of-stake failed for block %s", hash.ToString());
                }
            }

            // PoW is checked in CheckBlock()
            if (BlockStake.IsProofOfWork(block))
            {
                hashProof = pindex.Header.GetPoWHash();
            }

            // todo: is this the same as chain work?
            // compute chain trust score
            //pindexNew.nChainTrust = (pindexNew->pprev ? pindexNew->pprev->nChainTrust : 0) + pindexNew->GetBlockTrust();

            // compute stake entropy bit for stake modifier
            if (!blockStake.SetStakeEntropyBit(blockStake.GetStakeEntropyBit()))
            {
                return(false);                //error("AddToBlockIndex() : SetStakeEntropyBit() failed");
            }
            // Record proof hash value
            blockStake.HashProof = hashProof;

            // compute stake modifier
            return(ComputeStakeModifier(chainIndex, pindex, blockStake, stakeChain));
        }
예제 #7
0
        public static Target GetNextTargetRequired(StakeChain stakeChain, ChainedBlock indexLast, Consensus consensus, bool proofOfStake)
        {
            // Genesis block
            if (indexLast == null)
            {
                return(consensus.PowLimit);
            }

            // find the last two blocks that correspond to the mining algo
            // (i.e if this is a POS block we need to find the last two POS blocks)
            var targetLimit = proofOfStake
                                ? GetProofOfStakeLimit(consensus, indexLast.Height)
                                : consensus.PowLimit.ToBigInteger();

            // first block
            var pindexPrev = GetLastBlockIndex(stakeChain, indexLast, proofOfStake);

            if (pindexPrev.Previous == null)
            {
                return(new Target(targetLimit));
            }

            // second block
            var pindexPrevPrev = GetLastBlockIndex(stakeChain, pindexPrev.Previous, proofOfStake);

            if (pindexPrevPrev.Previous == null)
            {
                return(new Target(targetLimit));
            }

            int targetSpacing = GetTargetSpacing(indexLast.Height);
            int actualSpacing = (int)(pindexPrev.Header.Time - pindexPrevPrev.Header.Time);

            if (IsProtocolV1RetargetingFixed(indexLast.Height))
            {
                if (actualSpacing < 0)
                {
                    actualSpacing = targetSpacing;
                }
            }
            if (IsProtocolV3((int)indexLast.Header.Time))
            {
                if (actualSpacing > targetSpacing * 10)
                {
                    actualSpacing = targetSpacing * 10;
                }
            }

            // target change every block
            // retarget with exponential moving toward target spacing
            var targetTimespan = 16 * 60;             // 16 mins
            var target         = pindexPrev.Header.Bits.ToBigInteger();

            int interval = targetTimespan / targetSpacing;

            target = target.Multiply(BigInteger.ValueOf(((interval - 1) * targetSpacing + actualSpacing + actualSpacing)));
            target = target.Divide(BigInteger.ValueOf(((interval + 1) * targetSpacing)));

            if (target.CompareTo(BigInteger.Zero) <= 0 || target.CompareTo(targetLimit) >= 1)
            {
                //if (target <= 0 || target > targetLimit)
                target = targetLimit;
            }

            return(new Target(target));
        }