X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=src%2Fmain.cpp;h=68397250b5ee28f3f82eb7e9792acc3eacf68f7d;hb=b4458b34362433758542a3d20e0551a28584a6d2;hp=9fd19e3cb42b7100947c98060eaf835e6b42d097;hpb=5d639b906da7c7a37f0f738127e74ade8208966e;p=novacoin.git diff --git a/src/main.cpp b/src/main.cpp index 9fd19e3..6839725 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 @@ -757,6 +761,8 @@ unsigned int static GetNextTargetRequired(const CBlockIndex* pindexLast, bool fP // ppcoin: retarget with exponential moving toward target spacing CBigNum bnNew; 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); @@ -1075,10 +1081,6 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) if (!CheckBlock()) return false; - // ppcoin: coin stake tx must meet target protocol - if (IsProofOfStake() && !vtx[1].CheckProofOfStake(txdb, nBits)) - return error("ConnectBlock() : Block %s unable to meet hash target for coinstake", GetHash().ToString().c_str()); - //// issue here: it doesn't know the version unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size()); @@ -1101,7 +1103,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,7 +1285,7 @@ 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 +// 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. @@ -1302,7 +1304,7 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) // 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 +bool CTransaction::CheckProofOfStake(unsigned int nBits) const { CBigNum bnTargetPerCoinDay; bnTargetPerCoinDay.SetCompact(nBits); @@ -1310,33 +1312,39 @@ 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 + // First try finding the previous transaction in database + CTxDB txdb("r"); + CTransaction txPrev; + CTxIndex txindex; + if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex)) + return false; // previous transaction not in main chain + txdb.Close(); + if (nTime < txPrev.nTime) + return false; // Transaction timestamp violation - 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; - } + // Verify signature + if (!VerifySignature(txPrev, *this, 0)) + return DoS(100, error("CheckProofOfStake() : VerifySignature failed on coinstake %s", GetHash().ToString().c_str())); - 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 DoS(100, error("CheckProofOfStake() : check target failed on coinstake %s", GetHash().ToString().c_str())); } // ppcoin: total coin age spent in transaction, in the unit of coin-days. @@ -1420,6 +1428,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(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime)); pindexNew->phashBlock = &((*mi).first); map::iterator miPrev = mapBlockIndex.find(hashPrevBlock); @@ -1496,6 +1506,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")); @@ -1518,14 +1532,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")); @@ -1546,19 +1553,14 @@ bool CBlock::AcceptBlock() CBlockIndex* pindexPrev = (*mi).second; int nHeight = pindexPrev->nHeight+1; - // ppcoin: check for coinstake duplicate - if (IsProofOfStake()) - { // check if coinstake is already connected; that would imply the owner - // of the coinstake sent multiple blocks with the same coinstake - CTxIndex txindex; - if (CTxDB("r").ReadTxIndex(vtx[1].GetHash(), txindex)) - return error("AcceptBlock() : block %s has duplicate coinstake %s", hash.ToString().c_str(), vtx[1].GetHash().ToString().c_str()); - } - // Check proof-of-work or proof-of-stake if (nBits != GetNextTargetRequired(pindexPrev, IsProofOfStake())) return DoS(100, error("AcceptBlock() : incorrect proof-of-work/proof-of-stake")); + // ppcoin: coinstake tx must meet target protocol + if (IsProofOfStake() && !vtx[1].CheckProofOfStake(nBits)) + return error("AcceptBlock() : Block %s unable to meet hash target for coinstake", GetHash().ToString().c_str()); + // Check timestamp against prev if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || GetBlockTime() + nMaxClockDrift < pindexPrev->GetBlockTime()) return error("AcceptBlock() : block's timestamp is too early"); @@ -1605,6 +1607,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, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str()); + // Preliminary checks if (!pblock->CheckBlock()) return error("ProcessBlock() : CheckBlock FAILED"); @@ -1636,6 +1644,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, %d) for orphan block %s", pblock2->GetProofOfStake().first.ToString().c_str(), pblock2->GetProofOfStake().second, hash.ToString().c_str()); + else + setStakeSeenOrphan.insert(pblock2->GetProofOfStake()); + } mapOrphanBlocks.insert(make_pair(hash, pblock2)); mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2)); @@ -1663,6 +1681,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); @@ -1814,6 +1833,13 @@ bool LoadBlockIndex(bool fAllowNew) return error("LoadBlockIndex() : writing genesis block to disk failed"); if (!block.AddToBlockIndex(nFile, nBlockPos)) return error("LoadBlockIndex() : genesis block not accepted"); + + // ppcoin: initialize synchronized checkpoint + CTxDB txdbc; + if (!txdbc.WriteSyncCheckpoint(hashGenesisBlock)) + return error("LoadBlockIndex() : failed to init sync checkpoint"); + txdbc.Close(); + Checkpoints::hashSyncCheckpoint = hashGenesisBlock; } return true; @@ -2139,6 +2165,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts) item.second.RelayTo(pfrom); + // ppcoin: relay sync-checkpoint + CRITICAL_BLOCK(Checkpoints::cs_hashSyncCheckpoint) + if (!Checkpoints::checkpointMessage.IsNull()) + Checkpoints::checkpointMessage.RelayTo(pfrom); + pfrom->fSuccessfullyConnected = true; printf("version message: version %d, blocks=%d\n", pfrom->nVersion, pfrom->nStartingHeight); @@ -2552,6 +2583,20 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } } + else if (strCommand == "checkpoint") + { + Checkpoints::CSyncCheckpoint checkpoint; + vRecv >> checkpoint; + + if (checkpoint.ProcessSyncCheckpoint(pfrom)) + { + // Relay + pfrom->hashCheckpointKnown = checkpoint.hashCheckpoint; + CRITICAL_BLOCK(cs_vNodes) + BOOST_FOREACH(CNode* pnode, vNodes) + checkpoint.RelayTo(pnode); + } + } else { @@ -3011,9 +3056,10 @@ 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; } } @@ -3139,7 +3185,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();