// Get the last stake modifier and its generation time from a given block private bool GetLastStakeModifier(ChainedBlock pindex, StakeValidator.StakeModifierContext stakeModifier) { stakeModifier.StakeModifier = 0; stakeModifier.ModifierTime = 0; if (pindex == null) { return(false); } var blockStake = this.stakeChain.Get(pindex.HashBlock); while (pindex != null && pindex.Previous != null && !blockStake.GeneratedStakeModifier()) { pindex = pindex.Previous; blockStake = this.stakeChain.Get(pindex.HashBlock); } if (!blockStake.GeneratedStakeModifier()) { return(false); // error("GetLastStakeModifier: no generation at genesis block"); } stakeModifier.StakeModifier = blockStake.StakeModifier; stakeModifier.ModifierTime = pindex.Header.Time; return(true); }
// 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. public void ComputeNextStakeModifier(ChainBase chainIndex, ChainedBlock pindexPrev, StakeValidator.StakeModifierContext stakeModifier) { stakeModifier.StakeModifier = 0; stakeModifier.GeneratedStakeModifier = false; if (pindexPrev == null) { stakeModifier.GeneratedStakeModifier = true; return; // 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 (!this.GetLastStakeModifier(pindexPrev, stakeModifier)) { ConsensusErrors.ModifierNotFound.Throw(); } if (nModifierTime / this.consensusOptions.StakeModifierInterval >= pindexPrev.Header.Time / this.consensusOptions.StakeModifierInterval) { return; } // Sort candidate blocks by timestamp var sortedByTimestamp = new SortedDictionary <uint, ChainedBlock>(); long nSelectionInterval = GetStakeModifierSelectionInterval(); long nSelectionIntervalStart = (pindexPrev.Header.Time / this.consensusOptions.StakeModifierInterval) * this.consensusOptions.StakeModifierInterval - nSelectionInterval; var pindex = pindexPrev; while (pindex != null && pindex.Header.Time >= nSelectionIntervalStart) { sortedByTimestamp.Add(pindex.Header.Time, pindex); 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>(); var counter = sortedByTimestamp.Count; var sorted = sortedByTimestamp.Values.ToArray(); for (int nRound = 0; nRound < Math.Min(64, counter); nRound++) { // add an interval section to the current selection round nSelectionIntervalStop += GetStakeModifierSelectionIntervalSection(nRound); // select a block from the candidates of current round BlockStake blockStake; if (!this.SelectBlockFromCandidates(sorted, mapSelectedBlocks, nSelectionIntervalStop, stakeModifier.StakeModifier, out pindex, out blockStake)) { ConsensusErrors.FailedSelectBlock.Throw(); } // write the entropy bit of the selected block 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())); stakeModifier.StakeModifier = nStakeModifierNew; stakeModifier.GeneratedStakeModifier = true; }