X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=src%2Fmain.cpp;h=aaf3f36a1374d476864878117ca77139ef83e8ba;hb=09f2a990950512cbe418526d91838887a0dded2a;hp=38f2a93c0fa459a54d0b3e2bc73f61957f2578ef;hpb=36f387a8006ad1ba91d19eff7ed78a1fbf4b658a;p=novacoin.git diff --git a/src/main.cpp b/src/main.cpp index 38f2a93..aaf3f36 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2011 The Bitcoin developers -// Copyright (c) 2011 The PPCoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file license.txt or http://www.opensource.org/licenses/mit-license.php. #include "headers.h" @@ -29,7 +29,8 @@ unsigned int nTransactionsUpdated = 0; map mapNextTx; map mapBlockIndex; -uint256 hashGenesisBlock("0x000000007ba3367beb8b04bf8bc62a8e265662d99779824070f3cdfe89852a66"); +set > setStakeSeen; +uint256 hashGenesisBlock = hashGenesisBlockOfficial; 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; @@ -64,7 +66,7 @@ int fUseUPnP = true; #else int fUseUPnP = false; #endif - +int64 nBalanceReserve = 0; ////////////////////////////////////////////////////////////////////////////// // @@ -116,8 +118,18 @@ void static EraseFromWallets(uint256 hash) } // make sure all wallets know about the given transaction, in the given block -void static SyncWithWallets(const CTransaction& tx, const CBlock* pblock = NULL, bool fUpdate = false) +void static SyncWithWallets(const CTransaction& tx, const CBlock* pblock = NULL, bool fUpdate = false, bool fConnect = true) { + if (!fConnect) + { + // ppcoin: wallets need to refund inputs when disconnecting coinstake + if (tx.IsCoinStake()) + BOOST_FOREACH(CWallet* pwallet, setpwalletRegistered) + if (pwallet->IsFromMe(tx)) + pwallet->DisableTransaction(tx); + return; + } + BOOST_FOREACH(CWallet* pwallet, setpwalletRegistered) pwallet->AddToWalletIfInvolvingMe(tx, pblock, fUpdate); } @@ -314,8 +326,11 @@ bool CTransaction::CheckTransaction() const // Check for negative or overflow output values int64 nValueOut = 0; - BOOST_FOREACH(const CTxOut& txout, vout) + 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) @@ -360,6 +375,9 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi // Coinbase is only valid in a block, not as a loose transaction if (IsCoinBase()) return DoS(100, error("AcceptToMemoryPool() : coinbase as individual tx")); + // ppcoin: coinstake is also only valid in a block, not as a loose transaction + if (IsCoinStake()) + return DoS(100, error("AcceptToMemoryPool() : coinstake as individual tx")); // To help v0.1.5 clients who would see it as a negative number if ((int64)nLockTime > INT_MAX) @@ -545,7 +563,7 @@ int CMerkleTx::GetDepthInMainChain(int& nHeightRet) const int CMerkleTx::GetBlocksToMaturity() const { - if (!IsCoinBase()) + if (!(IsCoinBase() || IsCoinStake())) return 0; return max(0, (COINBASE_MATURITY+20) - GetDepthInMainChain()); } @@ -580,7 +598,7 @@ bool CWalletTx::AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs) // Add previous supporting transactions first BOOST_FOREACH(CMerkleTx& tx, vtxPrev) { - if (!tx.IsCoinBase()) + if (!(tx.IsCoinBase() || tx.IsCoinStake())) { uint256 hash = tx.GetHash(); if (!mapTransactions.count(hash) && !txdb.ContainsTx(hash)) @@ -650,7 +668,16 @@ uint256 static GetOrphanRoot(const CBlock* pblock) return pblock->GetHash(); } -int64 static GetBlockValue(unsigned int nBits, int64 nFees) +// ppcoin: find block wanted by given orphan block +uint256 WantedByOrphan(const CBlock* pblockOrphan) +{ + // Work back to the first block in the orphan chain + while (mapOrphanBlocks.count(pblockOrphan->hashPrevBlock)) + pblockOrphan = mapOrphanBlocks[pblockOrphan->hashPrevBlock]; + return pblockOrphan->hashPrevBlock; +} + +int64 static GetProofOfWorkReward(unsigned int nBits) { CBigNum bnSubsidyLimit = 9999 * COIN; // subsidy amount for difficulty 1 CBigNum bnTarget; @@ -667,7 +694,7 @@ int64 static GetBlockValue(unsigned int nBits, int64 nFees) { CBigNum bnMidValue = (bnLowerBound + bnUpperBound) / 2; if (fDebug && GetBoolArg("-printcreation")) - printf("GetBlockValue() : lower=%d upper=%d mid=%d\n", bnLowerBound.getint(), bnUpperBound.getint(), bnMidValue.getint()); + printf("GetProofOfWorkReward() : lower=%"PRI64d" upper=%"PRI64d" mid=%"PRI64d"\n", bnLowerBound.getuint64(), bnUpperBound.getuint64(), bnMidValue.getuint64()); if (bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnTargetLimit > bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnTarget) bnUpperBound = bnMidValue; else @@ -677,15 +704,25 @@ int64 static GetBlockValue(unsigned int nBits, int64 nFees) int64 nSubsidy = bnUpperBound.getuint64(); nSubsidy = (nSubsidy / CENT) * CENT; if (fDebug && GetBoolArg("-printcreation")) - printf("GetBlockValue() : nBits=0x%08x nSubsidy=%"PRI64d"\n", nBits, nSubsidy); + printf("GetProofOfWorkReward() : create=%s nBits=0x%08x nSubsidy=%"PRI64d"\n", FormatMoney(nSubsidy).c_str(), nBits, nSubsidy); + + return nSubsidy; +} - return nSubsidy + nFees; +// ppcoin: miner's coin stake is rewarded based on coin age spent (coin-days) +int64 GetProofOfStakeReward(int64 nCoinAge) +{ + static int64 nRewardCoinYear = CENT; // creation amount per coin-year + int64 nSubsidy = nCoinAge * 33 / (365 * 33 + 8) * nRewardCoinYear; + if (fDebug && GetBoolArg("-printcreation")) + printf("GetProofOfStakeReward(): create=%s nCoinAge=%"PRI64d"\n", FormatMoney(nSubsidy).c_str(), 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 @@ -695,30 +732,46 @@ unsigned int ComputeMinWork(unsigned int nBase, int64 nTime) { CBigNum bnResult; bnResult.SetCompact(nBase); + bnResult *= 2; while (nTime > 0 && bnResult < bnProofOfWorkLimit) { - // Maximum 400% adjustment... - bnResult *= 4; - // ... in best-case exactly 4-times-normal target time - nTime -= nTargetTimespan*4; + // Maximum 200% adjustment per day... + bnResult *= 2; + nTime -= 24 * 60 * 60; } if (bnResult > bnProofOfWorkLimit) bnResult = bnProofOfWorkLimit; return bnResult.GetCompact(); } -unsigned int static GetNextWorkRequired(const CBlockIndex* pindexLast) +// ppcoin: find last block index up to pindex +const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake) +{ + while (pindex && (pindex->IsProofOfStake() != fProofOfStake)) + pindex = pindex->pprev; + return pindex; +} + +unsigned int static GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake) { // Genesis block and first block if (pindexLast == NULL || pindexLast->pprev == NULL) return bnProofOfWorkLimit.GetCompact(); - int64 nActualSpacing = pindexLast->GetBlockTime() - pindexLast->pprev->GetBlockTime(); + const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake); + if (pindexPrev == NULL) + return bnProofOfWorkLimit.GetCompact(); + const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake); + if (pindexPrevPrev == NULL) + return bnProofOfWorkLimit.GetCompact(); + int64 nActualSpacing = pindexPrev->GetBlockTime() - pindexPrevPrev->GetBlockTime(); // 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); @@ -877,11 +930,11 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map& mapTestPoo if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size()) return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %d %d prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str())); - // If prev is coinbase, check that it's matured - if (txPrev.IsCoinBase()) + // If prev is coinbase/coinstake, check that it's matured + if (txPrev.IsCoinBase() || txPrev.IsCoinStake()) for (CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev) if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile) - return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight); + return error("ConnectInputs() : tried to spend coinbase/coinstake at depth %d", pindexBlock->nHeight - pindex->nHeight); // ppcoin: check transaction timestamp if (txPrev.nTime > nTime) @@ -916,18 +969,32 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map& mapTestPoo } } - if (nValueIn < GetValueOut()) - return DoS(100, error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str())); + if (IsCoinStake()) + { + // ppcoin: coin stake tx earns reward instead of paying fee + uint64 nCoinAge; + if (!GetCoinAge(txdb, nCoinAge)) + return error("ConnectInputs() : %s unable to get coin age for coinstake", GetHash().ToString().substr(0,10).c_str()); + int64 nStakeReward = GetValueOut() - nValueIn; + if (nStakeReward > GetProofOfStakeReward(nCoinAge)) + return DoS(100, error("ConnectInputs() : %s stake reward exceeded", GetHash().ToString().substr(0,10).c_str())); + } + else + { + if (nValueIn < GetValueOut()) + return DoS(100, error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str())); - // Tally transaction fees - int64 nTxFee = nValueIn - GetValueOut(); - if (nTxFee < 0) - return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str())); - if (nTxFee < nMinFee) - return false; - nFees += nTxFee; - if (!MoneyRange(nFees)) - return DoS(100, error("ConnectInputs() : nFees out of range")); + // Tally transaction fees + int64 nTxFee = nValueIn - GetValueOut(); + if (nTxFee < 0) + return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str())); + // ppcoin: enforce transaction fees for every block + if (nTxFee < nMinFee) + return fBlock? DoS(100, error("ConnectInputs() : %s not paying required fee=%s, paid=%s", GetHash().ToString().substr(0,10).c_str(), FormatMoney(nMinFee).c_str(), FormatMoney(nTxFee).c_str())) : false; + nFees += nTxFee; + if (!MoneyRange(nFees)) + return DoS(100, error("ConnectInputs() : nFees out of range")); + } } if (fBlock) @@ -1010,6 +1077,10 @@ bool CBlock::DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex) return error("DisconnectBlock() : WriteBlockIndex failed"); } + // ppcoin: clean up wallet after disconnecting coinstake + BOOST_FOREACH(CTransaction& tx, vtx) + SyncWithWallets(tx, this, false, false); + return true; } @@ -1020,7 +1091,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) return false; //// issue here: it doesn't know the version - unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - 1 + GetSizeOfCompactSize(vtx.size()); + unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size()); map mapQueuedChanges; int64 nFees = 0; @@ -1029,7 +1100,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos); nTxPos += ::GetSerializeSize(tx, SER_DISK); - if (!tx.ConnectInputs(txdb, mapQueuedChanges, posThisTx, pindex, nFees, true, false)) + if (!tx.ConnectInputs(txdb, mapQueuedChanges, posThisTx, pindex, nFees, true, false, tx.GetMinFee())) return false; } // Write queued txindex changes @@ -1039,8 +1110,12 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) return error("ConnectBlock() : UpdateTxIndex failed"); } - if (vtx[0].GetValueOut() > GetBlockValue(nBits, nFees)) + // ppcoin: fees are not collected by miners as in bitcoin + // ppcoin: fees are destroyed to compensate the entire network + 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); // Update block index on disk without changing it in memory. // The memory index structure will be changed after the db commits. @@ -1059,7 +1134,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"); @@ -1100,7 +1175,7 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) // Queue memory transactions to resurrect BOOST_FOREACH(const CTransaction& tx, block.vtx) - if (!tx.IsCoinBase()) + if (!(tx.IsCoinBase() || tx.IsCoinStake())) vResurrect.push_back(tx); } @@ -1125,8 +1200,6 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) } if (!txdb.WriteHashBestChain(pindexNew->GetBlockHash())) return error("Reorganize() : WriteHashBestChain failed"); - if (!txdb.WriteAutoCheckpoint(Checkpoints::GetNextAutoCheckpoint(pindexNew->nCheckpoint))) - return error("Reorganize() : WriteAutoCheckpoint failed"); // Make sure it's successfully written to disk before changing memory structure if (!txdb.TxnCommit()) @@ -1162,7 +1235,6 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) if (pindexGenesisBlock == NULL && hash == hashGenesisBlock) { txdb.WriteHashBestChain(hash); - txdb.WriteAutoCheckpoint(Checkpoints::GetNextAutoCheckpoint(pindexNew->nCheckpoint)); if (!txdb.TxnCommit()) return error("SetBestChain() : TxnCommit failed"); pindexGenesisBlock = pindexNew; @@ -1170,7 +1242,7 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) else if (hashPrevBlock == hashBestChain) { // Adding to current best branch - if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash) || !txdb.WriteAutoCheckpoint(Checkpoints::GetNextAutoCheckpoint(pindexNew->nCheckpoint))) + if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash)) { txdb.TxnAbort(); InvalidChainFound(pindexNew); @@ -1211,60 +1283,141 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) nBestChainTrust = pindexNew->nChainTrust; nTimeBestReceived = GetTime(); nTransactionsUpdated++; - Checkpoints::AdvanceAutoCheckpoint(pindexBest->nCheckpoint); printf("SetBestChain: new best=%s height=%d trust=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str()); return true; } -// ppcoin: total coin age spent in block, in the unit of coin-days. -// Only those coins last spent at least a week ago count. As those +// ppcoin: coinstake must meet hash target according to the protocol: +// 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, 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(unsigned int nBits) const +{ + CBigNum bnTargetPerCoinDay; + bnTargetPerCoinDay.SetCompact(nBits); + + if (!IsCoinStake()) + return true; + + // Input 0 must match the stake hash target per coin age (nBits) + const CTxIn& txin = 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 false; // previous transaction not in main chain + txdb.Close(); + if (nTime < txPrev.nTime) + return false; // Transaction timestamp violation + + // Verify signature + if (!VerifySignature(txPrev, *this, 0)) + return DoS(100, error("CheckProofOfStake() : VerifySignature failed on coinstake %s", GetHash().ToString().c_str())); + + // 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() + STAKE_MIN_AGE > nTime) + return false; // only count coins meeting min age requirement + + 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. +// 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 auto checkpoint. This rule is +// 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. -uint64 CBlock::GetBlockCoinAge() +bool CTransaction::GetCoinAge(CTxDB& txdb, uint64& nCoinAge) const { - CBigNum bnCentSecond = 0; + CBigNum bnCentSecond = 0; // coin age in the unit of cent-seconds + nCoinAge = 0; - BOOST_FOREACH(const CTransaction& tx, vtx) - { - if (tx.IsCoinBase()) - continue; + if (IsCoinBase()) + return true; - BOOST_FOREACH(const CTxIn& txin, tx.vin) - { - // First try finding the previous transaction in database - CTxDB txdb("r"); - CTransaction txPrev; - CTxIndex txindex; - if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex)) - continue; // previous transaction not in main chain - if (tx.nTime < txPrev.nTime) - return 0; // Transaction timestamp violation + 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 0; // unable to read block of previous transaction - if (block.GetBlockTime() + AUTO_CHECKPOINT_TRUST_SPAN > tx.nTime) - continue; // only count coins from at least one week ago + // 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() + STAKE_MIN_AGE > nTime) + continue; // only count coins meeting min age requirement - int64 nValueIn = txPrev.vout[txin.prevout.n].nValue; - bnCentSecond += CBigNum(nValueIn) * (tx.nTime-txPrev.nTime) / CENT; + int64 nValueIn = txPrev.vout[txin.prevout.n].nValue; + bnCentSecond += CBigNum(nValueIn) * (nTime-txPrev.nTime) / CENT; - if (fDebug && GetBoolArg("-printcoinage")) - printf("coin age nValueIn=%-12I64d nTimeDiff=%d bnCentSecond=%s\n", nValueIn, tx.nTime - txPrev.nTime, bnCentSecond.ToString().c_str()); - } + if (fDebug && GetBoolArg("-printcoinage")) + printf("coin age nValueIn=%-12I64d nTimeDiff=%d bnCentSecond=%s\n", nValueIn, nTime - txPrev.nTime, bnCentSecond.ToString().c_str()); } - CBigNum bnCoinAge = bnCentSecond * CENT / COIN / (24 * 60 * 60); - if (bnCoinAge == 0) - bnCoinAge = 1; + 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; +} - return bnCoinAge.getuint64(); +// ppcoin: total coin age spent in block, in the unit of coin-days. +bool CBlock::GetCoinAge(uint64& nCoinAge) const +{ + nCoinAge = 0; + + CTxDB txdb("r"); + BOOST_FOREACH(const CTransaction& tx, vtx) + { + uint64 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=%"PRI64d"\n", nCoinAge); + return true; } @@ -1280,6 +1433,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); @@ -1287,15 +1442,11 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) { pindexNew->pprev = (*miPrev).second; pindexNew->nHeight = pindexNew->pprev->nHeight + 1; - - // ppcoin: compute chain checkpoint - pindexNew->nCheckpoint = Checkpoints::GetNextChainCheckpoint(pindexNew->pprev); - assert (pindexNew->nCheckpoint >= pindexNew->pprev->nCheckpoint); } // ppcoin: compute chain trust score - uint64 nCoinAge = GetBlockCoinAge(); - if (!nCoinAge) + uint64 nCoinAge; + if (!GetCoinAge(nCoinAge)) return error("AddToBlockIndex() : invalid transaction in block"); pindexNew->nChainTrust = (pindexNew->pprev ? pindexNew->pprev->nChainTrust : 0) + nCoinAge; @@ -1337,7 +1488,7 @@ bool CBlock::CheckBlock() const return DoS(100, error("CheckBlock() : size limits failed")); // Check proof of work matches claimed amount - if (!CheckProofOfWork(GetHash(), nBits)) + if (IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits)) return DoS(50, error("CheckBlock() : proof of work failed")); // Check timestamp @@ -1351,10 +1502,23 @@ bool CBlock::CheckBlock() const if (vtx[i].IsCoinBase()) return DoS(100, error("CheckBlock() : more than one coinbase")); + // ppcoin: only the second transaction can be the optional coinstake + for (int i = 2; i < vtx.size(); i++) + 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) { @@ -1373,6 +1537,10 @@ bool CBlock::CheckBlock() const if (hashMerkleRoot != BuildMerkleTree()) return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch")); + // ppcoin: check block signature + if (!CheckBlockSignature()) + return DoS(100, error("CheckBlock() : bad block signature")); + return true; } @@ -1390,9 +1558,9 @@ bool CBlock::AcceptBlock() CBlockIndex* pindexPrev = (*mi).second; int nHeight = pindexPrev->nHeight+1; - // Check proof of work - if (nBits != GetNextWorkRequired(pindexPrev)) - return DoS(100, error("AcceptBlock() : incorrect proof of work")); + // 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")); // Check timestamp against prev if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || GetBlockTime() + nMaxClockDrift < pindexPrev->GetBlockTime()) @@ -1407,9 +1575,9 @@ bool CBlock::AcceptBlock() if (!Checkpoints::CheckHardened(nHeight, hash)) return DoS(100, error("AcceptBlock() : rejected by hardened checkpoint lockin at %d", nHeight)); - // ppcoin: check that the block satisfies automatic checkpoint - 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))) @@ -1428,6 +1596,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; } @@ -1440,43 +1611,67 @@ 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) && !Checkpoints::WantedByPendingSyncCheckpoint(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"); - CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex); - if (pcheckpoint && pblock->hashPrevBlock != hashBestChain) + // 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::GetLastSyncCheckpoint(); + if (pcheckpoint && pblock->hashPrevBlock != hashBestChain && !Checkpoints::WantedByPendingSyncCheckpoint(hash)) { // Extra checks to prevent "fill up memory by spamming with bogus blocks" int64 deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime; - if (deltaTime < 0) - { - pfrom->Misbehaving(100); - return error("ProcessBlock() : block with timestamp before last checkpoint"); - } CBigNum bnNewBlock; bnNewBlock.SetCompact(pblock->nBits); CBigNum bnRequired; - bnRequired.SetCompact(ComputeMinWork(pcheckpoint->nBits, deltaTime)); + bnRequired.SetCompact(ComputeMinWork(GetLastBlockIndex(pcheckpoint, pblock->IsProofOfStake())->nBits, deltaTime)); + 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"); } } + // ppcoin: ask for pending sync-checkpoint if any + if (!IsInitialBlockDownload()) + Checkpoints::AskForPendingSyncCheckpoint(pfrom); // If don't already have its previous block, shunt it off to holding area until we get it if (!mapBlockIndex.count(pblock->hashPrevBlock)) { 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) && !Checkpoints::WantedByPendingSyncCheckpoint(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, WantedByOrphan(pblock2))); + } return true; } @@ -1498,6 +1693,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); @@ -1577,7 +1773,7 @@ bool LoadBlockIndex(bool fAllowNew) { if (fTestNet) { - hashGenesisBlock = uint256("0x00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"); + hashGenesisBlock = hashGenesisBlockTestNet; bnProofOfWorkLimit = CBigNum(~uint256(0) >> 28); pchMessageStart[0] = 0xfa; pchMessageStart[1] = 0xbf; @@ -1611,20 +1807,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 = 1325225177; + 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 = GetBlockValue(bnProofOfWorkLimit.GetCompact(), 0); - 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 = 1325228535; + block.nTime = 1339540307; block.nBits = bnProofOfWorkLimit.GetCompact(); - block.nNonce = 1664714209; + block.nNonce = 1281822831; if (fTestNet) { @@ -1637,9 +1832,10 @@ 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("0x1fe83f5d79b725b13786bd4377b6fd4d5bba5c2777649a989783d47a63a27b91")); + assert(block.hashMerkleRoot == uint256("0x1557f46a17fcf8843dbe4c0c0edfd1d17eeff2c3c48d73a59d11f5d176e4b54d")); block.print(); assert(block.GetHash() == hashGenesisBlock); + assert(block.CheckBlock()); // Start new block file unsigned int nFile; @@ -1648,6 +1844,28 @@ 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 + if (!Checkpoints::WriteSyncCheckpoint(hashGenesisBlock)) + return error("LoadBlockIndex() : failed to init sync checkpoint"); + } + + // ppcoin: if checkpoint master key changed must reset sync-checkpoint + { + CTxDB txdb; + string strPubKey = ""; + if (!txdb.ReadCheckpointPubKey(strPubKey) || strPubKey != CSyncCheckpoint::strMasterPubKey) + { + // write checkpoint master key to db + txdb.TxnBegin(); + if (!txdb.WriteCheckpointPubKey(CSyncCheckpoint::strMasterPubKey)) + return error("LoadBlockIndex() : failed to write new checkpoint master key to db"); + if (!txdb.TxnCommit()) + return error("LoadBlockIndex() : failed to commit new checkpoint master key to db"); + if (!Checkpoints::ResetSyncCheckpoint()) + return error("LoadBlockIndex() : failed to reset sync-checkpoint"); + } + txdb.Close(); } return true; @@ -1700,11 +1918,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()); @@ -1766,6 +1985,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) { @@ -1920,6 +2145,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) return true; } + // ppcoin: record my external IP reported by peer + if (addrFrom.IsRoutable() && addrMe.IsRoutable()) + addrSeenByPeer = addrMe; + // Be shy and don't send version until we hear if (pfrom->fInbound) pfrom->PushVersion(); @@ -1968,11 +2197,20 @@ 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); cPeerBlockCounts.input(pfrom->nStartingHeight); + + // ppcoin: ask for pending sync-checkpoint if any + if (!IsInitialBlockDownload()) + Checkpoints::AskForPendingSyncCheckpoint(pfrom); } @@ -2381,6 +2619,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 { @@ -2807,9 +3059,9 @@ public: }; -CBlock* CreateNewBlock(CReserveKey& reservekey) +CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfWorkOnly) { - CBlockIndex* pindexPrev = pindexBest; + CReserveKey reservekey(pwallet); // Create new block auto_ptr pblock(new CBlock()); @@ -2826,6 +3078,36 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) // Add our coinbase tx as first transaction pblock->vtx.push_back(txNew); + // ppcoin: if coinstake available add coinstake tx + static unsigned int nLastCoinStakeCheckTime = GetAdjustedTime() - nMaxClockDrift + 60; // only initialized at startup + CBlockIndex* pindexPrev = pindexBest; + + if (!fProofOfWorkOnly) + { + 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) + { + // 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(pblock->nBits, txCoinStake)) + { + pblock->vtx.push_back(txCoinStake); + pblock->vtx[0].vout[0].SetEmpty(); + break; + } + } + } + + pblock->nBits = GetNextTargetRequired(pindexPrev, pblock->IsProofOfStake()); + // Collect memory pool transactions into the block int64 nFees = 0; CRITICAL_BLOCK(cs_main) @@ -2840,7 +3122,7 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) for (map::iterator mi = mapTransactions.begin(); mi != mapTransactions.end(); ++mi) { CTransaction& tx = (*mi).second; - if (tx.IsCoinBase() || !tx.IsFinal()) + if (tx.IsCoinBase() || tx.IsCoinStake() || !tx.IsFinal()) continue; COrphan* porphan = NULL; @@ -2945,8 +3227,8 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) } } } - pblock->nBits = GetNextWorkRequired(pindexPrev); - pblock->vtx[0].vout[0].nValue = GetBlockValue(pblock->nBits, nFees); + if (pblock->IsProofOfWork()) + pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits); // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash(); @@ -3026,12 +3308,12 @@ bool CheckWork(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey) uint256 hash = pblock->GetHash(); uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256(); - if (hash > hashTarget) - return false; + if (hash > hashTarget && pblock->IsProofOfWork()) + return error("BitcoinMiner : proof-of-work not meeting target"); //// debug print printf("BitcoinMiner:\n"); - printf("proof-of-work found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); + printf("new block found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); pblock->print(); printf("%s ", DateTimeStrFormat("%x %H:%M", GetTime()).c_str()); printf("generated %s\n", FormatMoney(pblock->vtx[0].vout[0].nValue).c_str()); @@ -3090,11 +3372,27 @@ void static BitcoinMiner(CWallet *pwallet) unsigned int nTransactionsUpdatedLast = nTransactionsUpdated; CBlockIndex* pindexPrev = pindexBest; - auto_ptr pblock(CreateNewBlock(reservekey)); + auto_ptr pblock(CreateNewBlock(pwallet)); if (!pblock.get()) return; + IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce); + // ppcoin: if proof-of-stake block found then process block + if (pblock->IsProofOfStake()) + { + if (!pblock->SignBlock(*pwalletMain)) + { + error("BitcoinMiner: Unable to sign new proof-of-stake block"); + return; + } + printf("BitcoinMiner : proof-of-stake block found %s\n", pblock->GetHash().ToString().c_str()); + SetThreadPriority(THREAD_PRIORITY_NORMAL); + CheckWork(pblock.get(), *pwalletMain, reservekey); + SetThreadPriority(THREAD_PRIORITY_LOWEST); + continue; + } + printf("Running BitcoinMiner with %d transactions in block\n", pblock->vtx.size()); @@ -3138,6 +3436,11 @@ void static BitcoinMiner(CWallet *pwallet) // Found a solution pblock->nNonce = ByteReverse(nNonceFound); assert(hash == pblock->GetHash()); + if (!pblock->SignBlock(*pwalletMain)) + { + error("BitcoinMiner: Unable to sign new proof-of-work block"); + return; + } SetThreadPriority(THREAD_PRIORITY_NORMAL); CheckWork(pblock.get(), *pwalletMain, reservekey);