2 using System.Collections.Generic;
3 using System.Diagnostics.Contracts;
8 using System.Threading.Tasks;
12 public class StakeModifier
15 /// 30 days as zero time weight
17 public const uint nStakeMinAge = 60 * 60 * 24 * 30;
20 /// 90 days as full weight
22 public const uint nStakeMaxAge = 60 * 60 * 24 * 90;
25 /// 10-minute stakes spacing
27 public const uint nStakeTargetSpacing = 10 * 60;
30 /// Time to elapse before new modifier is computed
32 public const uint nModifierInterval = 6 * 60 * 60;
35 /// Ratio of group interval length between the last group and the first group
37 public const int nModifierIntervalRatio = 3;
40 /// Protocol switch time for fixed kernel modifier interval
42 /// Mon, 20 Oct 2014 00:00:00 GMT
44 internal const uint nModifierSwitchTime = 1413763200;
47 /// Whether the given block is subject to new modifier protocol
49 /// <param name="nTimeBlock">Block timestamp</param>
50 /// <returns>Result</returns>
51 internal static bool IsFixedModifierInterval(uint nTimeBlock)
53 return (nTimeBlock >= nModifierSwitchTime);
57 /// Get the last stake modifier and its generation time from a given block
59 /// <param name="cursor">Block cursor</param>
60 /// <param name="nStakeModifier">Stake modifier (ref)</param>
61 /// <param name="nModifierTime">Stake modifier generation time (ref)</param>
62 /// <returns></returns>
63 internal static bool GetLastStakeModifier(CBlockStoreItem cursor, ref long nStakeModifier, ref uint nModifierTime)
70 while (cursor != null && cursor.prev != null && !cursor.GeneratedStakeModifier)
75 if (!cursor.GeneratedStakeModifier)
77 return false; // no generation at genesis block
80 nStakeModifier = cursor.nStakeModifier;
81 nModifierTime = cursor.nTime;
87 /// Get selection interval section (in seconds)
89 /// <param name="nSection"></param>
90 /// <returns></returns>
91 internal static long GetStakeModifierSelectionIntervalSection(int nSection)
93 Contract.Assert(nSection >= 0 && nSection < 64);
94 return (nModifierInterval * 63 / (63 + ((63 - nSection) * (nModifierIntervalRatio - 1))));
98 /// Get stake modifier selection interval (in seconds)
100 /// <returns></returns>
101 static long GetStakeModifierSelectionInterval()
103 long nSelectionInterval = 0;
104 for (int nSection = 0; nSection < 64; nSection++)
105 nSelectionInterval += GetStakeModifierSelectionIntervalSection(nSection);
106 return nSelectionInterval;
111 /// Select a block from the candidate blocks in vSortedByTimestamp, excluding
112 /// already selected blocks in vSelectedBlocks, and with timestamp
113 /// up to nSelectionIntervalStop.
115 /// <param name="sortedByTimestamp"></param>
116 /// <param name="mapSelectedBlocks"></param>
117 /// <param name="nSelectionIntervalStop">Upper boundary for timestamp value.</param>
118 /// <param name="nStakeModifierPrev">Previous value of stake modifier.</param>
119 /// <param name="selectedCursor">Selection result.</param>
120 /// <returns></returns>
121 static bool SelectBlockFromCandidates(List<Tuple<uint, uint256>> sortedByTimestamp, Dictionary<uint256, CBlockStoreItem> mapSelectedBlocks, long nSelectionIntervalStop, long nStakeModifierPrev, ref CBlockStoreItem selectedCursor)
123 bool fSelected = false;
124 uint256 hashBest = 0;
125 selectedCursor = null;
126 foreach (var item in sortedByTimestamp)
128 CBlockStoreItem cursor = CBlockStore.Instance.GetCursor(item.Item2);
132 return false; // Failed to find block index for candidate block
135 if (fSelected && cursor.nTime > nSelectionIntervalStop)
140 uint256 selectedBlockHash = cursor.Hash;
142 if (mapSelectedBlocks.Count(pair => pair.Key == selectedBlockHash) > 0)
147 // compute the selection hash by hashing its proof-hash and the
148 // previous proof-of-stake modifier
150 uint256 hashProof = cursor.IsProofOfStake ? (uint256)cursor.hashProofOfStake : selectedBlockHash;
151 uint256 hashSelection;
153 var s = new MemoryStream();
154 var writer = new BinaryWriter(s);
156 writer.Write(hashProof);
157 writer.Write(nStakeModifierPrev);
158 hashSelection = CryptoUtils.ComputeHash256(s.ToArray());
161 // the selection hash is divided by 2**32 so that proof-of-stake block
162 // is always favored over proof-of-work block. this is to preserve
163 // the energy efficiency property
164 if (cursor.IsProofOfStake)
165 hashSelection >>= 32;
166 if (fSelected && hashSelection < hashBest)
168 hashBest = hashSelection;
169 selectedCursor = cursor;
174 hashBest = hashSelection;
175 selectedCursor = cursor;
183 /// Stake Modifier (hash modifier of proof-of-stake):
184 /// The purpose of stake modifier is to prevent a txout (coin) owner from
185 /// computing future proof-of-stake generated by this txout at the time
186 /// of transaction confirmation. To meet kernel protocol, the txout
187 /// must hash with a future stake modifier to generate the proof.
188 /// Stake modifier consists of bits each of which is contributed from a
189 /// selected block of a given block group in the past.
190 /// The selection of a block is based on a hash of the block's proof-hash and
191 /// the previous stake modifier.
192 /// Stake modifier is recomputed at a fixed time interval instead of every
193 /// block. This is to make it difficult for an attacker to gain control of
194 /// additional bits in the stake modifier, even after generating a chain of
197 bool ComputeNextStakeModifier(CBlockStoreItem cursorCurrent, ref long nStakeModifier, ref bool fGeneratedStakeModifier)
200 fGeneratedStakeModifier = false;
201 CBlockStoreItem cursorPrev = cursorCurrent.prev;
202 if (cursorPrev == null)
204 fGeneratedStakeModifier = true;
205 return true; // genesis block's modifier is 0
208 // First find current stake modifier and its generation block time
209 // if it's not old enough, return the same stake modifier
210 uint nModifierTime = 0;
211 if (!GetLastStakeModifier(cursorPrev, ref nStakeModifier, ref nModifierTime))
212 return false; // Unable to get last modifier
213 if (nModifierTime / nModifierInterval >= cursorPrev.nTime / nModifierInterval)
215 // no new interval keep current modifier
218 if (nModifierTime / nModifierInterval >= cursorCurrent.nTime / nModifierInterval)
220 // fixed interval protocol requires current block timestamp also be in a different modifier interval
221 if (IsFixedModifierInterval(cursorCurrent.nTime))
223 // no new interval keep current modifier
228 // old modifier not meeting fixed modifier interval
232 // Sort candidate blocks by timestamp
233 List<Tuple<uint, uint256>> vSortedByTimestamp = new List<Tuple<uint, uint256>>();
234 // vSortedByTimestamp.reserve(64 * nModifierInterval / nStakeTargetSpacing);
236 long nSelectionInterval = GetStakeModifierSelectionInterval();
237 long nSelectionIntervalStart = (cursorPrev.nTime / nModifierInterval) * nModifierInterval - nSelectionInterval;
239 CBlockStoreItem cursor = cursorPrev;
240 while (cursor != null && cursor.nTime >= nSelectionIntervalStart)
242 vSortedByTimestamp.Add(new Tuple<uint, uint256>(cursor.nTime, cursor.Hash));
243 cursor = cursor.prev;
245 uint nHeightFirstCandidate = cursor != null ? (cursor.nHeight + 1) : 0;
246 vSortedByTimestamp.Reverse();
247 vSortedByTimestamp.Sort((x, y) => x.Item1.CompareTo(y.Item1));
249 // Select 64 blocks from candidate blocks to generate stake modifier
250 long nStakeModifierNew = 0;
251 long nSelectionIntervalStop = nSelectionIntervalStart;
252 Dictionary<uint256, CBlockStoreItem> mapSelectedBlocks = new Dictionary<uint256, CBlockStoreItem>();
253 for (int nRound = 0; nRound < Math.Min(64, (int)vSortedByTimestamp.Count); nRound++)
255 // add an interval section to the current selection round
256 nSelectionIntervalStop += GetStakeModifierSelectionIntervalSection(nRound);
257 // select a block from the candidates of current round
258 if (!SelectBlockFromCandidates(vSortedByTimestamp, mapSelectedBlocks, nSelectionIntervalStop, nStakeModifier, ref cursor))
260 return false; // unable to select block
263 // write the entropy bit of the selected block
264 nStakeModifierNew |= ((cursor.StakeEntropyBit) << nRound);
266 // add the selected block from candidates to selected list
267 mapSelectedBlocks.Add(cursor.Hash, cursor);
270 nStakeModifier = nStakeModifierNew;
271 fGeneratedStakeModifier = true;
276 /// The stake modifier used to hash for a stake kernel is chosen as the stake
277 /// modifier about a selection interval later than the coin generating the kernel
279 static bool GetKernelStakeModifier(uint256 hashBlockFrom, ref long nStakeModifier, ref uint nStakeModifierHeight, ref uint nStakeModifierTime)
282 var cursorFrom = CBlockStore.Instance.GetCursor(hashBlockFrom);
283 if (cursorFrom == null)
285 return false; // Block not indexed
288 nStakeModifierHeight = cursorFrom.nHeight;
289 nStakeModifierTime = cursorFrom.nTime;
291 long nStakeModifierSelectionInterval = GetStakeModifierSelectionInterval();
292 CBlockStoreItem cursor = cursorFrom;
294 // loop to find the stake modifier later by a selection interval
295 while (nStakeModifierTime < cursorFrom.nTime + nStakeModifierSelectionInterval)
297 if (cursor.next == null)
299 // reached best block; may happen if node is behind on block chain
302 cursor = cursor.next;
303 if (cursor.GeneratedStakeModifier)
305 nStakeModifierHeight = cursor.nHeight;
306 nStakeModifierTime = cursor.nTime;
309 nStakeModifier = cursor.nStakeModifier;
314 bool GetKernelStakeModifier(uint256 hashBlockFrom, ref long nStakeModifier)
316 uint nStakeModifierHeight = 0;
317 uint nStakeModifierTime = 0;
319 return GetKernelStakeModifier(hashBlockFrom, ref nStakeModifier, ref nStakeModifierHeight, ref nStakeModifierTime);
322 bool CheckStakeKernelHash(uint nBits, CBlock blockFrom, uint nTxPrevOffset, CTransaction txPrev, COutPoint prevout, uint nTimeTx, ref uint256 hashProofOfStake, ref uint256 targetProofOfStake)
324 if (nTimeTx < txPrev.nTime)
326 return false; // Transaction timestamp violation
329 uint nTimeBlockFrom = blockFrom.header.nTime;
330 if (nTimeBlockFrom + nStakeMinAge > nTimeTx) // Min age requirement
332 return false; // Min age violation
335 uint256 nTargetPerCoinDay = new uint256();
336 nTargetPerCoinDay.Compact = nBits;
338 ulong nValueIn = txPrev.vout[prevout.n].nValue;
339 uint256 hashBlockFrom = blockFrom.header.Hash;
340 BigInteger bnCoinDayWeight = (long)nValueIn * GetWeight(txPrev.nTime, nTimeTx) / (long)CTransaction.nCoin / (24 * 60 * 60);
342 targetProofOfStake = (bnCoinDayWeight * new BigInteger(nTargetPerCoinDay)).ToByteArray();
345 long nStakeModifier = 0;
346 uint nStakeModifierHeight = 0;
347 uint nStakeModifierTime = 0;
348 if (!GetKernelStakeModifier(hashBlockFrom, ref nStakeModifier, ref nStakeModifierHeight, ref nStakeModifierTime))
353 MemoryStream s = new MemoryStream();
354 BinaryWriter w = new BinaryWriter(s);
356 w.Write(nStakeModifier);
357 w.Write(nTimeBlockFrom);
358 w.Write(nTxPrevOffset);
359 w.Write(txPrev.nTime);
363 hashProofOfStake = CryptoUtils.ComputeHash256(s.ToArray());
366 // Now check if proof-of-stake hash meets target protocol
367 if (hashProofOfStake > targetProofOfStake)
373 // Get time weight using supplied timestamps
374 static long GetWeight(long nIntervalBeginning, long nIntervalEnd)
376 // Kernel hash weight starts from 0 at the 30-day min age
377 // this change increases active coins participating the hash and helps
378 // to secure the network when proof-of-stake difficulty is low
380 // Maximum TimeWeight is 90 days.
382 return Math.Min(nIntervalEnd - nIntervalBeginning - nStakeMinAge, nStakeMaxAge);