2 using System.Collections.Generic;
3 using System.Diagnostics.Contracts;
9 public class StakeModifier
12 /// 30 days as zero time weight
14 public const uint nStakeMinAge = 60 * 60 * 24 * 30;
17 /// 90 days as full weight
19 public const uint nStakeMaxAge = 60 * 60 * 24 * 90;
22 /// 10-minute stakes spacing
24 public const uint nStakeTargetSpacing = 10 * 60;
27 /// Time to elapse before new modifier is computed
29 public const uint nModifierInterval = 6 * 60 * 60;
32 /// Ratio of group interval length between the last group and the first group
34 public const int nModifierIntervalRatio = 3;
37 /// Protocol switch time for fixed kernel modifier interval
39 /// Mon, 20 Oct 2014 00:00:00 GMT
41 internal const uint nModifierSwitchTime = 1413763200;
44 /// Whether the given block is subject to new modifier protocol
46 /// <param name="nTimeBlock">Block timestamp</param>
47 /// <returns>Result</returns>
48 internal static bool IsFixedModifierInterval(uint nTimeBlock)
50 return (nTimeBlock >= nModifierSwitchTime);
54 /// Get the last stake modifier and its generation time from a given block
56 /// <param name="cursor">Block cursor</param>
57 /// <param name="nStakeModifier">Stake modifier (ref)</param>
58 /// <param name="nModifierTime">Stake modifier generation time (ref)</param>
59 /// <returns></returns>
60 internal static bool GetLastStakeModifier(CBlockStoreItem cursor, ref long nStakeModifier, ref uint nModifierTime)
67 while (cursor != null && cursor.prev != null && !cursor.GeneratedStakeModifier)
72 if (!cursor.GeneratedStakeModifier)
74 return false; // no generation at genesis block
77 nStakeModifier = cursor.nStakeModifier;
78 nModifierTime = cursor.nTime;
84 /// Get selection interval section (in seconds)
86 /// <param name="nSection"></param>
87 /// <returns></returns>
88 internal static long GetStakeModifierSelectionIntervalSection(int nSection)
90 Contract.Assert(nSection >= 0 && nSection < 64);
91 return (nModifierInterval * 63 / (63 + ((63 - nSection) * (nModifierIntervalRatio - 1))));
95 /// Get stake modifier selection interval (in seconds)
97 /// <returns></returns>
98 static long GetStakeModifierSelectionInterval()
100 long nSelectionInterval = 0;
101 for (int nSection = 0; nSection < 64; nSection++)
102 nSelectionInterval += GetStakeModifierSelectionIntervalSection(nSection);
103 return nSelectionInterval;
108 /// Select a block from the candidate blocks in vSortedByTimestamp, excluding
109 /// already selected blocks in vSelectedBlocks, and with timestamp
110 /// up to nSelectionIntervalStop.
112 /// <param name="sortedByTimestamp"></param>
113 /// <param name="mapSelectedBlocks"></param>
114 /// <param name="nSelectionIntervalStop">Upper boundary for timestamp value.</param>
115 /// <param name="nStakeModifierPrev">Previous value of stake modifier.</param>
116 /// <param name="selectedCursor">Selection result.</param>
117 /// <returns></returns>
118 static bool SelectBlockFromCandidates(List<Tuple<uint, uint256>> sortedByTimestamp, Dictionary<uint256, CBlockStoreItem> mapSelectedBlocks, long nSelectionIntervalStop, long nStakeModifierPrev, ref CBlockStoreItem selectedCursor)
120 bool fSelected = false;
121 uint256 hashBest = 0;
122 selectedCursor = null;
123 foreach (var item in sortedByTimestamp)
125 CBlockStoreItem cursor = CBlockStore.Instance.GetCursor(item.Item2);
129 return false; // Failed to find block index for candidate block
132 if (fSelected && cursor.nTime > nSelectionIntervalStop)
137 uint256 selectedBlockHash = cursor.Hash;
139 if (mapSelectedBlocks.Count(pair => pair.Key == selectedBlockHash) > 0)
144 // compute the selection hash by hashing its proof-hash and the
145 // previous proof-of-stake modifier
147 var hashProof = cursor.IsProofOfStake ? (uint256)cursor.hashProofOfStake : selectedBlockHash;
148 uint256 hashSelection;
150 var s = new MemoryStream();
151 var writer = new BinaryWriter(s);
153 writer.Write(hashProof);
154 writer.Write(nStakeModifierPrev);
155 hashSelection = CryptoUtils.ComputeHash256(s.ToArray());
158 // the selection hash is divided by 2**32 so that proof-of-stake block
159 // is always favored over proof-of-work block. this is to preserve
160 // the energy efficiency property
161 if (cursor.IsProofOfStake)
162 hashSelection >>= 32;
163 if (fSelected && hashSelection < hashBest)
165 hashBest = hashSelection;
166 selectedCursor = cursor;
171 hashBest = hashSelection;
172 selectedCursor = cursor;
180 /// Stake Modifier (hash modifier of proof-of-stake):
181 /// The purpose of stake modifier is to prevent a txout (coin) owner from
182 /// computing future proof-of-stake generated by this txout at the time
183 /// of transaction confirmation. To meet kernel protocol, the txout
184 /// must hash with a future stake modifier to generate the proof.
185 /// Stake modifier consists of bits each of which is contributed from a
186 /// selected block of a given block group in the past.
187 /// The selection of a block is based on a hash of the block's proof-hash and
188 /// the previous stake modifier.
189 /// Stake modifier is recomputed at a fixed time interval instead of every
190 /// block. This is to make it difficult for an attacker to gain control of
191 /// additional bits in the stake modifier, even after generating a chain of
194 bool ComputeNextStakeModifier(CBlockStoreItem cursorCurrent, ref long nStakeModifier, ref bool fGeneratedStakeModifier)
197 fGeneratedStakeModifier = false;
198 CBlockStoreItem cursorPrev = cursorCurrent.prev;
199 if (cursorPrev == null)
201 fGeneratedStakeModifier = true;
202 return true; // genesis block's modifier is 0
205 // First find current stake modifier and its generation block time
206 // if it's not old enough, return the same stake modifier
207 uint nModifierTime = 0;
208 if (!GetLastStakeModifier(cursorPrev, ref nStakeModifier, ref nModifierTime))
209 return false; // Unable to get last modifier
210 if (nModifierTime / nModifierInterval >= cursorPrev.nTime / nModifierInterval)
212 // no new interval keep current modifier
215 if (nModifierTime / nModifierInterval >= cursorCurrent.nTime / nModifierInterval)
217 // fixed interval protocol requires current block timestamp also be in a different modifier interval
218 if (IsFixedModifierInterval(cursorCurrent.nTime))
220 // no new interval keep current modifier
225 // old modifier not meeting fixed modifier interval
229 // Sort candidate blocks by timestamp
230 List<Tuple<uint, uint256>> vSortedByTimestamp = new List<Tuple<uint, uint256>>();
231 // vSortedByTimestamp.reserve(64 * nModifierInterval / nStakeTargetSpacing);
233 long nSelectionInterval = GetStakeModifierSelectionInterval();
234 long nSelectionIntervalStart = (cursorPrev.nTime / nModifierInterval) * nModifierInterval - nSelectionInterval;
236 CBlockStoreItem cursor = cursorPrev;
237 while (cursor != null && cursor.nTime >= nSelectionIntervalStart)
239 vSortedByTimestamp.Add(new Tuple<uint, uint256>(cursor.nTime, cursor.Hash));
240 cursor = cursor.prev;
242 uint nHeightFirstCandidate = cursor != null ? (cursor.nHeight + 1) : 0;
243 vSortedByTimestamp.Reverse();
244 vSortedByTimestamp.Sort((x, y) => x.Item1.CompareTo(y.Item1));
246 // Select 64 blocks from candidate blocks to generate stake modifier
247 long nStakeModifierNew = 0;
248 long nSelectionIntervalStop = nSelectionIntervalStart;
249 Dictionary<uint256, CBlockStoreItem> mapSelectedBlocks = new Dictionary<uint256, CBlockStoreItem>();
250 for (int nRound = 0; nRound < Math.Min(64, (int)vSortedByTimestamp.Count); nRound++)
252 // add an interval section to the current selection round
253 nSelectionIntervalStop += GetStakeModifierSelectionIntervalSection(nRound);
254 // select a block from the candidates of current round
255 if (!SelectBlockFromCandidates(vSortedByTimestamp, mapSelectedBlocks, nSelectionIntervalStop, nStakeModifier, ref cursor))
257 return false; // unable to select block
260 // write the entropy bit of the selected block
261 nStakeModifierNew |= ((cursor.StakeEntropyBit) << nRound);
263 // add the selected block from candidates to selected list
264 mapSelectedBlocks.Add(cursor.Hash, cursor);
267 nStakeModifier = nStakeModifierNew;
268 fGeneratedStakeModifier = true;
273 /// The stake modifier used to hash for a stake kernel is chosen as the stake
274 /// modifier about a selection interval later than the coin generating the kernel
276 static bool GetKernelStakeModifier(uint256 hashBlockFrom, ref long nStakeModifier, ref uint nStakeModifierHeight, ref uint nStakeModifierTime)
279 var cursorFrom = CBlockStore.Instance.GetCursor(hashBlockFrom);
280 if (cursorFrom == null)
282 return false; // Block not indexed
285 nStakeModifierHeight = cursorFrom.nHeight;
286 nStakeModifierTime = cursorFrom.nTime;
288 long nStakeModifierSelectionInterval = GetStakeModifierSelectionInterval();
289 CBlockStoreItem cursor = cursorFrom;
291 // loop to find the stake modifier later by a selection interval
292 while (nStakeModifierTime < cursorFrom.nTime + nStakeModifierSelectionInterval)
294 if (cursor.next == null)
296 // reached best block; may happen if node is behind on block chain
299 cursor = cursor.next;
300 if (cursor.GeneratedStakeModifier)
302 nStakeModifierHeight = cursor.nHeight;
303 nStakeModifierTime = cursor.nTime;
306 nStakeModifier = cursor.nStakeModifier;
311 bool GetKernelStakeModifier(uint256 hashBlockFrom, ref long nStakeModifier)
313 uint nStakeModifierHeight = 0;
314 uint nStakeModifierTime = 0;
316 return GetKernelStakeModifier(hashBlockFrom, ref nStakeModifier, ref nStakeModifierHeight, ref nStakeModifierTime);
319 bool CheckStakeKernelHash(uint nBits, CBlock blockFrom, uint nTxPrevOffset, CTransaction txPrev, COutPoint prevout, uint nTimeTx, ref uint256 hashProofOfStake, ref uint256 targetProofOfStake)
321 if (nTimeTx < txPrev.nTime)
323 return false; // Transaction timestamp violation
326 uint nTimeBlockFrom = blockFrom.header.nTime;
327 if (nTimeBlockFrom + nStakeMinAge > nTimeTx) // Min age requirement
329 return false; // Min age violation
332 uint256 nTargetPerCoinDay = 0;
333 nTargetPerCoinDay.Compact = nBits;
335 ulong nValueIn = txPrev.vout[prevout.n].nValue;
336 uint256 hashBlockFrom = blockFrom.header.Hash;
337 uint256 nCoinDayWeight = new uint256(nValueIn) * GetWeight(txPrev.nTime, nTimeTx) / CTransaction.nCoin / (24 * 60 * 60);
339 targetProofOfStake = nCoinDayWeight * nTargetPerCoinDay;
342 long nStakeModifier = 0;
343 uint nStakeModifierHeight = 0;
344 uint nStakeModifierTime = 0;
345 if (!GetKernelStakeModifier(hashBlockFrom, ref nStakeModifier, ref nStakeModifierHeight, ref nStakeModifierTime))
350 MemoryStream s = new MemoryStream();
351 BinaryWriter w = new BinaryWriter(s);
353 w.Write(nStakeModifier);
354 w.Write(nTimeBlockFrom);
355 w.Write(nTxPrevOffset);
356 w.Write(txPrev.nTime);
360 hashProofOfStake = CryptoUtils.ComputeHash256(s.ToArray());
363 // Now check if proof-of-stake hash meets target protocol
364 if (hashProofOfStake > targetProofOfStake)
370 // Get time weight using supplied timestamps
371 static ulong GetWeight(ulong nIntervalBeginning, ulong nIntervalEnd)
373 // Kernel hash weight starts from 0 at the 30-day min age
374 // this change increases active coins participating the hash and helps
375 // to secure the network when proof-of-stake difficulty is low
377 // Maximum TimeWeight is 90 days.
379 return Math.Min(nIntervalEnd - nIntervalBeginning - nStakeMinAge, nStakeMaxAge);