X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=src%2Fmain.cpp;h=e5234c8f170e9ecc8bc7def04dded9fdbf660aef;hb=4bb99fbce1b57eef730491a98d442679a1e6bd7f;hp=0866ab06f04df85744a367bc1bc7d36e0fb08fe3;hpb=06b5c239c34478fe8256f32b1b60986f4f0b3966;p=novacoin.git diff --git a/src/main.cpp b/src/main.cpp index 0866ab0..e5234c8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,7 +29,8 @@ unsigned int nTransactionsUpdated = 0; map mapNextTx; map mapBlockIndex; -uint256 hashGenesisBlock("0x000000006d52486334316794cc38ffeb7ebf35a7ebd661fd39f5f46b0d001575"); +set > setStakeSeen; +uint256 hashGenesisBlock("0x000000007c82d1f0aa2896b01bf533a8cc26a1f44790be4ceb4ecde7bee24add"); static CBigNum bnProofOfWorkLimit(~uint256(0) >> 32); const int nInitialBlockThreshold = 120; // Regard blocks up until N-threshold as "initial download" CBlockIndex* pindexGenesisBlock = NULL; @@ -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) @@ -656,7 +660,7 @@ bool CBlock::ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions) return true; } -uint256 static GetOrphanRoot(const CBlock* pblock) +uint256 GetOrphanRoot(const CBlock* pblock) { // Work back to the first block in the orphan chain while (mapOrphanBlocks.count(pblock->hashPrevBlock)) @@ -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); @@ -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); @@ -1123,7 +1125,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) return true; } -bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) +bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) { printf("REORGANIZE\n"); @@ -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,10 +1506,18 @@ 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")); + // Check coinstake timestamp + if (IsProofOfStake() && GetBlockTime() > (int64)vtx[1].nTime + nMaxClockDrift) + return DoS(50, error("CheckBlock() : coinstake timestamp is too early")); + // Check transactions BOOST_FOREACH(const CTransaction& tx, vtx) { @@ -1518,14 +1536,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,15 +1557,6 @@ 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")); @@ -1576,6 +1578,10 @@ bool CBlock::AcceptBlock() if (!Checkpoints::CheckAuto(pindexPrev)) return DoS(100, error("AcceptBlock() : rejected by automatic checkpoint at %d", Checkpoints::nAutoCheckpoint)); + // ppcoin: check that the block satisfies synchronized checkpoint + if (!Checkpoints::CheckSync(hash, pindexPrev)) + return error("AcceptBlock() : rejected by synchronized checkpoint"); + // Write block to history file if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK))) return error("AcceptBlock() : out of disk space"); @@ -1593,6 +1599,9 @@ bool CBlock::AcceptBlock() if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : 140700)) pnode->PushInventory(CInv(MSG_BLOCK, hash)); + // ppcoin: check pending sync-checkpoint + Checkpoints::AcceptPendingSyncCheckpoint(); + return true; } @@ -1605,10 +1614,20 @@ 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"); + // ppcoin: verify hash target and signature of coinstake tx + if (pblock->IsProofOfStake() && !pblock->vtx[1].CheckProofOfStake(pblock->nBits)) + return error("ProcessBlock() : check proof-of-stake failed for block %s", hash.ToString().c_str()); + CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex); if (pcheckpoint && pblock->hashPrevBlock != hashBestChain) { @@ -1622,11 +1641,15 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) CBigNum bnNewBlock; bnNewBlock.SetCompact(pblock->nBits); CBigNum bnRequired; - bnRequired.SetCompact(ComputeMinWork(pcheckpoint->nBits, deltaTime)); - if (pblock->IsProofOfWork() && bnNewBlock > bnRequired) + if (pblock->IsProofOfWork()) + bnRequired.SetCompact(ComputeMinWork(pcheckpoint->nBits, deltaTime)); + else + bnRequired = bnNewBlock; // TODO: Computer Min Stake Target Allowed + + if (bnNewBlock > bnRequired) { pfrom->Misbehaving(100); - return error("ProcessBlock() : block with too little proof-of-work"); + return error("ProcessBlock() : block with too little %s", pblock->IsProofOfStake()? "proof-of-stake" : "proof-of-work"); } } @@ -1636,12 +1659,27 @@ 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)); // Ask this guy to fill in what we're missing if (pfrom) + { pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(pblock2)); + // ppcoin: getblocks may not obtain the ancestor block rejected + // earlier by duplicate-stake check so we ask for it again directly + pfrom->AskFor(CInv(MSG_BLOCK, GetOrphanRoot(pblock2))); + } return true; } @@ -1663,6 +1701,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); @@ -1776,20 +1815,19 @@ bool LoadBlockIndex(bool fAllowNew) // Genesis block const char* pszTimestamp = "MarketWatch 07/Nov/2011 Gold tops $1,790 to end at over six-week high"; CTransaction txNew; - txNew.nTime = 1325878371; + txNew.nTime = 1339538219; txNew.vin.resize(1); txNew.vout.resize(1); txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp)); - txNew.vout[0].nValue = GetProofOfWorkReward(bnProofOfWorkLimit.GetCompact()); - txNew.vout[0].scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG; + txNew.vout[0].SetEmpty(); CBlock block; block.vtx.push_back(txNew); block.hashPrevBlock = 0; block.hashMerkleRoot = block.BuildMerkleTree(); block.nVersion = 1; - block.nTime = 1325882536; + block.nTime = 1339540307; block.nBits = bnProofOfWorkLimit.GetCompact(); - block.nNonce = 2081920190; + block.nNonce = 1281822831; if (fTestNet) { @@ -1802,7 +1840,7 @@ bool LoadBlockIndex(bool fAllowNew) printf("%s\n", block.GetHash().ToString().c_str()); printf("%s\n", hashGenesisBlock.ToString().c_str()); printf("%s\n", block.hashMerkleRoot.ToString().c_str()); - assert(block.hashMerkleRoot == uint256("0xc7311b56de266580cca65be108ae53d7100b5c3b17da8b1106044103abd7a521")); + assert(block.hashMerkleRoot == uint256("0x1557f46a17fcf8843dbe4c0c0edfd1d17eeff2c3c48d73a59d11f5d176e4b54d")); block.print(); assert(block.GetHash() == hashGenesisBlock); assert(block.CheckBlock()); @@ -1814,6 +1852,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; @@ -1933,6 +1978,12 @@ string GetWarnings(string strFor) strStatusBar = strRPC = "WARNING: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade."; } + if (Checkpoints::hashInvalidCheckpoint != 0) + { + nPriority = 3000; + strStatusBar = strRPC = "WARNING: Invalid checkpoint found! Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade."; + } + // Alerts CRITICAL_BLOCK(cs_mapAlerts) { @@ -2139,6 +2190,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 +2608,20 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } } + else if (strCommand == "checkpoint") + { + 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 { @@ -2998,7 +3068,7 @@ CBlock* CreateNewBlock(CWallet* pwallet) pblock->vtx.push_back(txNew); // ppcoin: if coinstake available add coinstake tx - static unsigned int nLastCoinStakeCheckTime = GetAdjustedTime() - nMaxClockDrift; // only initialized at startup + static unsigned int nLastCoinStakeCheckTime = GetAdjustedTime() - nMaxClockDrift + 60; // only initialized at startup CBlockIndex* pindexPrev = pindexBest; while (nLastCoinStakeCheckTime < GetAdjustedTime()) { @@ -3008,12 +3078,15 @@ CBlock* CreateNewBlock(CWallet* pwallet) CTransaction txCoinStake; CRITICAL_BLOCK(cs) { - nLastCoinStakeCheckTime++; + // mining may have been suspended for a while so + // need to take max to satisfy the timestamp protocol + nLastCoinStakeCheckTime = max(++nLastCoinStakeCheckTime, (unsigned int) (GetAdjustedTime() - nMaxClockDrift + 60)); 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 +3212,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();