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