X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=src%2Fmain.cpp;h=b8b665ba37fc256487d3a5c902144e1bf5355d7f;hb=bb2d1adf243226e21e4af203a510cb8a40a0cf73;hp=42198ccb692d6522695b6617a35b373c25e977f0;hpb=1f260e60704ee42f6b10577f3d0655e15ff6082a;p=novacoin.git diff --git a/src/main.cpp b/src/main.cpp index 42198cc..b8b665b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,6 +29,7 @@ unsigned int nTransactionsUpdated = 0; map mapNextTx; map mapBlockIndex; +set setStakeSeen; uint256 hashGenesisBlock("0x000000006d52486334316794cc38ffeb7ebf35a7ebd661fd39f5f46b0d001575"); static CBigNum bnProofOfWorkLimit(~uint256(0) >> 32); const int nInitialBlockThreshold = 120; // Regard blocks up until N-threshold as "initial download" @@ -44,6 +45,7 @@ CMedianFilter cPeerBlockCounts(5, 0); // Amount of blocks that other nodes map mapOrphanBlocks; multimap mapOrphanBlocksByPrev; +set setStakeSeenOrphan; map mapOrphanTransactions; multimap mapOrphanTransactionsByPrev; @@ -324,9 +326,11 @@ bool CTransaction::CheckTransaction() const // Check for negative or overflow output values int64 nValueOut = 0; - for (int i = (IsCoinStake()? 1 : 0); i < vout.size(); i++) + for (int i = 0; i < vout.size(); i++) { const CTxOut& txout = vout[i]; + if (txout.IsEmpty() && (!IsCoinBase()) && (!IsCoinStake())) + return DoS(100, error("CTransaction::CheckTransaction() : txout empty for user transaction")); if (txout.nValue < 0) return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue negative")); if (txout.nValue > MAX_MONEY) @@ -706,10 +710,10 @@ int64 GetProofOfStakeReward(int64 nCoinAge) return nSubsidy; } -static const int64 nTargetTimespan = 7 * 24 * 60 * 60; // one week -static const int64 nTargetSpacing = 10 * 60; -static const int64 nInterval = nTargetTimespan / nTargetSpacing; -static const int64 nMaxClockDrift = 2 * 60 * 60; // 2 hours +static const int64 nTargetTimespan = 7 * 24 * 60 * 60; // one week +static const int64 nTargetSpacingStake = 10 * 60; // ten minutes +static const int64 nTargetSpacingWorkMax = 2 * 60 * 60; // two hours +static const int64 nMaxClockDrift = 2 * 60 * 60; // two hours // // minimum amount of work that could possibly be required nTime after @@ -756,7 +760,9 @@ unsigned int static GetNextTargetRequired(const CBlockIndex* pindexLast, bool fP // ppcoin: target change every block // ppcoin: retarget with exponential moving toward target spacing CBigNum bnNew; - bnNew.SetCompact(pindexLast->nBits); + bnNew.SetCompact(pindexPrev->nBits); + int64 nTargetSpacing = fProofOfStake? nTargetSpacingStake : min(nTargetSpacingWorkMax, nTargetSpacingStake * (1 + pindexLast->nHeight - pindexPrev->nHeight)); + int64 nInterval = nTargetTimespan / nTargetSpacing; bnNew *= ((nInterval - 1) * nTargetSpacing + nActualSpacing + nActualSpacing); bnNew /= ((nInterval + 1) * nTargetSpacing); @@ -1101,7 +1107,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) // ppcoin: fees are not collected by miners as in bitcoin // ppcoin: fees are destroyed to compensate the entire network - if (vtx[0].GetValueOut() > GetProofOfWorkReward(nBits)) + if (IsProofOfWork() && vtx[0].GetValueOut() > GetProofOfWorkReward(nBits)) return false; if (fDebug && GetBoolArg("-printcreation")) printf("ConnectBlock() : destroy=%s nFees=%"PRI64d"\n", FormatMoney(nFees).c_str(), nFees); @@ -1283,18 +1289,24 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) // ppcoin: coinstake must meet hash target according to the protocol: -// at least one input must meet the formula -// hash(nBits + txPrev.block.nTime + txPrev.nTime + nTime) < bnTarget * nCoinDay +// input 0 must meet the formula +// hash(nBits + txPrev.block.nTime + txPrev.offset + txPrev.nTime + txPrev.vout.n + nTime) < bnTarget * nCoinDay // 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: -// nBits: encodes all past block timestamps, prevents computing hash in advance -// txPrev.block.nTime: prevent nodes from guessing a good timestamp to generate -// transaction for future advantage -// txPrev.nTime: prevent nodes from meeting target simultaneously -// block/tx hash should not be used here as they can be generated in vast quatities -// so as to generate blocks faster, degrading the system back into a proof-of-work -// situation. +// nBits: encodes all past block timestamps, making computing hash in advance +// more difficult +// 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 CTransaction::CheckProofOfStake(CTxDB& txdb, unsigned int nBits) const { @@ -1304,33 +1316,33 @@ bool CTransaction::CheckProofOfStake(CTxDB& txdb, unsigned int nBits) const if (!IsCoinStake()) 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 + // Input 0 must match the stake hash target per coin age (nBits) + const CTxIn& txin = vin[0]; - // 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() + AUTO_CHECKPOINT_TRUST_SPAN > nTime) - continue; // only count coins from at least one week ago - - int64 nValueIn = txPrev.vout[txin.prevout.n].nValue; - CBigNum bnCoinDay = CBigNum(nValueIn) * (nTime-txPrev.nTime) / COIN / (24 * 60 * 60); - // Calculate hash - CDataStream ss(SER_GETHASH, VERSION); - ss << nBits << block.nTime << txPrev.nTime << nTime; - if (CBigNum(Hash(ss.begin(), ss.end())) <= bnCoinDay * bnTargetPerCoinDay) - return true; - } + // First try finding the previous transaction in database + CTransaction txPrev; + CTxIndex txindex; + if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex)) + return false; // previous transaction not in main chain + if (nTime < txPrev.nTime) + return false; // Transaction timestamp violation - return false; + // 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() + AUTO_CHECKPOINT_TRUST_SPAN > nTime) + return false; // only count coins from at least one week ago + + int64 nValueIn = txPrev.vout[txin.prevout.n].nValue; + CBigNum bnCoinDay = CBigNum(nValueIn) * (nTime-txPrev.nTime) / COIN / (24 * 60 * 60); + // Calculate hash + CDataStream ss(SER_GETHASH, VERSION); + ss << nBits << block.nTime << (txindex.pos.nTxPos - txindex.pos.nBlockPos) << txPrev.nTime << txin.prevout.n << nTime; + if (CBigNum(Hash(ss.begin(), ss.end())) <= bnCoinDay * bnTargetPerCoinDay) + return true; + else + return false; } // ppcoin: total coin age spent in transaction, in the unit of coin-days. @@ -1414,6 +1426,8 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) if (!pindexNew) return error("AddToBlockIndex() : new CBlockIndex failed"); map::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first; + if (pindexNew->fProofOfStake) + setStakeSeen.insert(pindexNew->prevoutStake); pindexNew->phashBlock = &((*mi).first); map::iterator miPrev = mapBlockIndex.find(hashPrevBlock); @@ -1490,6 +1504,10 @@ bool CBlock::CheckBlock() const if (vtx[i].IsCoinStake()) return DoS(100, error("CheckBlock() : coinstake in wrong position")); + // ppcoin: coinbase output should be empty if proof-of-stake block + if (IsProofOfStake() && !vtx[0].vout[0].IsEmpty()) + return error("CheckBlock() : coinbase output not empty for proof-of-stake block"); + // Check coinbase timestamp if (GetBlockTime() > (int64)vtx[0].nTime + nMaxClockDrift) return DoS(50, error("CheckBlock() : coinbase timestamp is too early")); @@ -1512,14 +1530,7 @@ bool CBlock::CheckBlock() const if (hashMerkleRoot != BuildMerkleTree()) return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch")); - // Coin base vout[0] scriptPubKey must be the same as coin stake vout[1] - // scriptPubKey - if (vtx.size() > 1 && vtx[1].IsCoinStake() && - vtx[0].vout[0].scriptPubKey != vtx[1].vout[1].scriptPubKey) - return DoS(100, error("CheckBlock() : block key mismatch")); - - - // Check block signature + // ppcoin: check block signature if (!CheckBlockSignature()) return DoS(100, error("CheckBlock() : bad block signature")); @@ -1599,6 +1610,12 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) if (mapOrphanBlocks.count(hash)) return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,20).c_str()); + // ppcoin: check proof-of-stake + // Limited duplicity on stake: prevents block flood attack + // Duplicate stake allowed only when there is orphan child block + if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash)) + return error("ProcessBlock() : duplicate proof-of-stake (%s) for block %s", pblock->GetProofOfStake().ToString().c_str(), hash.ToString().c_str()); + // Preliminary checks if (!pblock->CheckBlock()) return error("ProcessBlock() : CheckBlock FAILED"); @@ -1630,6 +1647,16 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) { printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,20).c_str()); CBlock* pblock2 = new CBlock(*pblock); + // ppcoin: check proof-of-stake + if (pblock2->IsProofOfStake()) + { + // Limited duplicity on stake: prevents block flood attack + // Duplicate stake allowed only when there is orphan child block + if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash)) + return error("ProcessBlock() : duplicate proof-of-stake (%s) for orphan block %s", pblock2->GetProofOfStake().ToString().c_str(), hash.ToString().c_str()); + else + setStakeSeenOrphan.insert(pblock2->GetProofOfStake()); + } mapOrphanBlocks.insert(make_pair(hash, pblock2)); mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2)); @@ -1657,6 +1684,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) if (pblockOrphan->AcceptBlock()) vWorkQueue.push_back(pblockOrphan->GetHash()); mapOrphanBlocks.erase(pblockOrphan->GetHash()); + setStakeSeenOrphan.erase(pblockOrphan->GetProofOfStake()); delete pblockOrphan; } mapOrphanBlocksByPrev.erase(hashPrev); @@ -1860,11 +1888,12 @@ void PrintBlockTree() // print item CBlock block; block.ReadFromDisk(pindex); - printf("%d (%u,%u) %s %s tx %d", + printf("%d (%u,%u) %s %08lx %s tx %d", pindex->nHeight, pindex->nFile, pindex->nBlockPos, block.GetHash().ToString().substr(0,20).c_str(), + block.nBits, DateTimeStrFormat("%x %H:%M:%S", block.GetBlockTime()).c_str(), block.vtx.size()); @@ -2973,7 +3002,6 @@ public: CBlock* CreateNewBlock(CWallet* pwallet) { - CBlockIndex* pindexPrev = pindexBest; CReserveKey reservekey(pwallet); // Create new block @@ -2993,9 +3021,11 @@ CBlock* CreateNewBlock(CWallet* pwallet) // ppcoin: if coinstake available add coinstake tx static unsigned int nLastCoinStakeCheckTime = GetAdjustedTime() - nMaxClockDrift; // only initialized at startup - pblock->nBits = GetNextTargetRequired(pindexPrev, true); + CBlockIndex* pindexPrev = pindexBest; while (nLastCoinStakeCheckTime < GetAdjustedTime()) { + pindexPrev = pindexBest; // get best block again to avoid getting stale + pblock->nBits = GetNextTargetRequired(pindexPrev, true); static CCriticalSection cs; CTransaction txCoinStake; CRITICAL_BLOCK(cs) @@ -3003,14 +3033,15 @@ CBlock* CreateNewBlock(CWallet* pwallet) nLastCoinStakeCheckTime++; txCoinStake.nTime = nLastCoinStakeCheckTime; } - if (pwallet->CreateCoinStake(txNew.vout[0].scriptPubKey, pblock->nBits, txCoinStake)) + if (pwallet->CreateCoinStake(pblock->nBits, txCoinStake)) { pblock->vtx.push_back(txCoinStake); + pblock->vtx[0].vout[0].SetEmpty(); break; } } - if (pblock->IsProofOfWork()) - pblock->nBits = GetNextTargetRequired(pindexPrev, false); + + pblock->nBits = GetNextTargetRequired(pindexPrev, pblock->IsProofOfStake()); // Collect memory pool transactions into the block int64 nFees = 0; @@ -3131,7 +3162,8 @@ CBlock* CreateNewBlock(CWallet* pwallet) } } } - pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits); + if (pblock->IsProofOfWork()) + pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits); // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash();