forked from AdultChain/AdultChain
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathkernel.cpp
439 lines (378 loc) · 19.1 KB
/
kernel.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
/* @flow */
// Copyright (c) 2012-2013 The PPCoin developers
// Copyright (c) 2015-2017 The PIVX developers
// Copyright (c) 2017 The Smrtc developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <boost/assign/list_of.hpp>
#include <boost/lexical_cast.hpp>
#include "db.h"
#include "kernel.h"
#include "script/interpreter.h"
#include "timedata.h"
#include "util.h"
using namespace std;
bool fTestNet = false; //Params().NetworkID() == CBaseChainParams::TESTNET;
// Modifier interval: time to elapse before new modifier is computed
// Set to 3-hour for production network and 20-minute for test network
unsigned int nModifierInterval;
int nStakeTargetSpacing = 60;
unsigned int getIntervalVersion(bool fTestNet)
{
if (fTestNet)
return MODIFIER_INTERVAL_TESTNET;
else
return MODIFIER_INTERVAL;
}
// Hard checkpoints of stake modifiers to ensure they are deterministic
static std::map<int, unsigned int> mapStakeModifierCheckpoints =
boost::assign::map_list_of(0, 0xfd11f4e7u);
// Get time weight
int64_t GetWeight(int64_t nIntervalBeginning, int64_t nIntervalEnd)
{
return nIntervalEnd - nIntervalBeginning - nStakeMinAge;
}
// Get the last stake modifier and its generation time from a given block
static bool GetLastStakeModifier(const CBlockIndex* pindex, uint64_t& nStakeModifier, int64_t& nModifierTime)
{
if (!pindex)
return error("GetLastStakeModifier: null pindex");
while (pindex && pindex->pprev && !pindex->GeneratedStakeModifier())
pindex = pindex->pprev;
if (!pindex->GeneratedStakeModifier())
return error("GetLastStakeModifier: no generation at genesis block");
nStakeModifier = pindex->nStakeModifier;
nModifierTime = pindex->GetBlockTime();
return true;
}
// Get selection interval section (in seconds)
static int64_t GetStakeModifierSelectionIntervalSection(int nSection)
{
assert(nSection >= 0 && nSection < 64);
int64_t a = getIntervalVersion(fTestNet) * 63 / (63 + ((63 - nSection) * (MODIFIER_INTERVAL_RATIO - 1)));
return a;
}
// Get stake modifier selection interval (in seconds)
static int64_t GetStakeModifierSelectionInterval()
{
int64_t nSelectionInterval = 0;
for (int nSection = 0; nSection < 64; nSection++) {
nSelectionInterval += GetStakeModifierSelectionIntervalSection(nSection);
}
return nSelectionInterval;
}
// select a block from the candidate blocks in vSortedByTimestamp, excluding
// already selected blocks in vSelectedBlocks, and with timestamp up to
// nSelectionIntervalStop.
static bool SelectBlockFromCandidates(
vector<pair<int64_t, uint256> >& vSortedByTimestamp,
map<uint256, const CBlockIndex*>& mapSelectedBlocks,
int64_t nSelectionIntervalStop,
uint64_t nStakeModifierPrev,
const CBlockIndex** pindexSelected)
{
bool fModifierV2 = false;
bool fFirstRun = true;
bool fSelected = false;
uint256 hashBest = 0;
*pindexSelected = (const CBlockIndex*)0;
BOOST_FOREACH (const PAIRTYPE(int64_t, uint256) & item, vSortedByTimestamp) {
if (!mapBlockIndex.count(item.second))
return error("SelectBlockFromCandidates: failed to find block index for candidate block %s", item.second.ToString().c_str());
const CBlockIndex* pindex = mapBlockIndex[item.second];
if (fSelected && pindex->GetBlockTime() > nSelectionIntervalStop)
break;
//if the lowest block height (vSortedByTimestamp[0]) is >= switch height, use new modifier calc
if (fFirstRun){
fModifierV2 = pindex->nHeight >= Params().ModifierUpgradeBlock();
fFirstRun = false;
}
if (mapSelectedBlocks.count(pindex->GetBlockHash()) > 0)
continue;
// compute the selection hash by hashing an input that is unique to that block
uint256 hashProof;
if(fModifierV2)
hashProof = pindex->GetBlockHash();
else
hashProof = pindex->IsProofOfStake() ? 0 : pindex->GetBlockHash();
CDataStream ss(SER_GETHASH, 0);
ss << hashProof << nStakeModifierPrev;
uint256 hashSelection = Hash(ss.begin(), ss.end());
// 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 (pindex->IsProofOfStake())
hashSelection >>= 32;
if (fSelected && hashSelection < hashBest) {
hashBest = hashSelection;
*pindexSelected = (const CBlockIndex*)pindex;
} else if (!fSelected) {
fSelected = true;
hashBest = hashSelection;
*pindexSelected = (const CBlockIndex*)pindex;
}
}
if (GetBoolArg("-printstakemodifier", false))
LogPrintf("SelectBlockFromCandidates: selection hash=%s\n", hashBest.ToString().c_str());
return fSelected;
}
// 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.
bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64_t& nStakeModifier, bool& fGeneratedStakeModifier)
{
nStakeModifier = 0;
fGeneratedStakeModifier = false;
if (!pindexPrev) {
fGeneratedStakeModifier = true;
return true; // genesis block's modifier is 0
}
if (pindexPrev->nHeight == 0) {
//Give a stake modifier to the first block
fGeneratedStakeModifier = true;
nStakeModifier = uint64_t("stakemodifier");
return true;
}
// First find current stake modifier and its generation block time
// if it's not old enough, return the same stake modifier
int64_t nModifierTime = 0;
if (!GetLastStakeModifier(pindexPrev, nStakeModifier, nModifierTime))
return error("ComputeNextStakeModifier: unable to get last modifier");
if (GetBoolArg("-printstakemodifier", false))
LogPrintf("ComputeNextStakeModifier: prev modifier= %s time=%s\n", boost::lexical_cast<std::string>(nStakeModifier).c_str(), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", nModifierTime).c_str());
if (nModifierTime / getIntervalVersion(fTestNet) >= pindexPrev->GetBlockTime() / getIntervalVersion(fTestNet))
return true;
// Sort candidate blocks by timestamp
vector<pair<int64_t, uint256> > vSortedByTimestamp;
vSortedByTimestamp.reserve(64 * getIntervalVersion(fTestNet) / nStakeTargetSpacing);
int64_t nSelectionInterval = GetStakeModifierSelectionInterval();
int64_t nSelectionIntervalStart = (pindexPrev->GetBlockTime() / getIntervalVersion(fTestNet)) * getIntervalVersion(fTestNet) - nSelectionInterval;
const CBlockIndex* pindex = pindexPrev;
while (pindex && pindex->GetBlockTime() >= nSelectionIntervalStart) {
vSortedByTimestamp.push_back(make_pair(pindex->GetBlockTime(), pindex->GetBlockHash()));
pindex = pindex->pprev;
}
int nHeightFirstCandidate = pindex ? (pindex->nHeight + 1) : 0;
reverse(vSortedByTimestamp.begin(), vSortedByTimestamp.end());
sort(vSortedByTimestamp.begin(), vSortedByTimestamp.end());
// Select 64 blocks from candidate blocks to generate stake modifier
uint64_t nStakeModifierNew = 0;
int64_t nSelectionIntervalStop = nSelectionIntervalStart;
map<uint256, const CBlockIndex*> mapSelectedBlocks;
for (int nRound = 0; nRound < min(64, (int)vSortedByTimestamp.size()); nRound++) {
// add an interval section to the current selection round
nSelectionIntervalStop += GetStakeModifierSelectionIntervalSection(nRound);
// select a block from the candidates of current round
if (!SelectBlockFromCandidates(vSortedByTimestamp, mapSelectedBlocks, nSelectionIntervalStop, nStakeModifier, &pindex))
return error("ComputeNextStakeModifier: unable to select block at round %d", nRound);
// write the entropy bit of the selected block
nStakeModifierNew |= (((uint64_t)pindex->GetStakeEntropyBit()) << nRound);
// add the selected block from candidates to selected list
mapSelectedBlocks.insert(make_pair(pindex->GetBlockHash(), pindex));
if (fDebug || GetBoolArg("-printstakemodifier", false))
LogPrintf("ComputeNextStakeModifier: selected round %d stop=%s height=%d bit=%d\n",
nRound, DateTimeStrFormat("%Y-%m-%d %H:%M:%S", nSelectionIntervalStop).c_str(), pindex->nHeight, pindex->GetStakeEntropyBit());
}
// Print selection map for visualization of the selected blocks
if (fDebug || GetBoolArg("-printstakemodifier", false)) {
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.c_str());
}
if (fDebug || GetBoolArg("-printstakemodifier", false)) {
LogPrintf("ComputeNextStakeModifier: new modifier=%s time=%s\n", boost::lexical_cast<std::string>(nStakeModifierNew).c_str(), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pindexPrev->GetBlockTime()).c_str());
}
nStakeModifier = nStakeModifierNew;
fGeneratedStakeModifier = true;
return true;
}
// The stake modifier used to hash for a stake kernel is chosen as the stake
// modifier about a selection interval later than the coin generating the kernel
bool GetKernelStakeModifier(uint256 hashBlockFrom, uint64_t& nStakeModifier, int& nStakeModifierHeight, int64_t& nStakeModifierTime, bool fPrintProofOfStake)
{
nStakeModifier = 0;
if (!mapBlockIndex.count(hashBlockFrom))
return error("GetKernelStakeModifier() : block not indexed");
const CBlockIndex* pindexFrom = mapBlockIndex[hashBlockFrom];
nStakeModifierHeight = pindexFrom->nHeight;
nStakeModifierTime = pindexFrom->GetBlockTime();
int64_t nStakeModifierSelectionInterval = GetStakeModifierSelectionInterval();
const CBlockIndex* pindex = pindexFrom;
CBlockIndex* pindexNext = chainActive[pindexFrom->nHeight + 1];
// loop to find the stake modifier later by a selection interval
while (nStakeModifierTime < pindexFrom->GetBlockTime() + nStakeModifierSelectionInterval) {
if (!pindexNext) {
// Should never happen
return error("Null pindexNext\n");
}
pindex = pindexNext;
pindexNext = chainActive[pindexNext->nHeight + 1];
if (pindex->GeneratedStakeModifier()) {
nStakeModifierHeight = pindex->nHeight;
nStakeModifierTime = pindex->GetBlockTime();
}
}
nStakeModifier = pindex->nStakeModifier;
return true;
}
uint256 stakeHash(unsigned int nTimeTx, CDataStream ss, unsigned int prevoutIndex, uint256 prevoutHash, unsigned int nTimeBlockFrom)
{
//Smrtc will hash in the transaction hash and the index number in order to make sure each hash is unique
ss << nTimeBlockFrom << prevoutIndex << prevoutHash << nTimeTx;
return Hash(ss.begin(), ss.end());
}
//test hash vs target
bool stakeTargetHit(uint256 hashProofOfStake, int64_t nValueIn, uint256 bnTargetPerCoinDay)
{
//get the stake weight - weight is equal to coin amount
uint256 bnCoinDayWeight = uint256(nValueIn) / 100;
// Now check if proof-of-stake hash meets target protocol
return (uint256(hashProofOfStake) < bnCoinDayWeight * bnTargetPerCoinDay);
}
//instead of looping outside and reinitializing variables many times, we will give a nTimeTx and also search interval so that we can do all the hashing here
bool CheckStakeKernelHash(unsigned int nBits, const CBlock blockFrom, const CTransaction txPrev, const COutPoint prevout, unsigned int& nTimeTx, unsigned int nHashDrift, bool fCheck, uint256& hashProofOfStake, bool fPrintProofOfStake)
{
//assign new variables to make it easier to read
int64_t nValueIn = txPrev.vout[prevout.n].nValue;
unsigned int nTimeBlockFrom = blockFrom.GetBlockTime();
if (nTimeTx < nTimeBlockFrom) // Transaction timestamp violation
return error("CheckStakeKernelHash() : nTime violation");
if (nTimeBlockFrom + nStakeMinAge > nTimeTx) // Min age requirement
return error("CheckStakeKernelHash() : min age violation - nTimeBlockFrom=%d nStakeMinAge=%d nTimeTx=%d", nTimeBlockFrom, nStakeMinAge, nTimeTx);
//grab difficulty
uint256 bnTargetPerCoinDay;
bnTargetPerCoinDay.SetCompact(nBits);
//grab stake modifier
uint64_t nStakeModifier = 0;
int nStakeModifierHeight = 0;
int64_t nStakeModifierTime = 0;
if (!GetKernelStakeModifier(blockFrom.GetHash(), nStakeModifier, nStakeModifierHeight, nStakeModifierTime, fPrintProofOfStake)) {
LogPrintf("CheckStakeKernelHash(): failed to get kernel stake modifier \n");
return false;
}
//create data stream once instead of repeating it in the loop
CDataStream ss(SER_GETHASH, 0);
ss << nStakeModifier;
//if wallet is simply checking to make sure a hash is valid
if (fCheck) {
hashProofOfStake = stakeHash(nTimeTx, ss, prevout.n, prevout.hash, nTimeBlockFrom);
return stakeTargetHit(hashProofOfStake, nValueIn, bnTargetPerCoinDay);
}
bool fSuccess = false;
unsigned int nTryTime = 0;
unsigned int i;
int nHeightStart = chainActive.Height();
for (i = 0; i < (nHashDrift); i++) //iterate the hashing
{
//new block came in, move on
if (chainActive.Height() != nHeightStart)
break;
//hash this iteration
nTryTime = nTimeTx + nHashDrift - i;
hashProofOfStake = stakeHash(nTryTime, ss, prevout.n, prevout.hash, nTimeBlockFrom);
// if stake hash does not meet the target then continue to next iteration
if (!stakeTargetHit(hashProofOfStake, nValueIn, bnTargetPerCoinDay))
continue;
fSuccess = true; // if we make it this far then we have successfully created a stake hash
nTimeTx = nTryTime;
if (fDebug || fPrintProofOfStake) {
LogPrintf("CheckStakeKernelHash() : using modifier %s at height=%d timestamp=%s for block from height=%d timestamp=%s\n",
boost::lexical_cast<std::string>(nStakeModifier).c_str(), nStakeModifierHeight,
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", nStakeModifierTime).c_str(),
mapBlockIndex[blockFrom.GetHash()]->nHeight,
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", blockFrom.GetBlockTime()).c_str());
LogPrintf("CheckStakeKernelHash() : pass protocol=%s modifier=%s nTimeBlockFrom=%u prevoutHash=%s nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n",
"0.3",
boost::lexical_cast<std::string>(nStakeModifier).c_str(),
nTimeBlockFrom, prevout.hash.ToString().c_str(), nTimeBlockFrom, prevout.n, nTryTime,
hashProofOfStake.ToString().c_str());
}
break;
}
mapHashedBlocks.clear();
mapHashedBlocks[chainActive.Tip()->nHeight] = GetTime(); //store a time stamp of when we last hashed on this block
return fSuccess;
}
// Check kernel hash target and coinstake signature
bool CheckProofOfStake(const CBlock block, uint256& hashProofOfStake)
{
const CTransaction tx = block.vtx[1];
if (!tx.IsCoinStake())
return error("CheckProofOfStake() : called on non-coinstake %s", tx.GetHash().ToString().c_str());
// Kernel (input 0) must match the stake hash target per coin age (nBits)
const CTxIn& txin = tx.vin[0];
// First try finding the previous transaction in database
uint256 hashBlock;
CTransaction txPrev;
if (!GetTransaction(txin.prevout.hash, txPrev, hashBlock, true))
return error("CheckProofOfStake() : INFO: read txPrev failed");
//verify signature and script
if (!VerifyScript(txin.scriptSig, txPrev.vout[txin.prevout.n].scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&tx, 0)))
return error("CheckProofOfStake() : VerifySignature failed on coinstake %s", tx.GetHash().ToString().c_str());
CBlockIndex* pindex = NULL;
BlockMap::iterator it = mapBlockIndex.find(hashBlock);
if (it != mapBlockIndex.end())
pindex = it->second;
else
return error("CheckProofOfStake() : read block failed");
// Read block header
CBlock blockprev;
if (!ReadBlockFromDisk(blockprev, pindex->GetBlockPos()))
return error("CheckProofOfStake(): INFO: failed to find block");
unsigned int nInterval = 0;
unsigned int nTime = block.nTime;
if (!CheckStakeKernelHash(block.nBits, blockprev, txPrev, txin.prevout, nTime, nInterval, true, hashProofOfStake, fDebug))
return error("CheckProofOfStake() : INFO: check kernel failed on coinstake %s, hashProof=%s \n", tx.GetHash().ToString().c_str(), hashProofOfStake.ToString().c_str()); // may occur during initial download or if behind on block chain sync
return true;
}
// Check whether the coinstake timestamp meets protocol
bool CheckCoinStakeTimestamp(int64_t nTimeBlock, int64_t nTimeTx)
{
// v0.3 protocol
return (nTimeBlock == nTimeTx);
}
// Get stake modifier checksum
unsigned int GetStakeModifierChecksum(const CBlockIndex* pindex)
{
assert(pindex->pprev || pindex->GetBlockHash() == Params().HashGenesisBlock());
// Hash previous checksum with flags, hashProofOfStake and nStakeModifier
CDataStream ss(SER_GETHASH, 0);
if (pindex->pprev)
ss << pindex->pprev->nStakeModifierChecksum;
ss << pindex->nFlags << pindex->hashProofOfStake << pindex->nStakeModifier;
uint256 hashChecksum = Hash(ss.begin(), ss.end());
hashChecksum >>= (256 - 32);
return hashChecksum.Get64();
}
// Check stake modifier hard checkpoints
bool CheckStakeModifierCheckpoints(int nHeight, unsigned int nStakeModifierChecksum)
{
if (fTestNet) return true; // Testnet has no checkpoints
if (mapStakeModifierCheckpoints.count(nHeight)) {
return nStakeModifierChecksum == mapStakeModifierCheckpoints[nHeight];
}
return true;
}