From 5371c03a659d7eb56f2c78214b8a96dd58cabe77 Mon Sep 17 00:00:00 2001 From: jl777 Date: Thu, 28 Apr 2016 16:17:12 -0500 Subject: [PATCH] fix RT gettxout --- iguana/iguana_msg.c | 8 + iguana/iguana_stake.c | 860 +++++++++++++++++++++++++++++++++++++++ iguana/iguana_unspents.c | 13 +- iguana/main.c | 2 +- 4 files changed, 874 insertions(+), 9 deletions(-) create mode 100644 iguana/iguana_stake.c diff --git a/iguana/iguana_msg.c b/iguana/iguana_msg.c index 7fc48dda8..c1a0be164 100755 --- a/iguana/iguana_msg.c +++ b/iguana/iguana_msg.c @@ -236,6 +236,14 @@ void iguana_gotaddr(struct iguana_info *coin,struct iguana_peer *addr,struct igu expand_ipbits(ipaddr,ipbits); if ( port != 0 ) sprintf(ipport,"%s:%d",ipaddr,port); + if ( 0 ) + { + int32_t i; + printf("{{"); + for (i=0; i<16; i++) + printf("0x%02x%s",A->ip[i],i<15?",":""); + printf("}, 14631},\n"); + } iguana_possible_peer(coin,ipport); //printf("gotaddr.(%s:%d) from (%s)\n",ipaddr,port,addr->ipaddr); } diff --git a/iguana/iguana_stake.c b/iguana/iguana_stake.c new file mode 100644 index 000000000..e2c8e4e03 --- /dev/null +++ b/iguana/iguana_stake.c @@ -0,0 +1,860 @@ +#ifdef reference +static const int64_t COIN_YEAR_REWARD = 5 * CENT; // 5% per year +int nCoinbaseMaturity = 100; +static const int STAKE_TIMESTAMP_MASK = 15; + +// MODIFIER_INTERVAL_RATIO: +// ratio of group interval length between the last group and the first group +static const int MODIFIER_INTERVAL_RATIO = 3; + +CBigNum bnProofOfStakeLimit(~uint256(0) >> 20); +CBigNum bnProofOfStakeLimitV2(~uint256(0) >> 48); + +unsigned int nTargetSpacing = 1 * 60; // BitcoinDark - 1 minute +static const int64_t nTargetTimespan = 60 * 60; // BitcoinDark - every 1 hour +unsigned int nStakeMinAge = 8 * 60 * 60; // BitcoinDark - 8 hours +unsigned int nStakeMaxAge = -1; +unsigned int nModifierInterval = 10 * 60; // BitcoinDark - time to elapse before new modifier is + +unsigned int nStakeSplitAge = 1 * 24 * 60 * 60; +int64_t nStakeCombineThreshold = 1000 * COIN; + +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 +unsigned int nStakeModifierChecksum; // checksum of index; in-memeory only + + +uint256 CBlockIndex::GetBlockTrust() const +{ + CBigNum bnTarget; + bnTarget.SetCompact(nBits); + + if (bnTarget <= 0) + return 0; + + return ((CBigNum(1)<<256) / (bnTarget+1)).getuint256(); +} + +unsigned int GetStakeEntropyBit() const +{ + return ((nFlags & BLOCK_STAKE_ENTROPY) >> 1); +} + +bool SetStakeEntropyBit(unsigned int 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; +} + +// miner's coin stake reward based on coin age spent (coin-days) +int64_t GetProofOfStakeReward(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 +// +unsigned int ComputeMaxBits(CBigNum bnTargetLimit, unsigned int nBase, int64_t nTime) +{ + CBigNum bnResult; + bnResult.SetCompact(nBase); + bnResult *= 2; + while (nTime > 0 && bnResult < bnTargetLimit) + { + // Maximum 200% adjustment per day... + bnResult *= 2; + nTime -= 24 * 60 * 60; + } + if (bnResult > bnTargetLimit) + bnResult = bnTargetLimit; + return bnResult.GetCompact(); +} + +// +// minimum amount of stake that could possibly be required nTime after +// minimum proof-of-stake required was nBase +// +unsigned int ComputeMinStake(unsigned int nBase, int64_t nTime, unsigned int nBlockTime) +{ + return ComputeMaxBits(bnProofOfStakeLimit, nBase, nTime); +} + +static CBigNum GetProofOfStakeLimit(int nHeight) +{ + if(IsPoSV2(nHeight)) + return bnProofOfStakeLimitV2; + else + return bnProofOfStakeLimit; +} + +unsigned int GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake) +{ + CBigNum bnTargetLimit = fProofOfStake ? GetProofOfStakeLimit(pindexLast->nHeight) : bnProofOfWorkLimit; + + if (pindexLast == NULL) + return bnTargetLimit.GetCompact(); // genesis block + const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake); + if (pindexPrev->pprev == NULL) + return bnTargetLimit.GetCompact(); // first block + const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake); + if (pindexPrevPrev->pprev == NULL) + return bnTargetLimit.GetCompact(); // second block + + int64_t nActualSpacing = pindexPrev->GetBlockTime() - pindexPrevPrev->GetBlockTime(); + if (nActualSpacing < 0) + nActualSpacing = nTargetSpacing; + + // ppcoin: target change every block + // ppcoin: retarget with exponential moving toward target spacing + CBigNum bnNew; + bnNew.SetCompact(pindexPrev->nBits); + int64_t nInterval = nTargetTimespan / nTargetSpacing; + bnNew *= ((nInterval - 1) * nTargetSpacing + nActualSpacing + nActualSpacing); + bnNew /= ((nInterval + 1) * nTargetSpacing); + + if (bnNew <= 0 || bnNew > bnTargetLimit) + bnNew = bnTargetLimit; + + return bnNew.GetCompact(); +} + +// 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() + nStakeMinAge > 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 - nStakeMinAge, (int64_t)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(int 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 (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 >& 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; + } + 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 mapSelectedBlocks; + for (int 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() + nStakeMinAge - 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(unsigned int nBits, const CBlock& blockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake, uint256& targetProofOfStake, bool fPrintProofOfStake) +{ + if (nTimeTx < txPrev.nTime) // Transaction timestamp violation + return error("CheckStakeKernelHash() : nTime violation"); + + unsigned int nTimeBlockFrom = blockFrom.GetBlockTime(); + if (nTimeBlockFrom + nStakeMinAge > 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; + int 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, unsigned int nBits, const CBlock& blockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int 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, unsigned int nBits, int64_t nSearchInterval, int64_t nFees, CTransaction& txNew, CKey& key) +{ + CBlockIndex* pindexPrev = pindexBest; + CBigNum bnTargetPerCoinDay; + 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; + } + + static int nMaxStakeSearchInterval = 60; + if (block.GetBlockTime() + nStakeMinAge > txNew.nTime - nMaxStakeSearchInterval) + continue; // only count coins meeting min age requirement + + bool fKernelFound = false; + for (unsigned int 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 < nStakeMinAge) + 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 = GetProofOfStakeReward(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 + int nIn = 0; + BOOST_FOREACH(const CWalletTx* pcoin, vwtxPrev) + { + if (!SignSignature(*this, *pcoin, txNew, nIn++)) + return error("CreateCoinStake : failed to sign coinstake"); + } + + // Limit size + unsigned int 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, unsigned int 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 +unsigned int 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(int nHeight, unsigned int 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 diff --git a/iguana/iguana_unspents.c b/iguana/iguana_unspents.c index 18d5e66c2..b988d058a 100755 --- a/iguana/iguana_unspents.c +++ b/iguana/iguana_unspents.c @@ -597,14 +597,14 @@ struct iguana_txid *iguana_txidfind(struct iguana_info *coin,int32_t *heightp,st return(0); for (i=lasthdrsi; i>=0; i--) { - if ( (bp= coin->bundles[i]) != 0 && bp->emitfinish > 1 ) + if ( (bp= coin->bundles[i]) != 0 && (bp == coin->current || bp->emitfinish > 1) ) { ramchain = (bp == coin->current) ? &coin->RTramchain : &bp->ramchain; if ( ramchain->H.data != 0 ) { if ( (TXbits= ramchain->txbits) == 0 ) { - if ( coin->fastfind == 0 ) + if ( coin->fastfind == 0 && bp != coin->current ) iguana_alloctxbits(coin,ramchain); if ( (TXbits= ramchain->txbits) == 0 ) { @@ -813,11 +813,8 @@ int32_t iguana_unspentindfind(struct iguana_info *coin,int32_t *heightp,bits256 { return(firstvout + vout); } - //else - { - if ( (tp= iguana_txidfind(coin,heightp,&TX,txid,lasthdrsi)) != 0 ) - return(tp->firstvout + vout); - } + if ( (tp= iguana_txidfind(coin,heightp,&TX,txid,lasthdrsi)) != 0 ) + return(tp->firstvout + vout); return(-1); } @@ -2557,7 +2554,7 @@ int32_t iguana_bundlevalidate(struct iguana_info *coin,struct iguana_bundle *bp, static int32_t totalerrs,totalvalidated; FILE *fp; char fname[1024]; uint8_t *blockspace; uint32_t now = (uint32_t)time(NULL); int32_t i,max,len,errs = 0; struct sha256_vstate vstate; bits256 validatehash; int64_t total = 0; - if ( bp->ramchain.from_ro != 0 || bp == coin->current ) + if ( (coin->VALIDATENODE == 0 && coin->RELAYNODE == 0) || bp->ramchain.from_ro != 0 || bp == coin->current ) { bp->validated = (uint32_t)time(NULL); return(bp->n); diff --git a/iguana/main.c b/iguana/main.c index 97dd75437..8d750dd7f 100755 --- a/iguana/main.c +++ b/iguana/main.c @@ -1146,7 +1146,7 @@ void iguana_appletests(struct supernet_info *myinfo) exit(-1); } sleep(1);*/ - if ( 1 && (str= SuperNET_JSON(myinfo,cJSON_Parse("{\"prefetchlag\":-1,\"agent\":\"iguana\",\"method\":\"addcoin\",\"startpend\":500,\"endpend\":500,\"services\":128,\"maxpeers\":64,\"newcoin\":\"BTCD\",\"active\":1,\"numhelpers\":4,\"poll\":1}"),0,myinfo->rpcport)) != 0 ) + if ( 1 && (str= SuperNET_JSON(myinfo,cJSON_Parse("{\"RELAY\":0,\"VALIDATE\":0,\"prefetchlag\":-1,\"agent\":\"iguana\",\"method\":\"addcoin\",\"startpend\":500,\"endpend\":500,\"services\":128,\"maxpeers\":64,\"newcoin\":\"BTC\",\"active\":1,\"numhelpers\":4,\"poll\":100}"),0,myinfo->rpcport)) != 0 ) { free(str); if ( 0 && (str= SuperNET_JSON(myinfo,cJSON_Parse("{\"userhome\":\"/Users/jimbolaptop/Library/Application Support\",\"agent\":\"iguana\",\"method\":\"addcoin\",\"services\":1024,\"maxpeers\":256,\"newcoin\":\"BTCD\",\"active\":1}"),0,myinfo->rpcport)) != 0 )