// modify time.1462237906 modifier.baed58b98a00e41d #include "iguana777.h" #define CENT (SATOSHIDEN / 100) #define COIN_YEAR_REWARD ((int64_t)5 * CENT) // 5% per year #define NCOINBASEMATURITY 100 #define STAKE_TIMESTAMP_MASK 15 #define NTARGETSPACING 60 // BitcoinDark - 1 minute #define NTARGETTIMESPAN (60 * NTARGETSPACING) // BitcoinDark - every 1 hour #define NINTERVAL_MSPACING (((NTARGETTIMESPAN / NTARGETSPACING) - 1) * NTARGETSPACING) #define NINTERVAL_PSPACING (((NTARGETTIMESPAN / NTARGETSPACING) + 1) * NTARGETSPACING) #define NSTAKESPLITAGE (1 * 24 * NTARGETTIMESPAN) #define NSTAKE_MINAGE (8 * NTARGETTIMESPAN) // BitcoinDark - 8 hours #define NSTAKEMAXAGE ((int64_t)-1) #define NMAXSTAKESEARCHINTERVAL 60 #define NSTAKECOMBINETHRESHOLD (1000 * COIN) // ratio of group interval length between the last group and the first group #define MODIFIER_INTERVAL_RATIO 3 #define NMODIFIERINTERVAL (10 * NTARGETSPACING) // BitcoinDark - time to elapse before new modifier is // miner's coin stake reward based on coin age spent (coin-days) int64_t iguana_POSreward(int64_t nCoinAge, int64_t nFees) { int64_t nSubsidy = (nCoinAge * COIN_YEAR_REWARD * 33) / (365 * 33 + 8); return(nSubsidy + nFees); } // maximum nBits value could possible be required nTime after uint32_t iguana_maxbits(bits256 targetval,uint32_t nBits,int64_t nTime) { bits256 bitsval; bitsval = bits256_from_compact(nBits); bitsval = bits256_lshift(bitsval); while ( nTime > 0 && bits256_cmp(bitsval,targetval) < 0 ) { bitsval = bits256_rshift(bitsval); // Maximum 200% adjustment per day... nTime -= 24 * 60 * 60; } if ( bits256_cmp(bitsval,targetval) > 0 ) bitsval = targetval; return(bits256_to_compact(bitsval)); } bits256 iguana_targetval(struct iguana_info *coin,int32_t height,int32_t PoSflag) { int32_t i; bits256 targetval; if ( PoSflag == 0 ) return(coin->chain->PoWtarget); else { targetval = coin->chain->PoStargets[0]; for (i=0; ichain->numPoStargets; i++) { if ( height < coin->chain->PoSheights[i] ) break; targetval = coin->chain->PoStargets[i]; } } return(targetval); } // minimum amount of stake that could possibly be required nTime after // minimum proof-of-stake required was nBase uint32_t iguana_minstake(struct iguana_info *coin,int32_t height,uint32_t nBits,int64_t nTime,uint32_t nBlockTime) { return(iguana_maxbits(iguana_targetval(coin,height,1),nBits,nTime)); } uint32_t iguana_targetbits(struct iguana_info *coin,struct iguana_block *hwmchain,struct iguana_block *prev,struct iguana_block *prev2,int32_t PoSflag) { bits256 mpz_muldivcmp(bits256 oldval,int32_t mulval,int32_t divval,bits256 cmpval); bits256 targetval; int32_t gap; if ( hwmchain->height <= 2 ) return(hwmchain->RO.bits); targetval = iguana_targetval(coin,hwmchain->height,PoSflag); if ( prev != 0 ) { if ( prev2 != 0 ) { //if ( prev->RO.timestamp != 0 && prev2->RO.timestamp != 0 ) skip check for compatiblity { if ( (gap= prev->RO.timestamp - prev2->RO.timestamp) < 0 ) gap = NTARGETSPACING; // ppcoin: target change every block, retarget with exponential moving toward target spacing //printf("MSPACING.%d gap.%d\n",NINTERVAL_MSPACING,gap); targetval = mpz_muldivcmp(bits256_from_compact(prev->RO.bits),NINTERVAL_MSPACING + (gap << 1),NINTERVAL_PSPACING,targetval); } } } return(bits256_to_compact(targetval)); } #ifdef reference CBigNum bnProofOfStakeLimit(~uint256(0) >> 20); CBigNum bnProofOfStakeLimitV2(~uint256(0) >> 48); enum { BLOCK_PROOF_OF_STAKE = (1 << 0), // is proof-of-stake block BLOCK_STAKE_ENTROPY = (1 << 1), // entropy bit for stake modifier BLOCK_STAKE_MODIFIER = (1 << 2), // regenerated stake modifier }; uint64_t nStakeModifier; // hash modifier for proof-of-stake uint32_t nStakeModifierChecksum; // checksum of index; in-memory only uint256 CBlockIndex::GetBlockTrust() const { CBigNum bnTarget; bnTarget.SetCompact(nBits); if (bnTarget <= 0) return 0; return ((CBigNum(1)<<256) / (bnTarget+1)).getuint256(); } uint32_t GetStakeEntropyBit() const { return ((nFlags & BLOCK_STAKE_ENTROPY) >> 1); } bool SetStakeEntropyBit(uint32_t nEntropyBit) { if (nEntropyBit > 1) return false; nFlags |= (nEntropyBit? BLOCK_STAKE_ENTROPY : 0); return true; } bool GeneratedStakeModifier() const { return (nFlags & BLOCK_STAKE_MODIFIER); } void SetStakeModifier(uint64_t nModifier, bool fGeneratedStakeModifier) { nStakeModifier = nModifier; if (fGeneratedStakeModifier) nFlags |= BLOCK_STAKE_MODIFIER; } // ppcoin: total coin age spent in transaction, in the unit of coin-days. // Only those coins meeting minimum age requirement counts. As those // transactions not in main chain are not currently indexed so we // might not find out about their coin age. Older transactions are // guaranteed to be in main chain by sync-checkpoint. This rule is // introduced to help nodes establish a consistent view of the coin // age (trust score) of competing branches. bool CTransaction::GetCoinAge(CTxDB& txdb, uint64_t& nCoinAge) const { CBigNum bnCentSecond = 0; // coin age in the unit of cent-seconds nCoinAge = 0; if (IsCoinBase()) return true; BOOST_FOREACH(const CTxIn& txin, vin) { // First try finding the previous transaction in database CTransaction txPrev; CTxIndex txindex; if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex)) continue; // previous transaction not in main chain if (nTime < txPrev.nTime) return false; // Transaction timestamp violation // Read block header CBlock block; if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) return false; // unable to read block of previous transaction if (block.GetBlockTime() + NSTAKE_MINAGE > nTime) continue; // only count coins meeting min age requirement int64_t nValueIn = txPrev.vout[txin.prevout.n].nValue; bnCentSecond += CBigNum(nValueIn) * (nTime-txPrev.nTime) / CENT; if (fDebug && GetBoolArg("-printcoinage")) printf("coin age nValueIn=%"PRId64" nTimeDiff=%d bnCentSecond=%s\n", nValueIn, nTime - txPrev.nTime, bnCentSecond.ToString().c_str()); } CBigNum bnCoinDay = bnCentSecond * CENT / COIN / (24 * 60 * 60); if (fDebug && GetBoolArg("-printcoinage")) printf("coin age bnCoinDay=%s\n", bnCoinDay.ToString().c_str()); nCoinAge = bnCoinDay.getuint64(); return true; } // ppcoin: total coin age spent in block, in the unit of coin-days. bool CBlock::GetCoinAge(uint64_t& nCoinAge) const { nCoinAge = 0; CTxDB txdb("r"); BOOST_FOREACH(const CTransaction& tx, vtx) { uint64_t nTxCoinAge; if (tx.GetCoinAge(txdb, nTxCoinAge)) nCoinAge += nTxCoinAge; else return false; } if (nCoinAge == 0) // block coin age minimum 1 coin-day nCoinAge = 1; if (fDebug && GetBoolArg("-printcoinage")) printf("block coin age total nCoinDays=%"PRId64"\n", nCoinAge); return true; } // Get time weight int64_t GetWeight(int64_t nIntervalBeginning,int64_t nIntervalEnd) { // Kernel hash weight starts from 0 at the min age // this change increases active coins participating the hash and helps // to secure the network when proof-of-stake difficulty is low return min(nIntervalEnd - nIntervalBeginning - NSTAKE_MINAGE,NSTAKEMAXAGE); } // 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(int32_t nSection) { assert (nSection >= 0 && nSection < 64); return (NMODIFIERINTERVAL * 63 / (63 + ((63 - nSection) * (MODIFIER_INTERVAL_RATIO - 1)))); } // Get stake modifier selection interval (in seconds) static int64_t GetStakeModifierSelectionInterval() { int64_t nSelectionInterval = 0; for (int32_t 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 >& vSortedByTimestamp, map& mapSelectedBlocks,int64_t nSelectionIntervalStop, uint64_t nStakeModifierPrev, const CBlockIndex **pindexSelected) { 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 (mapSelectedBlocks.count(pindex->GetBlockHash()) > 0) continue; // compute the selection hash by hashing its proof-hash and the previous proof-of-stake modifier CDataStream ss(SER_GETHASH, 0); ss << pindex->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 (fDebug && GetBoolArg("-printstakemodifier")) printf("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 } // 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 (fDebug) { printf("ComputeNextStakeModifier: prev modifier=0x%016"PRIx64" time=%s\n", nStakeModifier, DateTimeStrFormat(nModifierTime).c_str()); } if (nModifierTime / NMODIFIERINTERVAL >= pindexPrev->GetBlockTime() / NMODIFIERINTERVAL) return true; // Sort candidate blocks by timestamp vector > vSortedByTimestamp; vSortedByTimestamp.reserve(64 * (NMODIFIERINTERVAL / NTARGETSPACING)); int64_t nSelectionInterval = GetStakeModifierSelectionInterval(); int64_t nSelectionIntervalStart = (pindexPrev->GetBlockTime() / NMODIFIERINTERVAL) * NMODIFIERINTERVAL - nSelectionInterval; const CBlockIndex* pindex = pindexPrev; while (pindex && pindex->GetBlockTime() >= nSelectionIntervalStart) { vSortedByTimestamp.push_back(make_pair(pindex->GetBlockTime(), pindex->GetBlockHash())); pindex = pindex->pprev; } int32_t 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 mapSelectedBlocks; for (int32_t nRound=0; nRoundGetStakeEntropyBit()) << nRound); // add the selected block from candidates to selected list mapSelectedBlocks.insert(make_pair(pindex->GetBlockHash(), pindex)); if (fDebug && GetBoolArg("-printstakemodifier")) printf("ComputeNextStakeModifier: selected round %d stop=%s height=%d bit=%d\n", nRound, DateTimeStrFormat(nSelectionIntervalStop).c_str(), pindex->nHeight, pindex->GetStakeEntropyBit()); } // Print selection map for visualization of the selected blocks if (fDebug && GetBoolArg("-printstakemodifier")) { 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"); } printf("ComputeNextStakeModifier: selection height [%d, %d] map %s\n", nHeightFirstCandidate, pindexPrev->nHeight, strSelectionMap.c_str()); } if (fDebug) { printf("ComputeNextStakeModifier: new modifier=0x%016"PRIx64" time=%s\n", nStakeModifierNew, DateTimeStrFormat(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 static 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; // loop to find the stake modifier later by a selection interval while (nStakeModifierTime < pindexFrom->GetBlockTime() + nStakeModifierSelectionInterval) { if (!pindex->pnext) { // reached best block; may happen if node is behind on block chain if (fPrintProofOfStake || (pindex->GetBlockTime() + NSTAKE_MINAGE - nStakeModifierSelectionInterval > GetAdjustedTime())) return error("GetKernelStakeModifier() : reached best block %s at height %d from block %s",pindex->GetBlockHash().ToString().c_str(), pindex->nHeight, hashBlockFrom.ToString().c_str()); else return false; } pindex = pindex->pnext; if (pindex->GeneratedStakeModifier()) { nStakeModifierHeight = pindex->nHeight; nStakeModifierTime = pindex->GetBlockTime(); } } nStakeModifier = pindex->nStakeModifier; return true; } // ppcoin kernel protocol // coinstake must meet hash target according to the protocol: // kernel (input 0) must meet the formula // hash(nStakeModifier + txPrev.block.nTime + txPrev.offset + txPrev.nTime + txPrev.vout.n + nTime) < bnTarget * nCoinDayWeight // this ensures that the chance of getting a coinstake is proportional to the // amount of coin age one owns. // The reason this hash is chosen is the following: // nStakeModifier: scrambles computation to make it very difficult to precompute // future proof-of-stake at the time of the coin's confirmation // txPrev.block.nTime: prevent nodes from guessing a good timestamp to // generate transaction for future advantage // txPrev.offset: offset of txPrev inside block, to reduce the chance of // nodes generating coinstake at the same time // txPrev.nTime: reduce the chance of nodes generating coinstake at the same // time // txPrev.vout.n: output number of txPrev, to reduce the chance of nodes // generating coinstake at the same time // block/tx hash should not be used here as they can be generated in vast // quantities so as to generate blocks faster, degrading the system back into // a proof-of-work situation. // bool CheckStakeKernelHashV1(uint32_t nBits, const CBlock& blockFrom,uint32_t nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout,uint32_t nTimeTx, uint256& hashProofOfStake, uint256& targetProofOfStake, bool fPrintProofOfStake) { if (nTimeTx < txPrev.nTime) // Transaction timestamp violation return error("CheckStakeKernelHash() : nTime violation"); uint32_t nTimeBlockFrom = blockFrom.GetBlockTime(); if (nTimeBlockFrom + NSTAKE_MINAGE > nTimeTx) // Min age requirement return error("CheckStakeKernelHash() : min age violation"); CBigNum bnTargetPerCoinDay; bnTargetPerCoinDay.SetCompact(nBits); int64_t nValueIn = txPrev.vout[prevout.n].nValue; uint256 hashBlockFrom = blockFrom.GetHash(); CBigNum bnCoinDayWeight = CBigNum(nValueIn) * GetWeight((int64_t)txPrev.nTime, (int64_t)nTimeTx) / COIN / (24 * 60 * 60); targetProofOfStake = (bnCoinDayWeight * bnTargetPerCoinDay).getuint256(); // Calculate hash CDataStream ss(SER_GETHASH, 0); uint64_t nStakeModifier = 0; int32_t nStakeModifierHeight = 0; int64_t nStakeModifierTime = 0; if ( !GetKernelStakeModifier(hashBlockFrom, nStakeModifier, nStakeModifierHeight, nStakeModifierTime, fPrintProofOfStake) ) return false; ss << nStakeModifier << nTimeBlockFrom << nTxPrevOffset << txPrev.nTime << prevout.n << nTimeTx; hashProofOfStake = Hash(ss.begin(), ss.end()); if (fPrintProofOfStake) { printf("CheckStakeKernelHash() : using modifier 0x%016"PRIx64" at height=%d timestamp=%s for block from height=%d timestamp=%s\n", nStakeModifier, nStakeModifierHeight, DateTimeStrFormat(nStakeModifierTime).c_str(), mapBlockIndex[hashBlockFrom]->nHeight, DateTimeStrFormat(blockFrom.GetBlockTime()).c_str()); printf("CheckStakeKernelHash() : check modifier=0x%016"PRIx64" nTimeBlockFrom=%u nTxPrevOffset=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n", nStakeModifier, nTimeBlockFrom, nTxPrevOffset, txPrev.nTime, prevout.n, nTimeTx, hashProofOfStake.ToString().c_str()); } // Now check if proof-of-stake hash meets target protocol if (CBigNum(hashProofOfStake) > bnCoinDayWeight * bnTargetPerCoinDay) return false; if (fDebug && !fPrintProofOfStake) { printf("CheckStakeKernelHash() : using modifier 0x%016"PRIx64" at height=%d timestamp=%s for block from height=%d timestamp=%s\n", nStakeModifier, nStakeModifierHeight, DateTimeStrFormat(nStakeModifierTime).c_str(), mapBlockIndex[hashBlockFrom]->nHeight, DateTimeStrFormat(blockFrom.GetBlockTime()).c_str()); printf("CheckStakeKernelHash() : pass modifier=0x%016"PRIx64" nTimeBlockFrom=%u nTxPrevOffset=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n", nStakeModifier, nTimeBlockFrom, nTxPrevOffset, txPrev.nTime, prevout.n, nTimeTx, hashProofOfStake.ToString().c_str()); } return true; } bool CheckStakeKernelHash(CBlockIndex* pindexPrev, uint32_t nBits, const CBlock& blockFrom, uint32_t nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, uint32_t nTimeTx, uint256& hashProofOfStake, uint256& targetProofOfStake, bool fPrintProofOfStake) { if (IsPoSV2(pindexPrev->nHeight+1)) return CheckStakeKernelHashV2(pindexPrev, nBits, blockFrom.GetBlockTime(), txPrev, prevout, nTimeTx, hashProofOfStake, targetProofOfStake, fPrintProofOfStake); else return CheckStakeKernelHashV1(nBits, blockFrom, nTxPrevOffset, txPrev, prevout, nTimeTx, hashProofOfStake, targetProofOfStake, fPrintProofOfStake); } bool CWallet::CreateCoinStake(const CKeyStore& keystore, uint32_t nBits, int64_t nSearchInterval, int64_t nFees, CTransaction& txNew, CKey& key) { CBigNum bnTargetPerCoinDay; CBlockIndex *pindexPrev = pindexBest; bnTargetPerCoinDay.SetCompact(nBits); txNew.vin.clear(); txNew.vout.clear(); // Mark coin stake transaction CScript scriptEmpty; scriptEmpty.clear(); txNew.vout.push_back(CTxOut(0, scriptEmpty)); // Choose coins to use int64_t nBalance = GetBalance(); if (nBalance <= nReserveBalance) return false; vector vwtxPrev; set > setCoins; int64_t nValueIn = 0; // Select coins with suitable depth if (!SelectCoinsSimple(nBalance - nReserveBalance, txNew.nTime, nCoinbaseMaturity + 10, setCoins, nValueIn)) return false; if (setCoins.empty()) return false; int64_t nCredit = 0; CScript scriptPubKeyKernel; CTxDB txdb("r"); BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) { CTxIndex txindex; { LOCK2(cs_main, cs_wallet); if (!txdb.ReadTxIndex(pcoin.first->GetHash(), txindex)) continue; } // Read block header CBlock block; { LOCK2(cs_main, cs_wallet); if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) continue; } if (block.GetBlockTime() + NSTAKE_MINAGE > txNew.nTime - NMAXSTAKESEARCHINTERVAL) continue; // only count coins meeting min age requirement bool fKernelFound = false; for (uint32_t n=0; nGetHash(), pcoin.second); if (CheckStakeKernelHash(pindexPrev, nBits, block, txindex.pos.nTxPos - txindex.pos.nBlockPos, *pcoin.first, prevoutStake, txNew.nTime - n, hashProofOfStake, targetProofOfStake)) { // Found a kernel if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : kernel found\n"); vector vSolutions; txnouttype whichType; CScript scriptPubKeyOut; scriptPubKeyKernel = pcoin.first->vout[pcoin.second].scriptPubKey; if (!Solver(scriptPubKeyKernel, whichType, vSolutions)) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : failed to parse kernel\n"); break; } if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : parsed kernel type=%d\n", whichType); if (whichType != TX_PUBKEY && whichType != TX_PUBKEYHASH) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : no support for kernel type=%d\n", whichType); break; // only support pay to public key and pay to address } if (whichType == TX_PUBKEYHASH) // pay to address type { // convert to pay to public key type if (!keystore.GetKey(uint160(vSolutions[0]), key)) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : failed to get key for kernel type=%d\n", whichType); break; // unable to find corresponding public key } scriptPubKeyOut << key.GetPubKey() << OP_CHECKSIG; } if (whichType == TX_PUBKEY) { valtype& vchPubKey = vSolutions[0]; if (!keystore.GetKey(Hash160(vchPubKey), key)) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : failed to get key for kernel type=%d\n", whichType); break; // unable to find corresponding public key } if (key.GetPubKey() != vchPubKey) { if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : invalid key for kernel type=%d\n", whichType); break; // keys mismatch } scriptPubKeyOut = scriptPubKeyKernel; } txNew.nTime -= n; txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second)); nCredit += pcoin.first->vout[pcoin.second].nValue; vwtxPrev.push_back(pcoin.first); txNew.vout.push_back(CTxOut(0, scriptPubKeyOut)); if (GetWeight(block.GetBlockTime(), (int64_t)txNew.nTime) < nStakeSplitAge) txNew.vout.push_back(CTxOut(0, scriptPubKeyOut)); //split stake if (fDebug && GetBoolArg("-printcoinstake")) printf("CreateCoinStake : added kernel type=%d\n", whichType); fKernelFound = true; break; } } if (fKernelFound || fShutdown) break; // if kernel is found stop searching } if (nCredit == 0 || nCredit > nBalance - nReserveBalance) return false; BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) { // Attempt to add more inputs: Only add coins of the same key/address as kernel if (txNew.vout.size() == 2 && ((pcoin.first->vout[pcoin.second].scriptPubKey == scriptPubKeyKernel || pcoin.first->vout[pcoin.second].scriptPubKey == txNew.vout[1].scriptPubKey)) && pcoin.first->GetHash() != txNew.vin[0].prevout.hash) { int64_t nTimeWeight = GetWeight((int64_t)pcoin.first->nTime, (int64_t)txNew.nTime); // Stop adding more inputs if already too many inputs if (txNew.vin.size() >= 100) break; // Stop adding more inputs if value is already pretty significant if (nCredit >= NSTAKECOMBINETHRESHOLD) break; // Stop adding inputs if reached reserve limit if (nCredit + pcoin.first->vout[pcoin.second].nValue > nBalance - nReserveBalance) break; // Do not add additional significant input if (pcoin.first->vout[pcoin.second].nValue >= NSTAKECOMBINETHRESHOLD) continue; // Do not add input that is still too young if (nTimeWeight < NSTAKE_MINAGE) continue; txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second)); nCredit += pcoin.first->vout[pcoin.second].nValue; vwtxPrev.push_back(pcoin.first); } } // Calculate coin age reward { uint64_t nCoinAge; CTxDB txdb("r"); if (!txNew.GetCoinAge(txdb, nCoinAge)) return error("CreateCoinStake : failed to calculate coin age"); int64_t nReward = iguana_POSreward(nCoinAge, nFees); if (nReward <= 0) return false; nCredit += nReward; } // Set output amount if (txNew.vout.size() == 3) { txNew.vout[1].nValue = (nCredit / 2 / CENT) * CENT; txNew.vout[2].nValue = nCredit - txNew.vout[1].nValue; } else txNew.vout[1].nValue = nCredit; // Sign int32_t nIn = 0; BOOST_FOREACH(const CWalletTx* pcoin, vwtxPrev) { if (!SignSignature(*this, *pcoin, txNew, nIn++)) return error("CreateCoinStake : failed to sign coinstake"); } // Limit size uint32_t nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION); if (nBytes >= MAX_BLOCK_SIZE_GEN/5) return error("CreateCoinStake : exceeded coinstake size limit"); // Successfully generated coinstake return true; } // Check kernel hash target and coinstake signature bool CheckProofOfStake(CBlockIndex* pindexPrev, const CTransaction& tx, uint32_t nBits, uint256& hashProofOfStake, uint256& targetProofOfStake) { 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 CTxDB txdb("r"); CTransaction txPrev; CTxIndex txindex; if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex)) return tx.DoS(1, error("CheckProofOfStake() : INFO: read txPrev failed")); // previous transaction not in main chain, may occur during initial download // Verify signature if (!VerifySignature(txPrev, tx, 0, 0)) return tx.DoS(100, error("CheckProofOfStake() : VerifySignature failed on coinstake %s", tx.GetHash().ToString().c_str())); // Read block header CBlock block; if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) return fDebug? error("CheckProofOfStake() : read block failed") : false; // unable to read block of previous transaction if (!CheckStakeKernelHash(pindexPrev, nBits, block, txindex.pos.nTxPos - txindex.pos.nBlockPos, txPrev, txin.prevout, tx.nTime, hashProofOfStake, targetProofOfStake, fDebug)) return tx.DoS(1, error("CheckProofOfStake() : INFO: check kernel failed on coinstake %s, hashProof=%s", 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 uint32_t GetStakeModifierChecksum(const CBlockIndex* pindex) { //assert (pindex->pprev || pindex->GetBlockHash() == (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet)); // Hash previous checksum with flags, hashProofOfStake and nStakeModifier CDataStream ss(SER_GETHASH, 0); if (pindex->pprev) ss << pindex->pprev->nStakeModifierChecksum; ss << pindex->nFlags << (pindex->IsProofOfStake() ? pindex->hashProof : 0) << pindex->nStakeModifier; uint256 hashChecksum = Hash(ss.begin(), ss.end()); hashChecksum >>= (256 - 32); return hashChecksum.Get64(); } // Check stake modifier hard checkpoints bool CheckStakeModifierCheckpoints(int32_t nHeight, uint32_t nStakeModifierChecksum) { MapModifierCheckpoints& checkpoints = (fTestNet ? mapStakeModifierCheckpointsTestNet : mapStakeModifierCheckpoints); if (checkpoints.count(nHeight)) return nStakeModifierChecksum == checkpoints[nHeight]; return true; } // novacoin: attempt to generate suitable proof-of-stake bool CBlock::SignBlock(CWallet& wallet, int64_t nFees) { // if we are trying to sign something except proof-of-stake block template if (!vtx[0].vout[0].IsEmpty()) return false; // if we are trying to sign a complete proof-of-stake block if (IsProofOfStake()) return true; static int64_t nLastCoinStakeSearchTime = GetAdjustedTime(); // startup timestamp CKey key; CTransaction txCoinStake; int64_t nSearchTime = txCoinStake.nTime; // search to current time if (nSearchTime > nLastCoinStakeSearchTime) { if (wallet.CreateCoinStake(wallet, nBits, nSearchTime-nLastCoinStakeSearchTime, nFees, txCoinStake, key)) { if (txCoinStake.nTime >= max(pindexBest->GetPastTimeLimit()+1, PastDrift(pindexBest->GetBlockTime()))) { // make sure coinstake would meet timestamp protocol as it would be the same as the block timestamp vtx[0].nTime = nTime = txCoinStake.nTime; nTime = max(pindexBest->GetPastTimeLimit()+1, GetMaxTransactionTime()); nTime = max(GetBlockTime(), PastDrift(pindexBest->GetBlockTime())); // we have to make sure that we have no future timestamps in our transactions set for (vector::iterator it = vtx.begin(); it != vtx.end();) if (it->nTime > nTime) { it = vtx.erase(it); } else { ++it; } vtx.insert(vtx.begin() + 1, txCoinStake); hashMerkleRoot = BuildMerkleTree(); // append a signature to our block return key.Sign(GetHash(), vchBlockSig); } } nLastCoinStakeSearchInterval = nSearchTime - nLastCoinStakeSearchTime; nLastCoinStakeSearchTime = nSearchTime; } return false; } bool CBlock::CheckBlockSignature() const { if (IsProofOfWork()) return vchBlockSig.empty(); vector vSolutions; txnouttype whichType; const CTxOut& txout = vtx[1].vout[1]; if (!Solver(txout.scriptPubKey, whichType, vSolutions)) return false; if (whichType == TX_PUBKEY) { valtype& vchPubKey = vSolutions[0]; CKey key; if (!key.SetPubKey(vchPubKey)) return false; if (vchBlockSig.empty()) return false; return key.Verify(GetHash(), vchBlockSig); } return false; } #endif