X-Git-Url: https://git.novaco.in/?p=novacoin.git;a=blobdiff_plain;f=src%2Fmain.cpp;h=85640d75c73ce9f6f6c208dccac7d3b08db9b4b9;hp=e5234c8f170e9ecc8bc7def04dded9fdbf660aef;hb=8e064484abe4d65e15e3ec1bcdbb2b91f97dd726;hpb=4bb99fbce1b57eef730491a98d442679a1e6bd7f diff --git a/src/main.cpp b/src/main.cpp index e5234c8..c41f43a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,72 +1,87 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2011 The Bitcoin developers -// Copyright (c) 2011-2012 The PPCoin developers +// Copyright (c) 2009-2012 The Bitcoin 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" +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "alert.h" #include "checkpoints.h" #include "db.h" -#include "net.h" +#include "txdb.h" #include "init.h" +#include "ui_interface.h" +#include "checkqueue.h" +#include "kernel.h" +#include #include #include +#include "main.h" + using namespace std; using namespace boost; -// -// Global state -// + CCriticalSection cs_setpwalletRegistered; set setpwalletRegistered; CCriticalSection cs_main; -static map mapTransactions; -CCriticalSection cs_mapTransactions; +CTxMemPool mempool; unsigned int nTransactionsUpdated = 0; -map mapNextTx; map mapBlockIndex; set > setStakeSeen; -uint256 hashGenesisBlock("0x000000007c82d1f0aa2896b01bf533a8cc26a1f44790be4ceb4ecde7bee24add"); -static CBigNum bnProofOfWorkLimit(~uint256(0) >> 32); -const int nInitialBlockThreshold = 120; // Regard blocks up until N-threshold as "initial download" + +CBigNum bnProofOfWorkLimit(~uint256(0) >> 20); // "standard" scrypt target limit for proof of work, results with 0,000244140625 proof-of-work difficulty +CBigNum bnProofOfStakeLegacyLimit(~uint256(0) >> 24); // proof of stake target limit from block #15000 and until 20 June 2013, results with 0,00390625 proof of stake difficulty +CBigNum bnProofOfStakeLimit(~uint256(0) >> 27); // proof of stake target limit since 20 June 2013, equal to 0.03125 proof of stake difficulty +CBigNum bnProofOfStakeHardLimit(~uint256(0) >> 30); // disabled temporarily, will be used in the future to fix minimal proof of stake difficulty at 0.25 +uint256 nPoWBase = uint256("0x00000000ffff0000000000000000000000000000000000000000000000000000"); // difficulty-1 target + +CBigNum bnProofOfWorkLimitTestNet(~uint256(0) >> 16); + +unsigned int nStakeMinAge = 30 * nOneDay; // 30 days as zero time weight +unsigned int nStakeMaxAge = 90 * nOneDay; // 90 days as full weight +unsigned int nStakeTargetSpacing = 10 * 60; // 10-minute stakes spacing +unsigned int nModifierInterval = 6 * nOneHour; // time to elapse before new modifier is computed + +int nCoinbaseMaturity = 500; + CBlockIndex* pindexGenesisBlock = NULL; int nBestHeight = -1; -uint64 nBestChainTrust = 0; -uint64 nBestInvalidTrust = 0; + +uint256 nBestChainTrust = 0; +uint256 nBestInvalidTrust = 0; + uint256 hashBestChain = 0; CBlockIndex* pindexBest = NULL; -int64 nTimeBestReceived = 0; +int64_t nTimeBestReceived = 0; +int nScriptCheckThreads = 0; CMedianFilter cPeerBlockCounts(5, 0); // Amount of blocks that other nodes claim to have map mapOrphanBlocks; multimap mapOrphanBlocksByPrev; set > setStakeSeenOrphan; +map mapProofOfStake; -map mapOrphanTransactions; -multimap mapOrphanTransactionsByPrev; +map mapOrphanTransactions; +map > mapOrphanTransactionsByPrev; +// Constant stuff for coinbase transactions we create: +CScript COINBASE_FLAGS; -double dHashesPerSec; -int64 nHPSTimerStart; +const string strMessageMagic = "NovaCoin Signed Message:\n"; // Settings -int fGenerateBitcoins = false; -int64 nTransactionFee = MIN_TX_FEE; -int fLimitProcessors = false; -int nLimitProcessors = 1; -int fMinimizeToTray = true; -int fMinimizeOnClose = true; -#if USE_UPNP -int fUseUPnP = true; -#else -int fUseUPnP = false; -#endif -int64 nBalanceReserve = 0; +int64_t nTransactionFee = MIN_TX_FEE; +int64_t nMinimumInputValue = MIN_TXOUT_AMOUNT; + +// Ping and address broadcast intervals +int64_t nPingInterval = 30 * 60; + +extern enum Checkpoints::CPMode CheckpointsMode; ////////////////////////////////////////////////////////////////////////////// // @@ -78,16 +93,16 @@ int64 nBalanceReserve = 0; void RegisterWallet(CWallet* pwalletIn) { - CRITICAL_BLOCK(cs_setpwalletRegistered) { + LOCK(cs_setpwalletRegistered); setpwalletRegistered.insert(pwalletIn); } } void UnregisterWallet(CWallet* pwalletIn) { - CRITICAL_BLOCK(cs_setpwalletRegistered) { + LOCK(cs_setpwalletRegistered); setpwalletRegistered.erase(pwalletIn); } } @@ -118,15 +133,17 @@ 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, bool fConnect = true) +void SyncWithWallets(const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fConnect) { if (!fConnect) { - // ppcoin: wallets need to refund inputs when disconnecting coinstake + // wallets need to refund inputs when disconnecting coinstake if (tx.IsCoinStake()) + { BOOST_FOREACH(CWallet* pwallet, setpwalletRegistered) if (pwallet->IsFromMe(tx)) pwallet->DisableTransaction(tx); + } return; } @@ -163,10 +180,10 @@ void static Inventory(const uint256& hash) } // ask wallets to resend their transactions -void static ResendWalletTransactions() +void ResendWalletTransactions(bool fForceResend) { BOOST_FOREACH(CWallet* pwallet, setpwalletRegistered) - pwallet->ResendWalletTransactions(); + pwallet->ResendWalletTransactions(fForceResend); } @@ -180,40 +197,66 @@ void static ResendWalletTransactions() // mapOrphanTransactions // -void static AddOrphanTx(const CDataStream& vMsg) +bool AddOrphanTx(const CTransaction& tx) { - CTransaction tx; - CDataStream(vMsg) >> tx; uint256 hash = tx.GetHash(); if (mapOrphanTransactions.count(hash)) - return; - CDataStream* pvMsg = mapOrphanTransactions[hash] = new CDataStream(vMsg); + return false; + + // Ignore big transactions, to avoid a + // send-big-orphans memory exhaustion attack. If a peer has a legitimate + // large transaction with a missing parent then we assume + // it will rebroadcast it later, after the parent transaction(s) + // have been mined or received. + // 10,000 orphans, each of which is at most 5,000 bytes big is + // at most 500 megabytes of orphans: + + size_t nSize = tx.GetSerializeSize(SER_NETWORK, CTransaction::CURRENT_VERSION); + + if (nSize > 5000) + { + printf("ignoring large orphan tx (size: %" PRIszu ", hash: %s)\n", nSize, hash.ToString().substr(0,10).c_str()); + return false; + } + + mapOrphanTransactions[hash] = tx; BOOST_FOREACH(const CTxIn& txin, tx.vin) - mapOrphanTransactionsByPrev.insert(make_pair(txin.prevout.hash, pvMsg)); + mapOrphanTransactionsByPrev[txin.prevout.hash].insert(hash); + + printf("stored orphan tx %s (mapsz %" PRIszu ")\n", hash.ToString().substr(0,10).c_str(), + mapOrphanTransactions.size()); + return true; } void static EraseOrphanTx(uint256 hash) { if (!mapOrphanTransactions.count(hash)) return; - const CDataStream* pvMsg = mapOrphanTransactions[hash]; - CTransaction tx; - CDataStream(*pvMsg) >> tx; + const CTransaction& tx = mapOrphanTransactions[hash]; BOOST_FOREACH(const CTxIn& txin, tx.vin) { - for (multimap::iterator mi = mapOrphanTransactionsByPrev.lower_bound(txin.prevout.hash); - mi != mapOrphanTransactionsByPrev.upper_bound(txin.prevout.hash);) - { - if ((*mi).second == pvMsg) - mapOrphanTransactionsByPrev.erase(mi++); - else - mi++; - } + mapOrphanTransactionsByPrev[txin.prevout.hash].erase(hash); + if (mapOrphanTransactionsByPrev[txin.prevout.hash].empty()) + mapOrphanTransactionsByPrev.erase(txin.prevout.hash); } - delete pvMsg; mapOrphanTransactions.erase(hash); } +unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) +{ + unsigned int nEvicted = 0; + while (mapOrphanTransactions.size() > nMaxOrphans) + { + // Evict a random orphan: + uint256 randomhash = GetRandHash(); + map::iterator it = mapOrphanTransactions.lower_bound(randomhash); + if (it == mapOrphanTransactions.end()) + it = mapOrphanTransactions.begin(); + EraseOrphanTx(it->first); + ++nEvicted; + } + return nEvicted; +} @@ -254,6 +297,152 @@ bool CTransaction::ReadFromDisk(COutPoint prevout) return ReadFromDisk(txdb, prevout, txindex); } +bool CTransaction::IsStandard(string& strReason) const +{ + if (nVersion > CTransaction::CURRENT_VERSION) + { + strReason = "version"; + return false; + } + + unsigned int nDataOut = 0; + txnouttype whichType; + BOOST_FOREACH(const CTxIn& txin, vin) + { + // Biggest 'standard' txin is a 15-of-15 P2SH multisig with compressed + // keys. (remember the 520 byte limit on redeemScript size) That works + // out to a (15*(33+1))+3=513 byte redeemScript, 513+1+15*(73+1)=1624 + // bytes of scriptSig, which we round off to 1650 bytes for some minor + // future-proofing. That's also enough to spend a 20-of-20 + // CHECKMULTISIG scriptPubKey, though such a scriptPubKey is not + // considered standard) + if (txin.scriptSig.size() > 1650) + { + strReason = "scriptsig-size"; + return false; + } + if (!txin.scriptSig.IsPushOnly()) + { + strReason = "scriptsig-not-pushonly"; + return false; + } + if (!txin.scriptSig.HasCanonicalPushes()) { + strReason = "txin-scriptsig-not-canonicalpushes"; + return false; + } + } + BOOST_FOREACH(const CTxOut& txout, vout) { + if (!::IsStandard(txout.scriptPubKey, whichType)) { + strReason = "scriptpubkey"; + return false; + } + if (whichType == TX_NULL_DATA) + nDataOut++; + else { + if (txout.nValue == 0) { + strReason = "txout-value=0"; + return false; + } + if (!txout.scriptPubKey.HasCanonicalPushes()) { + strReason = "txout-scriptsig-not-canonicalpushes"; + return false; + } + } + } + + // only one OP_RETURN txout is permitted + if (nDataOut > 1) { + strReason = "multi-op-return"; + return false; + } + + return true; +} + +// +// Check transaction inputs, and make sure any +// pay-to-script-hash transactions are evaluating IsStandard scripts +// +// Why bother? To avoid denial-of-service attacks; an attacker +// can submit a standard HASH... OP_EQUAL transaction, +// which will get accepted into blocks. The redemption +// script can be anything; an attacker could use a very +// expensive-to-check-upon-redemption script like: +// DUP CHECKSIG DROP ... repeated 100 times... OP_1 +// +bool CTransaction::AreInputsStandard(const MapPrevTx& mapInputs) const +{ + if (IsCoinBase()) + return true; // Coinbases don't use vin normally + + for (unsigned int i = 0; i < vin.size(); i++) + { + const CTxOut& prev = GetOutputFor(vin[i], mapInputs); + + vector > vSolutions; + txnouttype whichType; + // get the scriptPubKey corresponding to this input: + const CScript& prevScript = prev.scriptPubKey; + if (!Solver(prevScript, whichType, vSolutions)) + return false; + int nArgsExpected = ScriptSigArgsExpected(whichType, vSolutions); + if (nArgsExpected < 0) + return false; + + // Transactions with extra stuff in their scriptSigs are + // non-standard. Note that this EvalScript() call will + // be quick, because if there are any operations + // beside "push data" in the scriptSig the + // IsStandard() call returns false + vector > stack; + if (!EvalScript(stack, vin[i].scriptSig, *this, i, false, 0)) + return false; + + if (whichType == TX_SCRIPTHASH) + { + if (stack.empty()) + return false; + CScript subscript(stack.back().begin(), stack.back().end()); + vector > vSolutions2; + txnouttype whichType2; + if (!Solver(subscript, whichType2, vSolutions2)) + return false; + if (whichType2 == TX_SCRIPTHASH) + return false; + + int tmpExpected; + tmpExpected = ScriptSigArgsExpected(whichType2, vSolutions2); + if (tmpExpected < 0) + return false; + nArgsExpected += tmpExpected; + } + + if (stack.size() != (unsigned int)nArgsExpected) + return false; + } + + return true; +} + +unsigned int +CTransaction::GetLegacySigOpCount() const +{ + unsigned int nSigOps = 0; + if (!IsCoinBase()) + { + // Coinbase scriptsigs are never executed, so there is + // no sense in calculation of sigops. + BOOST_FOREACH(const CTxIn& txin, vin) + { + nSigOps += txin.scriptSig.GetSigOpCount(false); + } + } + BOOST_FOREACH(const CTxOut& txout, vout) + { + nSigOps += txout.scriptPubKey.GetSigOpCount(false); + } + return nSigOps; +} int CMerkleTx::SetMerkleBranch(const CBlock* pblock) @@ -281,10 +470,10 @@ int CMerkleTx::SetMerkleBranch(const CBlock* pblock) hashBlock = pblock->GetHash(); // Locate the transaction - for (nIndex = 0; nIndex < pblock->vtx.size(); nIndex++) + for (nIndex = 0; nIndex < (int)pblock->vtx.size(); nIndex++) if (pblock->vtx[nIndex] == *(CTransaction*)this) break; - if (nIndex == pblock->vtx.size()) + if (nIndex == (int)pblock->vtx.size()) { vMerkleBranch.clear(); nIndex = -1; @@ -321,18 +510,19 @@ bool CTransaction::CheckTransaction() const if (vout.empty()) return DoS(10, error("CTransaction::CheckTransaction() : vout empty")); // Size limits - if (::GetSerializeSize(*this, SER_NETWORK) > MAX_BLOCK_SIZE) + if (::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE) return DoS(100, error("CTransaction::CheckTransaction() : size limits failed")); // Check for negative or overflow output values - int64 nValueOut = 0; - for (int i = 0; i < vout.size(); i++) + int64_t nValueOut = 0; + for (unsigned int i = 0; i < vout.size(); i++) { const CTxOut& txout = vout[i]; - if (txout.IsEmpty() && (!IsCoinBase()) && (!IsCoinStake())) + 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")); + return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue is negative")); if (txout.nValue > MAX_MONEY) return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue too high")); nValueOut += txout.nValue; @@ -352,7 +542,7 @@ bool CTransaction::CheckTransaction() const if (IsCoinBase()) { if (vin[0].scriptSig.size() < 2 || vin[0].scriptSig.size() > 100) - return DoS(100, error("CTransaction::CheckTransaction() : coinbase script size")); + return DoS(100, error("CTransaction::CheckTransaction() : coinbase script size is invalid")); } else { @@ -364,52 +554,109 @@ bool CTransaction::CheckTransaction() const return true; } -bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMissingInputs) +int64_t CTransaction::GetMinFee(unsigned int nBlockSize, bool fAllowFree, enum GetMinFee_mode mode, unsigned int nBytes) const +{ + int64_t nMinTxFee = MIN_TX_FEE, nMinRelayTxFee = MIN_RELAY_TX_FEE; + + if(IsCoinStake()) + { + // Enforce 0.01 as minimum fee for coinstake + nMinTxFee = CENT; + nMinRelayTxFee = CENT; + } + + // Base fee is either nMinTxFee or nMinRelayTxFee + int64_t nBaseFee = (mode == GMF_RELAY) ? nMinRelayTxFee : nMinTxFee; + + unsigned int nNewBlockSize = nBlockSize + nBytes; + int64_t nMinFee = (1 + (int64_t)nBytes / 1000) * nBaseFee; + + if (fAllowFree) + { + if (nBlockSize == 1) + { + // Transactions under 1K are free + if (nBytes < 1000) + nMinFee = 0; + } + else + { + // Free transaction area + if (nNewBlockSize < 27000) + nMinFee = 0; + } + } + + // To limit dust spam, require additional MIN_TX_FEE/MIN_RELAY_TX_FEE for + // each non empty output which is less than 0.01 + // + // It's safe to ignore empty outputs here, because these inputs are allowed + // only for coinbase and coinstake transactions. + BOOST_FOREACH(const CTxOut& txout, vout) + if (txout.nValue < CENT && !txout.IsEmpty()) + nMinFee += nBaseFee; + + // Raise the price as the block approaches full + if (nBlockSize != 1 && nNewBlockSize >= MAX_BLOCK_SIZE_GEN/2) + { + if (nNewBlockSize >= MAX_BLOCK_SIZE_GEN) + return MAX_MONEY; + nMinFee *= MAX_BLOCK_SIZE_GEN / (MAX_BLOCK_SIZE_GEN - nNewBlockSize); + } + + if (!MoneyRange(nMinFee)) + nMinFee = MAX_MONEY; + + return nMinFee; +} + + +bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, + bool* pfMissingInputs) { if (pfMissingInputs) *pfMissingInputs = false; - if (!CheckTransaction()) - return error("AcceptToMemoryPool() : CheckTransaction failed"); + // Time (prevent mempool memory exhaustion attack) + if (tx.nTime > FutureDrift(GetAdjustedTime())) + return tx.DoS(10, error("CTxMemPool::accept() : transaction timestamp is too far in the future")); + + if (!tx.CheckTransaction()) + return error("CTxMemPool::accept() : CheckTransaction failed"); // Coinbase is only valid in a block, not as a loose transaction - if (IsCoinBase()) - return DoS(100, error("AcceptToMemoryPool() : coinbase as individual tx")); + if (tx.IsCoinBase()) + return tx.DoS(100, error("CTxMemPool::accept() : 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")); + if (tx.IsCoinStake()) + return tx.DoS(100, error("CTxMemPool::accept() : coinstake as individual tx")); // To help v0.1.5 clients who would see it as a negative number - if ((int64)nLockTime > INT_MAX) - return error("AcceptToMemoryPool() : not accepting nLockTime beyond 2038 yet"); - - // Safety limits - unsigned int nSize = ::GetSerializeSize(*this, SER_NETWORK); - // Checking ECDSA signatures is a CPU bottleneck, so to avoid denial-of-service - // attacks disallow transactions with more than one SigOp per 34 bytes. - // 34 bytes because a TxOut is: - // 20-byte address + 8 byte bitcoin amount + 5 bytes of ops + 1 byte script length - if (GetSigOpCount() > nSize / 34 || nSize < 100) - return error("AcceptToMemoryPool() : transaction with out-of-bounds SigOpCount"); + if ((int64_t)tx.nLockTime > std::numeric_limits::max()) + return error("CTxMemPool::accept() : not accepting nLockTime beyond 2038 yet"); // Rather not work on nonstandard transactions (unless -testnet) - if (!fTestNet && !IsStandard()) - return error("AcceptToMemoryPool() : nonstandard transaction type"); + string strNonStd; + if (!fTestNet && !tx.IsStandard(strNonStd)) + return error("CTxMemPool::accept() : nonstandard transaction (%s)", strNonStd.c_str()); // Do we already have it? - uint256 hash = GetHash(); - CRITICAL_BLOCK(cs_mapTransactions) - if (mapTransactions.count(hash)) + uint256 hash = tx.GetHash(); + { + LOCK(cs); + if (mapTx.count(hash)) return false; + } if (fCheckInputs) if (txdb.ContainsTx(hash)) return false; // Check for conflicts with in-memory transactions CTransaction* ptxOld = NULL; - for (int i = 0; i < vin.size(); i++) + for (unsigned int i = 0; i < tx.vin.size(); i++) { - COutPoint outpoint = vin[i].prevout; + COutPoint outpoint = tx.vin[i].prevout; if (mapNextTx.count(outpoint)) { // Disable replacement feature for now @@ -421,11 +668,11 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi ptxOld = mapNextTx[outpoint].ptx; if (ptxOld->IsFinal()) return false; - if (!IsNewerThan(*ptxOld)) + if (!tx.IsNewerThan(*ptxOld)) return false; - for (int i = 0; i < vin.size(); i++) + for (unsigned int i = 0; i < tx.vin.size(); i++) { - COutPoint outpoint = vin[i].prevout; + COutPoint outpoint = tx.vin[i].prevout; if (!mapNextTx.count(outpoint) || mapNextTx[outpoint].ptx != ptxOld) return false; } @@ -435,55 +682,78 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi if (fCheckInputs) { - // Check against previous transactions + MapPrevTx mapInputs; map mapUnused; - int64 nFees = 0; - if (!ConnectInputs(txdb, mapUnused, CDiskTxPos(1,1,1), pindexBest, nFees, false, false)) + bool fInvalid = false; + if (!tx.FetchInputs(txdb, mapUnused, false, false, mapInputs, fInvalid)) { + if (fInvalid) + return error("CTxMemPool::accept() : FetchInputs found invalid tx %s", hash.ToString().substr(0,10).c_str()); if (pfMissingInputs) *pfMissingInputs = true; - return error("AcceptToMemoryPool() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str()); + return false; } + // Check for non-standard pay-to-script-hash in inputs + if (!tx.AreInputsStandard(mapInputs) && !fTestNet) + return error("CTxMemPool::accept() : nonstandard transaction input"); + + // Note: if you modify this code to accept non-standard transactions, then + // you should add code here to check that the transaction does a + // reasonable number of ECDSA signature verifications. + + int64_t nFees = tx.GetValueIn(mapInputs)-tx.GetValueOut(); + unsigned int nSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); + // Don't accept it if it can't get into a block - if (nFees < GetMinFee(1000, false, true)) - return error("AcceptToMemoryPool() : not enough fees"); + int64_t txMinFee = tx.GetMinFee(1000, true, GMF_RELAY, nSize); + if (nFees < txMinFee) + return error("CTxMemPool::accept() : not enough fees %s, %" PRId64 " < %" PRId64, + hash.ToString().c_str(), + nFees, txMinFee); // Continuously rate-limit free transactions // This mitigates 'penny-flooding' -- sending thousands of free transactions just to - // be annoying or make other's transactions take longer to confirm. + // be annoying or make others' transactions take longer to confirm. if (nFees < MIN_RELAY_TX_FEE) { static CCriticalSection cs; static double dFreeCount; - static int64 nLastTime; - int64 nNow = GetTime(); + static int64_t nLastTime; + int64_t nNow = GetTime(); - CRITICAL_BLOCK(cs) { + LOCK(cs); // Use an exponentially decaying ~10-minute window: dFreeCount *= pow(1.0 - 1.0/600.0, (double)(nNow - nLastTime)); nLastTime = nNow; // -limitfreerelay unit is thousand-bytes-per-minute // At default rate it would take over a month to fill 1GB - if (dFreeCount > GetArg("-limitfreerelay", 15)*10*1000 && !IsFromMe(*this)) - return error("AcceptToMemoryPool() : free transaction rejected by rate limiter"); + if (dFreeCount > GetArg("-limitfreerelay", 15)*10*1000 && !IsFromMe(tx)) + return error("CTxMemPool::accept() : free transaction rejected by rate limiter"); if (fDebug) printf("Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize); dFreeCount += nSize; } } + + // Check against previous transactions + // This is done last to help prevent CPU exhaustion denial-of-service attacks. + if (!tx.ConnectInputs(txdb, mapInputs, mapUnused, CDiskTxPos(1,1,1), pindexBest, false, false, true, STRICT_FLAGS)) + { + return error("CTxMemPool::accept() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str()); + } } // Store transaction in memory - CRITICAL_BLOCK(cs_mapTransactions) { + LOCK(cs); if (ptxOld) { - printf("AcceptToMemoryPool() : replacing tx %s with new version\n", ptxOld->GetHash().ToString().c_str()); - ptxOld->RemoveFromMemoryPool(); + printf("CTxMemPool::accept() : replacing tx %s with new version\n", ptxOld->GetHash().ToString().c_str()); + remove(*ptxOld); } - AddToMemoryPoolUnchecked(); + addUnchecked(hash, tx); } ///// are we sure this is ok when loading transactions or restoring block txes @@ -491,51 +761,70 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi if (ptxOld) EraseFromWallets(ptxOld->GetHash()); - printf("AcceptToMemoryPool(): accepted %s\n", hash.ToString().substr(0,10).c_str()); + printf("CTxMemPool::accept() : accepted %s (poolsz %" PRIszu ")\n", + hash.ToString().substr(0,10).c_str(), + mapTx.size()); return true; } -bool CTransaction::AcceptToMemoryPool(bool fCheckInputs, bool* pfMissingInputs) +bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMissingInputs) { - CTxDB txdb("r"); - return AcceptToMemoryPool(txdb, fCheckInputs, pfMissingInputs); + return mempool.accept(txdb, *this, fCheckInputs, pfMissingInputs); } -bool CTransaction::AddToMemoryPoolUnchecked() +bool CTxMemPool::addUnchecked(const uint256& hash, CTransaction &tx) { // Add to memory pool without checking anything. Don't call this directly, - // call AcceptToMemoryPool to properly check the transaction first. - CRITICAL_BLOCK(cs_mapTransactions) + // call CTxMemPool::accept to properly check the transaction first. { - uint256 hash = GetHash(); - mapTransactions[hash] = *this; - for (int i = 0; i < vin.size(); i++) - mapNextTx[vin[i].prevout] = CInPoint(&mapTransactions[hash], i); + mapTx[hash] = tx; + for (unsigned int i = 0; i < tx.vin.size(); i++) + mapNextTx[tx.vin[i].prevout] = CInPoint(&mapTx[hash], i); nTransactionsUpdated++; } return true; } -bool CTransaction::RemoveFromMemoryPool() +bool CTxMemPool::remove(CTransaction &tx) { // Remove transaction from memory pool - CRITICAL_BLOCK(cs_mapTransactions) { - BOOST_FOREACH(const CTxIn& txin, vin) - mapNextTx.erase(txin.prevout); - mapTransactions.erase(GetHash()); - nTransactionsUpdated++; + LOCK(cs); + uint256 hash = tx.GetHash(); + if (mapTx.count(hash)) + { + BOOST_FOREACH(const CTxIn& txin, tx.vin) + mapNextTx.erase(txin.prevout); + mapTx.erase(hash); + nTransactionsUpdated++; + } } return true; } +void CTxMemPool::clear() +{ + LOCK(cs); + mapTx.clear(); + mapNextTx.clear(); + ++nTransactionsUpdated; +} + +void CTxMemPool::queryHashes(std::vector& vtxid) +{ + vtxid.clear(); + LOCK(cs); + vtxid.reserve(mapTx.size()); + for (map::iterator mi = mapTx.begin(); mi != mapTx.end(); ++mi) + vtxid.push_back((*mi).first); +} -int CMerkleTx::GetDepthInMainChain(int& nHeightRet) const +int CMerkleTx::GetDepthInMainChain(CBlockIndex* &pindexRet) const { if (hashBlock == 0 || nIndex == -1) return 0; @@ -556,7 +845,7 @@ int CMerkleTx::GetDepthInMainChain(int& nHeightRet) const fMerkleVerified = true; } - nHeightRet = pindex->nHeight; + pindexRet = pindex; return pindexBest->nHeight - pindex->nHeight + 1; } @@ -565,7 +854,7 @@ int CMerkleTx::GetBlocksToMaturity() const { if (!(IsCoinBase() || IsCoinStake())) return 0; - return max(0, (COINBASE_MATURITY+20) - GetDepthInMainChain()); + return max(0, (nCoinbaseMaturity+20) - GetDepthInMainChain()); } @@ -593,15 +882,16 @@ bool CMerkleTx::AcceptToMemoryPool() bool CWalletTx::AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs) { - CRITICAL_BLOCK(cs_mapTransactions) + { + LOCK(mempool.cs); // Add previous supporting transactions first BOOST_FOREACH(CMerkleTx& tx, vtxPrev) { if (!(tx.IsCoinBase() || tx.IsCoinStake())) { uint256 hash = tx.GetHash(); - if (!mapTransactions.count(hash) && !txdb.ContainsTx(hash)) + if (!mempool.exists(hash) && !txdb.ContainsTx(hash)) tx.AcceptToMemoryPool(txdb, fCheckInputs); } } @@ -610,7 +900,7 @@ bool CWalletTx::AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs) return false; } -bool CWalletTx::AcceptWalletTransaction() +bool CWalletTx::AcceptWalletTransaction() { CTxDB txdb("r"); return AcceptWalletTransaction(txdb); @@ -632,7 +922,31 @@ int CTxIndex::GetDepthInMainChain() const return 1 + nBestHeight - pindex->nHeight; } - +// Return transaction in tx, and if it was found inside a block, its hash is placed in hashBlock +bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock) +{ + { + LOCK(cs_main); + { + LOCK(mempool.cs); + if (mempool.exists(hash)) + { + tx = mempool.lookup(hash); + return true; + } + } + CTxDB txdb("r"); + CTxIndex txindex; + if (tx.ReadFromDisk(txdb, COutPoint(hash, 0), txindex)) + { + CBlock block; + if (block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) + hashBlock = block.GetHash(); + return true; + } + } + return false; +} @@ -646,6 +960,24 @@ int CTxIndex::GetDepthInMainChain() const // CBlock and CBlockIndex // +static CBlockIndex* pblockindexFBBHLast; +CBlockIndex* FindBlockByHeight(int nHeight) +{ + CBlockIndex *pblockindex; + if (nHeight < nBestHeight / 2) + pblockindex = pindexGenesisBlock; + else + pblockindex = pindexBest; + if (pblockindexFBBHLast && abs(nHeight - pblockindex->nHeight) > abs(nHeight - pblockindexFBBHLast->nHeight)) + pblockindex = pblockindexFBBHLast; + while (pblockindex->nHeight > nHeight) + pblockindex = pblockindex->pprev; + while (pblockindex->nHeight < nHeight) + pblockindex = pblockindex->pnext; + pblockindexFBBHLast = pblockindex; + return pblockindex; +} + bool CBlock::ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions) { if (!fReadTransactions) @@ -660,7 +992,7 @@ bool CBlock::ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions) return true; } -uint256 GetOrphanRoot(const CBlock* pblock) +uint256 static GetOrphanRoot(const CBlock* pblock) { // Work back to the first block in the orphan chain while (mapOrphanBlocks.count(pblock->hashPrevBlock)) @@ -668,106 +1000,219 @@ uint256 GetOrphanRoot(const CBlock* pblock) return pblock->GetHash(); } -int64 static GetProofOfWorkReward(unsigned int nBits) +// ppcoin: find block wanted by given orphan block +uint256 WantedByOrphan(const CBlock* pblockOrphan) { - CBigNum bnSubsidyLimit = 9999 * COIN; // subsidy amount for difficulty 1 + // Work back to the first block in the orphan chain + while (mapOrphanBlocks.count(pblockOrphan->hashPrevBlock)) + pblockOrphan = mapOrphanBlocks[pblockOrphan->hashPrevBlock]; + return pblockOrphan->hashPrevBlock; +} + +// select stake target limit according to hard-coded conditions +CBigNum inline GetProofOfStakeLimit(int nHeight, unsigned int nTime) +{ + if(fTestNet) // separate proof of stake target limit for testnet + return bnProofOfStakeLimit; + if(nTime > TARGETS_SWITCH_TIME) // 27 bits since 20 July 2013 + return bnProofOfStakeLimit; + if(nHeight + 1 > 15000) // 24 bits since block 15000 + return bnProofOfStakeLegacyLimit; + if(nHeight + 1 > 14060) // 31 bits since block 14060 until 15000 + return bnProofOfStakeHardLimit; + + return bnProofOfWorkLimit; // return bnProofOfWorkLimit of none matched +} + +// miner's coin base reward based on nBits +int64_t GetProofOfWorkReward(unsigned int nBits, int64_t nFees) +{ + CBigNum bnSubsidyLimit = MAX_MINT_PROOF_OF_WORK; + CBigNum bnTarget; bnTarget.SetCompact(nBits); CBigNum bnTargetLimit = bnProofOfWorkLimit; bnTargetLimit.SetCompact(bnTargetLimit.GetCompact()); - // ppcoin: subsidy is cut in half every 16x multiply of difficulty + // NovaCoin: subsidy is cut in half every 64x multiply of PoW difficulty // A reasonably continuous curve is used to avoid shock to market - // (nSubsidyLimit / nSubsidy) ** 4 == bnProofOfWorkLimit / bnTarget + // (nSubsidyLimit / nSubsidy) ** 6 == bnProofOfWorkLimit / bnTarget + // + // Human readable form: + // + // nSubsidy = 100 / (diff ^ 1/6) + // + // Please note that we're using bisection to find an approximate solutuion CBigNum bnLowerBound = CENT; CBigNum bnUpperBound = bnSubsidyLimit; while (bnLowerBound + CENT <= bnUpperBound) { CBigNum bnMidValue = (bnLowerBound + bnUpperBound) / 2; - if (fDebug && GetBoolArg("-printcreation")) - 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) + if (bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnTargetLimit > bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnTarget) bnUpperBound = bnMidValue; else bnLowerBound = bnMidValue; } - int64 nSubsidy = bnUpperBound.getuint64(); + int64_t nSubsidy = bnUpperBound.getuint64(); + nSubsidy = (nSubsidy / CENT) * CENT; if (fDebug && GetBoolArg("-printcreation")) - printf("GetProofOfWorkReward() : create=%s nBits=0x%08x nSubsidy=%"PRI64d"\n", FormatMoney(nSubsidy).c_str(), nBits, nSubsidy); + printf("GetProofOfWorkReward() : create=%s nBits=0x%08x nSubsidy=%" PRId64 "\n", FormatMoney(nSubsidy).c_str(), nBits, nSubsidy); - return nSubsidy; + return min(nSubsidy, MAX_MINT_PROOF_OF_WORK) + nFees; } -// ppcoin: miner's coin stake is rewarded based on coin age spent (coin-days) -int64 GetProofOfStakeReward(int64 nCoinAge) +// miner's coin stake reward based on nBits and coin age spent (coin-days) +int64_t GetProofOfStakeReward(int64_t nCoinAge, unsigned int nBits, int64_t nTime, bool bCoinYearOnly) { - static int64 nRewardCoinYear = CENT; // creation amount per coin-year - int64 nSubsidy = nCoinAge * 33 / (365 * 33 + 8) * nRewardCoinYear; + int64_t nRewardCoinYear, nSubsidy, nSubsidyLimit = 10 * COIN; + + // Stage 2 of emission process is mostly PoS-based. + + CBigNum bnRewardCoinYearLimit = MAX_MINT_PROOF_OF_STAKE; // Base stake mint rate, 100% year interest + CBigNum bnTarget; + bnTarget.SetCompact(nBits); + CBigNum bnTargetLimit = GetProofOfStakeLimit(0, nTime); + bnTargetLimit.SetCompact(bnTargetLimit.GetCompact()); + + // A reasonably continuous curve is used to avoid shock to market + + CBigNum bnLowerBound = 1 * CENT, // Lower interest bound is 1% per year + bnUpperBound = bnRewardCoinYearLimit, // Upper interest bound is 100% per year + bnMidPart, bnRewardPart; + + while (bnLowerBound + CENT <= bnUpperBound) + { + CBigNum bnMidValue = (bnLowerBound + bnUpperBound) / 2; + + // + // Reward for coin-year is cut in half every 8x multiply of PoS difficulty + // + // (nRewardCoinYearLimit / nRewardCoinYear) ** 3 == bnProofOfStakeLimit / bnTarget + // + // Human readable form: nRewardCoinYear = 1 / (posdiff ^ 1/3) + // + + bnMidPart = bnMidValue * bnMidValue * bnMidValue; + bnRewardPart = bnRewardCoinYearLimit * bnRewardCoinYearLimit * bnRewardCoinYearLimit; + + if (bnMidPart * bnTargetLimit > bnRewardPart * bnTarget) + bnUpperBound = bnMidValue; + else + bnLowerBound = bnMidValue; + } + + nRewardCoinYear = bnUpperBound.getuint64(); + nRewardCoinYear = min((nRewardCoinYear / CENT) * CENT, MAX_MINT_PROOF_OF_STAKE); + + if(bCoinYearOnly) + return nRewardCoinYear; + + nSubsidy = nCoinAge * nRewardCoinYear * 33 / (365 * 33 + 8); + + // Set reasonable reward limit for large inputs + // + // This will stimulate large holders to use smaller inputs, that's good for the network protection + + if (fDebug && GetBoolArg("-printcreation") && nSubsidyLimit < nSubsidy) + printf("GetProofOfStakeReward(): %s is greater than %s, coinstake reward will be truncated\n", FormatMoney(nSubsidy).c_str(), FormatMoney(nSubsidyLimit).c_str()); + + nSubsidy = min(nSubsidy, nSubsidyLimit); + if (fDebug && GetBoolArg("-printcreation")) - printf("GetProofOfStakeReward(): create=%s nCoinAge=%"PRI64d"\n", FormatMoney(nSubsidy).c_str(), nCoinAge); + printf("GetProofOfStakeReward(): create=%s nCoinAge=%" PRId64 " nBits=%d\n", FormatMoney(nSubsidy).c_str(), nCoinAge, nBits); + return nSubsidy; } -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 +static const int64_t nTargetTimespan = 7 * nOneDay; // one week + +// get proof of work blocks max spacing according to hard-coded conditions +int64_t inline GetTargetSpacingWorkMax(int nHeight, unsigned int nTime) +{ + if(nTime > TARGETS_SWITCH_TIME) + return 3 * nStakeTargetSpacing; // 30 minutes on mainNet since 20 Jul 2013 00:00:00 + + if(fTestNet) + return 3 * nStakeTargetSpacing; // 15 minutes on testNet + + return 12 * nStakeTargetSpacing; // 2 hours otherwise +} // -// minimum amount of work that could possibly be required nTime after -// minimum work required was nBase +// maximum nBits value could possible be required nTime after // -unsigned int ComputeMinWork(unsigned int nBase, int64 nTime) +unsigned int ComputeMaxBits(CBigNum bnTargetLimit, unsigned int nBase, int64_t nTime) { CBigNum bnResult; bnResult.SetCompact(nBase); - while (nTime > 0 && bnResult < bnProofOfWorkLimit) + bnResult *= 2; + while (nTime > 0 && bnResult < bnTargetLimit) { - // 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 -= nOneDay; } - if (bnResult > bnProofOfWorkLimit) - bnResult = bnProofOfWorkLimit; + if (bnResult > bnTargetLimit) + bnResult = bnTargetLimit; return bnResult.GetCompact(); } +// +// minimum amount of work that could possibly be required nTime after +// minimum proof-of-work required was nBase +// +unsigned int ComputeMinWork(unsigned int nBase, int64_t nTime) +{ + return ComputeMaxBits(bnProofOfWorkLimit, nBase, nTime); +} + +// +// 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(GetProofOfStakeLimit(0, nBlockTime), nBase, nTime); +} + + // ppcoin: find last block index up to pindex const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake) { - while (pindex && (pindex->IsProofOfStake() != fProofOfStake)) + while (pindex && pindex->pprev && (pindex->IsProofOfStake() != fProofOfStake)) pindex = pindex->pprev; return pindex; } -unsigned int static GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake) +unsigned int GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake) { - // Genesis block and first block - if (pindexLast == NULL || pindexLast->pprev == NULL) - return bnProofOfWorkLimit.GetCompact(); + if (pindexLast == NULL) + return bnProofOfWorkLimit.GetCompact(); // genesis block + + CBigNum bnTargetLimit = !fProofOfStake ? bnProofOfWorkLimit : GetProofOfStakeLimit(pindexLast->nHeight, pindexLast->nTime); const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake); - if (pindexPrev == NULL) - return bnProofOfWorkLimit.GetCompact(); + if (pindexPrev->pprev == NULL) + return bnTargetLimit.GetCompact(); // first block const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake); - if (pindexPrevPrev == NULL) - return bnProofOfWorkLimit.GetCompact(); - int64 nActualSpacing = pindexPrev->GetBlockTime() - pindexPrevPrev->GetBlockTime(); + if (pindexPrevPrev->pprev == NULL) + return bnTargetLimit.GetCompact(); // second block + + int64_t nActualSpacing = pindexPrev->GetBlockTime() - pindexPrevPrev->GetBlockTime(); // ppcoin: target change every block // 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; + int64_t nTargetSpacing = fProofOfStake? nStakeTargetSpacing : min(GetTargetSpacingWorkMax(pindexLast->nHeight, pindexLast->nTime), (int64_t) nStakeTargetSpacing * (1 + pindexLast->nHeight - pindexPrev->nHeight)); + int64_t nInterval = nTargetTimespan / nTargetSpacing; bnNew *= ((nInterval - 1) * nTargetSpacing + nActualSpacing + nActualSpacing); bnNew /= ((nInterval + 1) * nTargetSpacing); - if (bnNew > bnProofOfWorkLimit) - bnNew = bnProofOfWorkLimit; + if (bnNew > bnTargetLimit) + bnNew = bnTargetLimit; return bnNew.GetCompact(); } @@ -796,17 +1241,18 @@ int GetNumBlocksOfPeers() bool IsInitialBlockDownload() { - if (pindexBest == NULL || nBestHeight < (Checkpoints::GetTotalBlocksEstimate()-nInitialBlockThreshold)) + if (pindexBest == NULL || nBestHeight < Checkpoints::GetTotalBlocksEstimate()) return true; - static int64 nLastUpdate; + static int64_t nLastUpdate; static CBlockIndex* pindexLastBest; + int64_t nCurrentTime = GetTime(); if (pindexBest != pindexLastBest) { pindexLastBest = pindexBest; - nLastUpdate = GetTime(); + nLastUpdate = nCurrentTime; } - return (GetTime() - nLastUpdate < 10 && - pindexBest->GetBlockTime() < GetTime() - 24 * 60 * 60); + return (nCurrentTime - nLastUpdate < 10 && + pindexBest->GetBlockTime() < nCurrentTime - nOneDay); } void static InvalidChainFound(CBlockIndex* pindexNew) @@ -814,13 +1260,28 @@ void static InvalidChainFound(CBlockIndex* pindexNew) if (pindexNew->nChainTrust > nBestInvalidTrust) { nBestInvalidTrust = pindexNew->nChainTrust; - CTxDB().WriteBestInvalidTrust(nBestInvalidTrust); - MainFrameRepaint(); + CTxDB().WriteBestInvalidTrust(CBigNum(nBestInvalidTrust)); + uiInterface.NotifyBlocksChanged(); } - printf("InvalidChainFound: invalid block=%s height=%d trust=%s\n", pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight, CBigNum(pindexNew->nChainTrust).ToString().c_str()); - printf("InvalidChainFound: current best=%s height=%d trust=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str()); - if (pindexBest && nBestInvalidTrust > nBestChainTrust + pindexBest->GetBlockTrust() * 6) - printf("InvalidChainFound: WARNING: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade.\n"); + + uint256 nBestInvalidBlockTrust = pindexNew->nChainTrust - pindexNew->pprev->nChainTrust; + uint256 nBestBlockTrust = pindexBest->nHeight != 0 ? (pindexBest->nChainTrust - pindexBest->pprev->nChainTrust) : pindexBest->nChainTrust; + + printf("InvalidChainFound: invalid block=%s height=%d trust=%s blocktrust=%" PRId64 " date=%s\n", + pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight, + CBigNum(pindexNew->nChainTrust).ToString().c_str(), nBestInvalidBlockTrust.Get64(), + DateTimeStrFormat("%x %H:%M:%S", pindexNew->GetBlockTime()).c_str()); + printf("InvalidChainFound: current best=%s height=%d trust=%s blocktrust=%" PRId64 " date=%s\n", + hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, + CBigNum(pindexBest->nChainTrust).ToString().c_str(), + nBestBlockTrust.Get64(), + DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str()); +} + + +void CBlock::UpdateTime(const CBlockIndex* pindexPrev) +{ + nTime = max(GetBlockTime(), GetAdjustedTime()); } @@ -860,84 +1321,195 @@ bool CTransaction::DisconnectInputs(CTxDB& txdb) } // Remove transaction from index - if (!txdb.EraseTxIndex(*this)) - return error("DisconnectInputs() : EraseTxPos failed"); + // This can fail if a duplicate of this transaction was in a chain that got + // reorganized away. This is only possible if this transaction was completely + // spent, so erasing it would be a no-op anyway. + txdb.EraseTxIndex(*this); return true; } -bool CTransaction::ConnectInputs(CTxDB& txdb, map& mapTestPool, CDiskTxPos posThisTx, - CBlockIndex* pindexBlock, int64& nFees, bool fBlock, bool fMiner, int64 nMinFee) +bool CTransaction::FetchInputs(CTxDB& txdb, const map& mapTestPool, + bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid) +{ + // FetchInputs can return false either because we just haven't seen some inputs + // (in which case the transaction should be stored as an orphan) + // or because the transaction is malformed (in which case the transaction should + // be dropped). If tx is definitely invalid, fInvalid will be set to true. + fInvalid = false; + + if (IsCoinBase()) + return true; // Coinbase transactions have no inputs to fetch. + + for (unsigned int i = 0; i < vin.size(); i++) + { + COutPoint prevout = vin[i].prevout; + if (inputsRet.count(prevout.hash)) + continue; // Got it already + + // Read txindex + CTxIndex& txindex = inputsRet[prevout.hash].first; + bool fFound = true; + if ((fBlock || fMiner) && mapTestPool.count(prevout.hash)) + { + // Get txindex from current proposed changes + txindex = mapTestPool.find(prevout.hash)->second; + } + else + { + // Read txindex from txdb + fFound = txdb.ReadTxIndex(prevout.hash, txindex); + } + if (!fFound && (fBlock || fMiner)) + return fMiner ? false : error("FetchInputs() : %s prev tx %s index entry not found", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); + + // Read txPrev + CTransaction& txPrev = inputsRet[prevout.hash].second; + if (!fFound || txindex.pos == CDiskTxPos(1,1,1)) + { + // Get prev tx from single transactions in memory + { + LOCK(mempool.cs); + if (!mempool.exists(prevout.hash)) + return error("FetchInputs() : %s mempool Tx prev not found %s", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); + txPrev = mempool.lookup(prevout.hash); + } + if (!fFound) + txindex.vSpent.resize(txPrev.vout.size()); + } + else + { + // Get prev tx from disk + if (!txPrev.ReadFromDisk(txindex.pos)) + return error("FetchInputs() : %s ReadFromDisk prev tx %s failed", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); + } + } + + // Make sure all prevout.n indexes are valid: + for (unsigned int i = 0; i < vin.size(); i++) + { + const COutPoint prevout = vin[i].prevout; + assert(inputsRet.count(prevout.hash) != 0); + const CTxIndex& txindex = inputsRet[prevout.hash].first; + const CTransaction& txPrev = inputsRet[prevout.hash].second; + if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size()) + { + // Revisit this if/when transaction replacement is implemented and allows + // adding inputs: + fInvalid = true; + return DoS(100, error("FetchInputs() : %s prevout.n out of range %d %" PRIszu " %" PRIszu " 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())); + } + } + + return true; +} + +const CTxOut& CTransaction::GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const +{ + MapPrevTx::const_iterator mi = inputs.find(input.prevout.hash); + if (mi == inputs.end()) + throw std::runtime_error("CTransaction::GetOutputFor() : prevout.hash not found"); + + const CTransaction& txPrev = (mi->second).second; + if (input.prevout.n >= txPrev.vout.size()) + throw std::runtime_error("CTransaction::GetOutputFor() : prevout.n out of range"); + + return txPrev.vout[input.prevout.n]; +} + +int64_t CTransaction::GetValueIn(const MapPrevTx& inputs) const +{ + if (IsCoinBase()) + return 0; + + int64_t nResult = 0; + for (unsigned int i = 0; i < vin.size(); i++) + { + nResult += GetOutputFor(vin[i], inputs).nValue; + } + return nResult; + +} + +unsigned int CTransaction::GetP2SHSigOpCount(const MapPrevTx& inputs) const +{ + if (IsCoinBase()) + return 0; + + unsigned int nSigOps = 0; + for (unsigned int i = 0; i < vin.size(); i++) + { + const CTxOut& prevout = GetOutputFor(vin[i], inputs); + if (prevout.scriptPubKey.IsPayToScriptHash()) + nSigOps += prevout.scriptPubKey.GetSigOpCount(vin[i].scriptSig); + } + return nSigOps; +} + +bool CScriptCheck::operator()() const { + const CScript &scriptSig = ptxTo->vin[nIn].scriptSig; + if (!VerifyScript(scriptSig, scriptPubKey, *ptxTo, nIn, nFlags, nHashType)) + return error("CScriptCheck() : %s VerifySignature failed", ptxTo->GetHash().ToString().substr(0,10).c_str()); + return true; +} + +bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, unsigned int flags, int nHashType) +{ + return CScriptCheck(txFrom, txTo, nIn, flags, nHashType)(); +} + +bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs, map& mapTestPool, const CDiskTxPos& posThisTx, + const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, bool fScriptChecks, unsigned int flags, std::vector *pvChecks) { // Take over previous transactions' spent pointers // fBlock is true when this is called from AcceptBlock when a new best-block is added to the blockchain // fMiner is true when called from the internal bitcoin miner // ... both are false when called from CTransaction::AcceptToMemoryPool + if (!IsCoinBase()) { - int64 nValueIn = 0; - for (int i = 0; i < vin.size(); i++) + int64_t nValueIn = 0; + int64_t nFees = 0; + for (unsigned int i = 0; i < vin.size(); i++) { COutPoint prevout = vin[i].prevout; - - // Read txindex - CTxIndex txindex; - bool fFound = true; - if ((fBlock || fMiner) && mapTestPool.count(prevout.hash)) - { - // Get txindex from current proposed changes - txindex = mapTestPool[prevout.hash]; - } - else - { - // Read txindex from txdb - fFound = txdb.ReadTxIndex(prevout.hash, txindex); - } - if (!fFound && (fBlock || fMiner)) - return fMiner ? false : error("ConnectInputs() : %s prev tx %s index entry not found", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); - - // Read txPrev - CTransaction txPrev; - if (!fFound || txindex.pos == CDiskTxPos(1,1,1)) - { - // Get prev tx from single transactions in memory - CRITICAL_BLOCK(cs_mapTransactions) - { - if (!mapTransactions.count(prevout.hash)) - return error("ConnectInputs() : %s mapTransactions prev not found %s", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); - txPrev = mapTransactions[prevout.hash]; - } - if (!fFound) - txindex.vSpent.resize(txPrev.vout.size()); - } - else - { - // Get prev tx from disk - if (!txPrev.ReadFromDisk(txindex.pos)) - return error("ConnectInputs() : %s ReadFromDisk prev tx %s failed", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); - } + assert(inputs.count(prevout.hash) > 0); + CTxIndex& txindex = inputs[prevout.hash].first; + CTransaction& txPrev = inputs[prevout.hash].second; 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())); + return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %" PRIszu " %" PRIszu " 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/coinstake, check that it's matured + // If prev is coinbase or 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) + for (const CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < nCoinbaseMaturity; pindex = pindex->pprev) if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile) - return error("ConnectInputs() : tried to spend coinbase/coinstake at depth %d", pindexBlock->nHeight - pindex->nHeight); + return error("ConnectInputs() : tried to spend %s at depth %d", txPrev.IsCoinBase() ? "coinbase" : "coinstake", pindexBlock->nHeight - pindex->nHeight); // ppcoin: check transaction timestamp if (txPrev.nTime > nTime) return DoS(100, error("ConnectInputs() : transaction timestamp earlier than input transaction")); - // Skip ECDSA signature verification when connecting blocks (fBlock=true) during initial download - // (before the last blockchain checkpoint). This is safe because block merkle hashes are - // still computed and checked, and any change will be caught at the next checkpoint. - if (!(fBlock && IsInitialBlockDownload())) - // Verify signature - if (!VerifySignature(txPrev, *this, i)) - return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str())); + // Check for negative or overflow input values + nValueIn += txPrev.vout[prevout.n].nValue; + if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn)) + return DoS(100, error("ConnectInputs() : txin values out of range")); + + } + + if (pvChecks) + pvChecks->reserve(vin.size()); + + // The first loop above does all the inexpensive checks. + // Only if ALL inputs pass do we perform expensive ECDSA signature checks. + // Helps prevent CPU exhaustion attacks. + for (unsigned int i = 0; i < vin.size(); i++) + { + COutPoint prevout = vin[i].prevout; + assert(inputs.count(prevout.hash) > 0); + CTxIndex& txindex = inputs[prevout.hash].first; + CTransaction& txPrev = inputs[prevout.hash].second; // Check for conflicts (double-spend) // This doesn't trigger the DoS code on purpose; if it did, it would make it easier @@ -945,10 +1517,30 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map& mapTestPoo if (!txindex.vSpent[prevout.n].IsNull()) return fMiner ? false : error("ConnectInputs() : %s prev tx already used at %s", GetHash().ToString().substr(0,10).c_str(), txindex.vSpent[prevout.n].ToString().c_str()); - // Check for negative or overflow input values - nValueIn += txPrev.vout[prevout.n].nValue; - if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn)) - return DoS(100, error("ConnectInputs() : txin values out of range")); + // Skip ECDSA signature verification when connecting blocks (fBlock=true) + // before the last blockchain checkpoint. This is safe because block merkle hashes are + // still computed and checked, and any change will be caught at the next checkpoint. + if (fScriptChecks) + { + // Verify signature + CScriptCheck check(txPrev, *this, i, flags, 0); + if (pvChecks) + { + pvChecks->push_back(CScriptCheck()); + check.swap(pvChecks->back()); + } + else if (!check()) + { + if (flags & STRICT_FLAGS) + { + // Don't trigger DoS code in case of STRICT_FLAGS caused failure. + CScriptCheck check(txPrev, *this, i, flags & ~STRICT_FLAGS, 0); + if (check()) + return error("ConnectInputs() : %s strict VerifySignature failed", GetHash().ToString().substr(0,10).c_str()); + } + return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str())); + } + } // Mark outpoints as spent txindex.vSpent[prevout.n] = posThisTx; @@ -962,13 +1554,21 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map& mapTestPoo 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())); + if (nTime > Checkpoints::GetLastCheckpointTime()) + { + unsigned int nTxSize = GetSerializeSize(SER_NETWORK, PROTOCOL_VERSION); + + // coin stake tx earns reward instead of paying fee + uint64_t nCoinAge; + if (!GetCoinAge(txdb, nCoinAge)) + return error("ConnectInputs() : %s unable to get coin age for coinstake", GetHash().ToString().substr(0,10).c_str()); + + int64_t nReward = GetValueOut() - nValueIn; + int64_t nCalculatedReward = GetProofOfStakeReward(nCoinAge, pindexBlock->nBits, nTime) - GetMinFee(1, false, GMF_BLOCK, nTxSize) + CENT; + + if (nReward > nCalculatedReward) + return DoS(100, error("ConnectInputs() : coinstake pays too much(actual=%" PRId64 " vs calculated=%" PRId64 ")", nReward, nCalculatedReward)); + } } else { @@ -976,29 +1576,16 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map& mapTestPoo return DoS(100, error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str())); // Tally transaction fees - int64 nTxFee = nValueIn - GetValueOut(); + int64_t 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) - { - // Add transaction to changes - mapTestPool[GetHash()] = CTxIndex(posThisTx, vout.size()); - } - else if (fMiner) - { - // Add transaction to test pool - mapTestPool[GetHash()] = CTxIndex(CDiskTxPos(1,1,1), vout.size()); - } - return true; } @@ -1009,25 +1596,26 @@ bool CTransaction::ClientConnectInputs() return false; // Take over previous transactions' spent pointers - CRITICAL_BLOCK(cs_mapTransactions) { - int64 nValueIn = 0; - for (int i = 0; i < vin.size(); i++) + LOCK(mempool.cs); + int64_t nValueIn = 0; + for (unsigned int i = 0; i < vin.size(); i++) { // Get prev tx from single transactions in memory COutPoint prevout = vin[i].prevout; - if (!mapTransactions.count(prevout.hash)) + if (!mempool.exists(prevout.hash)) return false; - CTransaction& txPrev = mapTransactions[prevout.hash]; + CTransaction& txPrev = mempool.lookup(prevout.hash); if (prevout.n >= txPrev.vout.size()) return false; // Verify signature - if (!VerifySignature(txPrev, *this, i)) - return error("ConnectInputs() : VerifySignature failed"); + if (!VerifySignature(txPrev, *this, i, SCRIPT_VERIFY_NOCACHE | SCRIPT_VERIFY_P2SH, 0)) + return error("ClientConnectInputs() : VerifySignature failed"); - ///// this is redundant with the mapNextTx stuff, not sure which I want to get rid of + ///// this is redundant with the mempool.mapNextTx stuff, + ///// not sure which I want to get rid of ///// this has to go away now that posNext is gone // // Check for conflicts // if (!txPrev.vout[prevout.n].posNext.IsNull()) @@ -1075,25 +1663,145 @@ bool CBlock::DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex) return true; } -bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) +static CCheckQueue scriptcheckqueue(128); + +void ThreadScriptCheck(void*) { + vnThreadsRunning[THREAD_SCRIPTCHECK]++; + RenameThread("novacoin-scriptch"); + scriptcheckqueue.Thread(); + vnThreadsRunning[THREAD_SCRIPTCHECK]--; +} + +void ThreadScriptCheckQuit() { + scriptcheckqueue.Quit(); +} + +bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) { - // Check it again in case a previous version let a bad block in - if (!CheckBlock()) + // Check it again in case a previous version let a bad block in, but skip BlockSig checking + if (!CheckBlock(!fJustCheck, !fJustCheck, false)) return false; + // Do not allow blocks that contain transactions which 'overwrite' older transactions, + // unless those are already completely spent. + // If such overwrites are allowed, coinbases and transactions depending upon those + // can be duplicated to remove the ability to spend the first instance -- even after + // being sent to another address. + // See BIP30 and http://r6.ca/blog/20120206T005236Z.html for more information. + // This logic is not necessary for memory pool transactions, as AcceptToMemoryPool + // already refuses previously-known transaction ids entirely. + // This rule was originally applied all blocks whose timestamp was after March 15, 2012, 0:00 UTC. + // Now that the whole chain is irreversibly beyond that time it is applied to all blocks except the + // two in the chain that violate it. This prevents exploiting the issue against nodes in their + // initial block download. + bool fEnforceBIP30 = true; // Always active in NovaCoin + bool fScriptChecks = pindex->nHeight >= Checkpoints::GetTotalBlocksEstimate(); + //// issue here: it doesn't know the version - unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size()); + unsigned int nTxPos; + if (fJustCheck) + // FetchInputs treats CDiskTxPos(1,1,1) as a special "refer to memorypool" indicator + // Since we're just checking the block and not actually connecting it, it might not (and probably shouldn't) be on the disk to get the transaction from + nTxPos = 1; + else + nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size()); map mapQueuedChanges; - int64 nFees = 0; + CCheckQueueControl control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : NULL); + + int64_t nFees = 0; + int64_t nValueIn = 0; + int64_t nValueOut = 0; + unsigned int nSigOps = 0; BOOST_FOREACH(CTransaction& tx, vtx) { + uint256 hashTx = tx.GetHash(); + + if (fEnforceBIP30) { + CTxIndex txindexOld; + if (txdb.ReadTxIndex(hashTx, txindexOld)) { + BOOST_FOREACH(CDiskTxPos &pos, txindexOld.vSpent) + if (pos.IsNull()) + return false; + } + } + + nSigOps += tx.GetLegacySigOpCount(); + if (nSigOps > MAX_BLOCK_SIGOPS) + return DoS(100, error("ConnectBlock() : too many sigops")); + CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos); - nTxPos += ::GetSerializeSize(tx, SER_DISK); + if (!fJustCheck) + nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); - if (!tx.ConnectInputs(txdb, mapQueuedChanges, posThisTx, pindex, nFees, true, false, tx.GetMinFee())) - return false; + MapPrevTx mapInputs; + if (tx.IsCoinBase()) + nValueOut += tx.GetValueOut(); + else + { + bool fInvalid; + if (!tx.FetchInputs(txdb, mapQueuedChanges, true, false, mapInputs, fInvalid)) + return false; + + // Add in sigops done by pay-to-script-hash inputs; + // this is to prevent a "rogue miner" from creating + // an incredibly-expensive-to-validate block. + nSigOps += tx.GetP2SHSigOpCount(mapInputs); + if (nSigOps > MAX_BLOCK_SIGOPS) + return DoS(100, error("ConnectBlock() : too many sigops")); + + int64_t nTxValueIn = tx.GetValueIn(mapInputs); + int64_t nTxValueOut = tx.GetValueOut(); + nValueIn += nTxValueIn; + nValueOut += nTxValueOut; + if (!tx.IsCoinStake()) + nFees += nTxValueIn - nTxValueOut; + + unsigned int nFlags = SCRIPT_VERIFY_NOCACHE | SCRIPT_VERIFY_P2SH; + + if (tx.nTime >= CHECKLOCKTIMEVERIFY_SWITCH_TIME) { + nFlags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY; + // OP_CHECKSEQUENCEVERIFY is senseless without BIP68, so we're going disable it for now. + // nFlags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY; + } + + std::vector vChecks; + if (!tx.ConnectInputs(txdb, mapInputs, mapQueuedChanges, posThisTx, pindex, true, false, fScriptChecks, nFlags, nScriptCheckThreads ? &vChecks : NULL)) + return false; + control.Add(vChecks); + } + + mapQueuedChanges[hashTx] = CTxIndex(posThisTx, tx.vout.size()); + } + + if (!control.Wait()) + return DoS(100, false); + + if (IsProofOfWork()) + { + int64_t nBlockReward = GetProofOfWorkReward(nBits, nFees); + + // Check coinbase reward + if (vtx[0].GetValueOut() > nBlockReward) + return error("CheckBlock() : coinbase reward exceeded (actual=%" PRId64 " vs calculated=%" PRId64 ")", + vtx[0].GetValueOut(), + nBlockReward); } + + // track money supply and mint amount info + pindex->nMint = nValueOut - nValueIn + nFees; + pindex->nMoneySupply = (pindex->pprev? pindex->pprev->nMoneySupply : 0) + nValueOut - nValueIn; + if (!txdb.WriteBlockIndex(CDiskBlockIndex(pindex))) + return error("Connect() : WriteBlockIndex for pindex failed"); + + // fees are not collected by proof-of-stake miners + // fees are destroyed to compensate the entire network + if (fDebug && IsProofOfStake() && GetBoolArg("-printcreation")) + printf("ConnectBlock() : destroy=%s nFees=%" PRId64 "\n", FormatMoney(nFees).c_str(), nFees); + + if (fJustCheck) + return true; + // Write queued txindex changes for (map::iterator mi = mapQueuedChanges.begin(); mi != mapQueuedChanges.end(); ++mi) { @@ -1101,13 +1809,6 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) return error("ConnectBlock() : UpdateTxIndex failed"); } - // 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. if (pindex->pprev) @@ -1122,10 +1823,11 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) BOOST_FOREACH(CTransaction& tx, vtx) SyncWithWallets(tx, this, true); + return true; } -bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) +bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) { printf("REORGANIZE\n"); @@ -1135,11 +1837,11 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) while (pfork != plonger) { while (plonger->nHeight > pfork->nHeight) - if (!(plonger = plonger->pprev)) + if ((plonger = plonger->pprev) == NULL) return error("Reorganize() : plonger->pprev is null"); if (pfork == plonger) break; - if (!(pfork = pfork->pprev)) + if ((pfork = pfork->pprev) == NULL) return error("Reorganize() : pfork->pprev is null"); } @@ -1154,6 +1856,9 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) vConnect.push_back(pindex); reverse(vConnect.begin(), vConnect.end()); + printf("REORGANIZE: Disconnect %" PRIszu " blocks; %s..%s\n", vDisconnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexBest->GetBlockHash().ToString().substr(0,20).c_str()); + printf("REORGANIZE: Connect %" PRIszu " blocks; %s..%s\n", vConnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->GetBlockHash().ToString().substr(0,20).c_str()); + // Disconnect shorter branch vector vResurrect; BOOST_FOREACH(CBlockIndex* pindex, vDisconnect) @@ -1162,7 +1867,7 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) if (!block.ReadFromDisk(pindex)) return error("Reorganize() : ReadFromDisk for disconnect failed"); if (!block.DisconnectBlock(txdb, pindex)) - return error("Reorganize() : DisconnectBlock failed"); + return error("Reorganize() : DisconnectBlock %s failed", pindex->GetBlockHash().ToString().substr(0,20).c_str()); // Queue memory transactions to resurrect BOOST_FOREACH(const CTransaction& tx, block.vtx) @@ -1172,7 +1877,7 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) // Connect longer branch vector vDelete; - for (int i = 0; i < vConnect.size(); i++) + for (unsigned int i = 0; i < vConnect.size(); i++) { CBlockIndex* pindex = vConnect[i]; CBlock block; @@ -1181,8 +1886,7 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) if (!block.ConnectBlock(txdb, pindex)) { // Invalid block - txdb.TxnAbort(); - return error("Reorganize() : ConnectBlock failed"); + return error("Reorganize() : ConnectBlock %s failed", pindex->GetBlockHash().ToString().substr(0,20).c_str()); } // Queue memory transactions to delete @@ -1191,8 +1895,6 @@ bool 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()) @@ -1214,57 +1916,107 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) // Delete redundant memory transactions that are in the connected branch BOOST_FOREACH(CTransaction& tx, vDelete) - tx.RemoveFromMemoryPool(); + mempool.remove(tx); + + printf("REORGANIZE: done\n"); return true; } +// Called from inside SetBestChain: attaches a block to the new best chain being built +bool CBlock::SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew) +{ + uint256 hash = GetHash(); + + // Adding to current best branch + if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash)) + { + txdb.TxnAbort(); + InvalidChainFound(pindexNew); + return false; + } + if (!txdb.TxnCommit()) + return error("SetBestChain() : TxnCommit failed"); + + // Add to current best branch + pindexNew->pprev->pnext = pindexNew; + + // Delete redundant memory transactions + BOOST_FOREACH(CTransaction& tx, vtx) + mempool.remove(tx); + + return true; +} + bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) { uint256 hash = GetHash(); - txdb.TxnBegin(); - if (pindexGenesisBlock == NULL && hash == hashGenesisBlock) + if (!txdb.TxnBegin()) + return error("SetBestChain() : TxnBegin failed"); + + if (pindexGenesisBlock == NULL && hash == (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet)) { txdb.WriteHashBestChain(hash); - txdb.WriteAutoCheckpoint(Checkpoints::GetNextAutoCheckpoint(pindexNew->nCheckpoint)); if (!txdb.TxnCommit()) return error("SetBestChain() : TxnCommit failed"); pindexGenesisBlock = pindexNew; } else if (hashPrevBlock == hashBestChain) { - // Adding to current best branch - if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash) || !txdb.WriteAutoCheckpoint(Checkpoints::GetNextAutoCheckpoint(pindexNew->nCheckpoint))) + if (!SetBestChainInner(txdb, pindexNew)) + return error("SetBestChain() : SetBestChainInner failed"); + } + else + { + // the first block in the new chain that will cause it to become the new best chain + CBlockIndex *pindexIntermediate = pindexNew; + + // list of blocks that need to be connected afterwards + std::vector vpindexSecondary; + + // Reorganize is costly in terms of db load, as it works in a single db transaction. + // Try to limit how much needs to be done inside + while (pindexIntermediate->pprev && pindexIntermediate->pprev->nChainTrust > pindexBest->nChainTrust) { - txdb.TxnAbort(); - InvalidChainFound(pindexNew); - return error("SetBestChain() : ConnectBlock failed"); + vpindexSecondary.push_back(pindexIntermediate); + pindexIntermediate = pindexIntermediate->pprev; } - if (!txdb.TxnCommit()) - return error("SetBestChain() : TxnCommit failed"); - // Add to current best branch - pindexNew->pprev->pnext = pindexNew; + if (!vpindexSecondary.empty()) + printf("Postponing %" PRIszu " reconnects\n", vpindexSecondary.size()); - // Delete redundant memory transactions - BOOST_FOREACH(CTransaction& tx, vtx) - tx.RemoveFromMemoryPool(); - } - else - { - // New best branch - if (!Reorganize(txdb, pindexNew)) + // Switch to new best branch + if (!Reorganize(txdb, pindexIntermediate)) { txdb.TxnAbort(); InvalidChainFound(pindexNew); return error("SetBestChain() : Reorganize failed"); } + + // Connect further blocks + BOOST_REVERSE_FOREACH(CBlockIndex *pindex, vpindexSecondary) + { + CBlock block; + if (!block.ReadFromDisk(pindex)) + { + printf("SetBestChain() : ReadFromDisk failed\n"); + break; + } + if (!txdb.TxnBegin()) { + printf("SetBestChain() : TxnBegin 2 failed\n"); + break; + } + // errors now are not fatal, we still did a reorganisation to a new chain in a valid way + if (!block.SetBestChainInner(txdb, pindex)) + break; + } } // Update best block in wallet (so we can detect restored wallets) - if (!IsInitialBlockDownload()) + bool fIsInitialDownload = IsInitialBlockDownload(); + if (!fIsInitialDownload) { const CBlockLocator locator(pindexNew); ::SetBestChain(locator); @@ -1273,88 +2025,57 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) // New best block hashBestChain = hash; pindexBest = pindexNew; + pblockindexFBBHLast = NULL; nBestHeight = pindexBest->nHeight; 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; -} + uint256 nBestBlockTrust = pindexBest->nHeight != 0 ? (pindexBest->nChainTrust - pindexBest->pprev->nChainTrust) : pindexBest->nChainTrust; + printf("SetBestChain: new best=%s height=%d trust=%s blocktrust=%" PRId64 " date=%s\n", + hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, + CBigNum(nBestChainTrust).ToString().c_str(), + nBestBlockTrust.Get64(), + DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str()); -// 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; + // Check the version of the last 100 blocks to see if we need to upgrade: + if (!fIsInitialDownload) + { + int nUpgraded = 0; + const CBlockIndex* pindex = pindexBest; + for (int i = 0; i < 100 && pindex != NULL; i++) + { + if (pindex->nVersion > CBlock::CURRENT_VERSION) + ++nUpgraded; + pindex = pindex->pprev; + } + if (nUpgraded > 0) + printf("SetBestChain: %d of last 100 blocks above version %d\n", nUpgraded, CBlock::CURRENT_VERSION); + if (nUpgraded > 100/2) + // strMiscWarning is read by GetWarnings(), called by Qt and the JSON-RPC code to warn the user: + strMiscWarning = _("Warning: This version is obsolete, upgrade required!"); + } - // Input 0 must match the stake hash target per coin age (nBits) - const CTxIn& txin = vin[0]; + std::string strCmd = GetArg("-blocknotify", ""); - // 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())); + if (!fIsInitialDownload && !strCmd.empty()) + { + boost::replace_all(strCmd, "%s", hashBestChain.GetHex()); + boost::thread t(runCommand, strCmd); // thread runs free + } - // 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())); + return true; } // ppcoin: total coin age spent in transaction, in the unit of coin-days. -// Only those coins last spent at least a week ago count. As those +// 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. -bool CTransaction::GetCoinAge(CTxDB& txdb, uint64& nCoinAge) const +bool CTransaction::GetCoinAge(CTxDB& txdb, uint64_t& nCoinAge) const { CBigNum bnCentSecond = 0; // coin age in the unit of cent-seconds nCoinAge = 0; @@ -1376,17 +2097,17 @@ bool CTransaction::GetCoinAge(CTxDB& txdb, uint64& nCoinAge) const 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 + if (block.GetBlockTime() + nStakeMinAge > nTime) + continue; // only count coins meeting min age requirement - int64 nValueIn = txPrev.vout[txin.prevout.n].nValue; + int64_t 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, nTime - txPrev.nTime, bnCentSecond.ToString().c_str()); + 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); + CBigNum bnCoinDay = bnCentSecond * CENT / COIN / nOneDay; if (fDebug && GetBoolArg("-printcoinage")) printf("coin age bnCoinDay=%s\n", bnCoinDay.ToString().c_str()); nCoinAge = bnCoinDay.getuint64(); @@ -1394,14 +2115,14 @@ bool CTransaction::GetCoinAge(CTxDB& txdb, uint64& nCoinAge) const } // ppcoin: total coin age spent in block, in the unit of coin-days. -bool CBlock::GetCoinAge(uint64& nCoinAge) const +bool CBlock::GetCoinAge(uint64_t& nCoinAge) const { nCoinAge = 0; CTxDB txdb("r"); BOOST_FOREACH(const CTransaction& tx, vtx) { - uint64 nTxCoinAge; + uint64_t nTxCoinAge; if (tx.GetCoinAge(txdb, nTxCoinAge)) nCoinAge += nTxCoinAge; else @@ -1411,11 +2132,10 @@ bool CBlock::GetCoinAge(uint64& nCoinAge) const 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); + printf("block coin age total nCoinDays=%" PRId64 "\n", nCoinAge); return true; } - bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) { // Check for duplicate @@ -1424,33 +2144,52 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) return error("AddToBlockIndex() : %s already exists", hash.ToString().substr(0,20).c_str()); // Construct new block index object - CBlockIndex* pindexNew = new CBlockIndex(nFile, nBlockPos, *this); + CBlockIndex* pindexNew = new(nothrow) CBlockIndex(nFile, nBlockPos, *this); 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); + pindexNew->phashBlock = &hash; map::iterator miPrev = mapBlockIndex.find(hashPrevBlock); if (miPrev != mapBlockIndex.end()) { 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; - if (!GetCoinAge(nCoinAge)) - return error("AddToBlockIndex() : invalid transaction in block"); - pindexNew->nChainTrust = (pindexNew->pprev ? pindexNew->pprev->nChainTrust : 0) + nCoinAge; + pindexNew->nChainTrust = (pindexNew->pprev ? pindexNew->pprev->nChainTrust : 0) + pindexNew->GetBlockTrust(); + + // ppcoin: compute stake entropy bit for stake modifier + if (!pindexNew->SetStakeEntropyBit(GetStakeEntropyBit(pindexNew->nHeight))) + return error("AddToBlockIndex() : SetStakeEntropyBit() failed"); + + // ppcoin: record proof-of-stake hash value + if (pindexNew->IsProofOfStake()) + { + if (!mapProofOfStake.count(hash)) + return error("AddToBlockIndex() : hashProofOfStake not found in map"); + pindexNew->hashProofOfStake = mapProofOfStake[hash]; + } + + // ppcoin: compute stake modifier + uint64_t nStakeModifier = 0; + bool fGeneratedStakeModifier = false; + if (!ComputeNextStakeModifier(pindexNew, nStakeModifier, fGeneratedStakeModifier)) + return error("AddToBlockIndex() : ComputeNextStakeModifier() failed"); + pindexNew->SetStakeModifier(nStakeModifier, fGeneratedStakeModifier); + pindexNew->nStakeModifierChecksum = GetStakeModifierChecksum(pindexNew); + if (!CheckStakeModifierCheckpoints(pindexNew->nHeight, pindexNew->nStakeModifierChecksum)) + return error("AddToBlockIndex() : Rejected by stake modifier checkpoint height=%d, modifier=0x%016" PRIx64, pindexNew->nHeight, nStakeModifier); + + // Add to mapBlockIndex + map::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first; + if (pindexNew->IsProofOfStake()) + setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime)); + pindexNew->phashBlock = &((*mi).first); + // Write to disk block index CTxDB txdb; - txdb.TxnBegin(); + if (!txdb.TxnBegin()) + return false; txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew)); if (!txdb.TxnCommit()) return false; @@ -1460,8 +2199,6 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) if (!SetBestChain(txdb, pindexNew)) return false; - txdb.Close(); - if (pindexNew == pindexBest) { // Notify UI to display prev block's coinbase if it was ours @@ -1470,76 +2207,123 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) hashPrevBestCoinBase = vtx[0].GetHash(); } - MainFrameRepaint(); + static int8_t counter = 0; + if( (++counter & 0x0F) == 0 || !IsInitialBlockDownload()) // repaint every 16 blocks if not in initial block download + uiInterface.NotifyBlocksChanged(); return true; } -bool CBlock::CheckBlock() const +bool CBlock::CheckBlock(bool fCheckPOW, bool fCheckMerkleRoot, bool fCheckSig) const { // These are checks that are independent of context // that can be verified before saving an orphan block. + set uniqueTx; // tx hashes + unsigned int nSigOps = 0; // total sigops + // Size limits - if (vtx.empty() || vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(*this, SER_NETWORK) > MAX_BLOCK_SIZE) + if (vtx.empty() || vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE) return DoS(100, error("CheckBlock() : size limits failed")); - // Check proof of work matches claimed amount - if (IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits)) - return DoS(50, error("CheckBlock() : proof of work failed")); - - // Check timestamp - if (GetBlockTime() > GetAdjustedTime() + nMaxClockDrift) - return error("CheckBlock() : block timestamp too far in the future"); + bool fProofOfStake = IsProofOfStake(); // First transaction must be coinbase, the rest must not be - if (vtx.empty() || !vtx[0].IsCoinBase()) + if (!vtx[0].IsCoinBase()) return DoS(100, error("CheckBlock() : first tx is not coinbase")); - for (int i = 1; i < vtx.size(); i++) - 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")); + if (!vtx[0].CheckTransaction()) + return DoS(vtx[0].nDoS, error("CheckBlock() : CheckTransaction failed on coinbase")); - // 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"); + uniqueTx.insert(vtx[0].GetHash()); + nSigOps += vtx[0].GetLegacySigOpCount(); - // Check coinbase timestamp - if (GetBlockTime() > (int64)vtx[0].nTime + nMaxClockDrift) - return DoS(50, error("CheckBlock() : coinbase timestamp is too early")); + if (fProofOfStake) + { + // Proof-of-STake related checkings. Note that we know here that 1st transactions is coinstake. We don't need + // check the type of 1st transaction because it's performed earlier by IsProofOfStake() - // Check coinstake timestamp - if (IsProofOfStake() && GetBlockTime() > (int64)vtx[1].nTime + nMaxClockDrift) - return DoS(50, error("CheckBlock() : coinstake timestamp is too early")); + // nNonce must be zero for proof-of-stake blocks + if (nNonce != 0) + return DoS(100, error("CheckBlock() : non-zero nonce in proof-of-stake block")); - // Check transactions - BOOST_FOREACH(const CTransaction& tx, vtx) + // Coinbase output should be empty if proof-of-stake block + if (vtx[0].vout.size() != 1 || !vtx[0].vout[0].IsEmpty()) + return DoS(100, error("CheckBlock() : coinbase output not empty for proof-of-stake block")); + + // Check coinstake timestamp + if (GetBlockTime() != (int64_t)vtx[1].nTime) + return DoS(50, error("CheckBlock() : coinstake timestamp violation nTimeBlock=%" PRId64 " nTimeTx=%u", GetBlockTime(), vtx[1].nTime)); + + // NovaCoin: check proof-of-stake block signature + if (fCheckSig && !CheckBlockSignature()) + return DoS(100, error("CheckBlock() : bad proof-of-stake block signature")); + + if (!vtx[1].CheckTransaction()) + return DoS(vtx[1].nDoS, error("CheckBlock() : CheckTransaction failed on coinstake")); + + uniqueTx.insert(vtx[1].GetHash()); + nSigOps += vtx[1].GetLegacySigOpCount(); + } + else + { + // Check proof of work matches claimed amount + if (fCheckPOW && !CheckProofOfWork(GetHash(), nBits)) + return DoS(50, error("CheckBlock() : proof of work failed")); + + // Check timestamp + if (GetBlockTime() > FutureDrift(GetAdjustedTime())) + return error("CheckBlock() : block timestamp too far in the future"); + + // Check coinbase timestamp + if (GetBlockTime() < PastDrift((int64_t)vtx[0].nTime)) + return DoS(50, error("CheckBlock() : coinbase timestamp is too late")); + } + + // Iterate all transactions starting from second for proof-of-stake block + // or first for proof-of-work block + for (unsigned int i = fProofOfStake ? 2 : 1; i < vtx.size(); i++) { + const CTransaction& tx = vtx[i]; + + // Reject coinbase transactions at non-zero index + if (tx.IsCoinBase()) + return DoS(100, error("CheckBlock() : coinbase at wrong index")); + + // Reject coinstake transactions at index != 1 + if (tx.IsCoinStake()) + return DoS(100, error("CheckBlock() : coinstake at wrong index")); + + // Check transaction timestamp + if (GetBlockTime() < (int64_t)tx.nTime) + return DoS(50, error("CheckBlock() : block timestamp earlier than transaction timestamp")); + + // Check transaction consistency if (!tx.CheckTransaction()) return DoS(tx.nDoS, error("CheckBlock() : CheckTransaction failed")); - // ppcoin: check transaction timestamp - if (GetBlockTime() < (int64)tx.nTime) - return DoS(50, error("CheckBlock() : block timestamp earlier than transaction timestamp")); + + // Add transaction hash into list of unique transaction IDs + uniqueTx.insert(tx.GetHash()); + + // Calculate sigops count + nSigOps += tx.GetLegacySigOpCount(); } - // Check that it's not full of nonstandard transactions - if (GetSigOpCount() > MAX_BLOCK_SIGOPS) + // Check for duplicate txids. This is caught by ConnectInputs(), + // but catching it earlier avoids a potential DoS attack: + if (uniqueTx.size() != vtx.size()) + return DoS(100, error("CheckBlock() : duplicate transaction")); + + // Reject block if validation would consume too much resources. + if (nSigOps > MAX_BLOCK_SIGOPS) return DoS(100, error("CheckBlock() : out-of-bounds SigOpCount")); - // Check merkleroot - if (hashMerkleRoot != BuildMerkleTree()) + // Check merkle root + if (fCheckMerkleRoot && 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; } @@ -1559,33 +2343,49 @@ bool CBlock::AcceptBlock() // 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")); + return DoS(100, error("AcceptBlock() : incorrect %s", IsProofOfWork() ? "proof-of-work" : "proof-of-stake")); + + int64_t nMedianTimePast = pindexPrev->GetMedianTimePast(); + int nMaxOffset = 12 * nOneHour; // 12 hours + if (fTestNet || pindexPrev->nTime < 1450569600) + nMaxOffset = 7 * nOneWeek; // One week (permanently on testNet or until 20 Dec, 2015 on mainNet) // Check timestamp against prev - if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || GetBlockTime() + nMaxClockDrift < pindexPrev->GetBlockTime()) + if (GetBlockTime() <= nMedianTimePast || FutureDrift(GetBlockTime()) < pindexPrev->GetBlockTime()) return error("AcceptBlock() : block's timestamp is too early"); + // Don't accept blocks with future timestamps + if (pindexPrev->nHeight > 1 && nMedianTimePast + nMaxOffset < GetBlockTime()) + return error("AcceptBlock() : block's timestamp is too far in the future"); + // Check that all transactions are finalized BOOST_FOREACH(const CTransaction& tx, vtx) if (!tx.IsFinal(nHeight, GetBlockTime())) return DoS(10, error("AcceptBlock() : contains a non-final transaction")); - // Check that the block chain matches the known block chain up to a hardened checkpoint + // Check that the block chain matches the known block chain up to a checkpoint if (!Checkpoints::CheckHardened(nHeight, hash)) - return DoS(100, error("AcceptBlock() : rejected by hardened checkpoint lockin at %d", nHeight)); + return DoS(100, error("AcceptBlock() : rejected by hardened checkpoint lock-in 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)); + bool cpSatisfies = Checkpoints::CheckSync(hash, pindexPrev); - // ppcoin: check that the block satisfies synchronized checkpoint - if (!Checkpoints::CheckSync(hash, pindexPrev)) + // Check that the block satisfies synchronized checkpoint + if (CheckpointsMode == Checkpoints::STRICT && !cpSatisfies) return error("AcceptBlock() : rejected by synchronized checkpoint"); + if (CheckpointsMode == Checkpoints::ADVISORY && !cpSatisfies) + strMiscWarning = _("WARNING: syncronized checkpoint violation detected, but skipped!"); + + // Enforce rule that the coinbase starts with serialized block height + CScript expect = CScript() << nHeight; + if (vtx[0].vin[0].scriptSig.size() < expect.size() || + !std::equal(expect.begin(), expect.end(), vtx[0].vin[0].scriptSig.begin())) + return DoS(100, error("AcceptBlock() : block height mismatch in coinbase")); + // Write block to history file - if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK))) + if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK, CLIENT_VERSION))) return error("AcceptBlock() : out of disk space"); - unsigned int nFile = -1; + unsigned int nFile = std::numeric_limits::max(); unsigned int nBlockPos = 0; if (!WriteToDisk(nFile, nBlockPos)) return error("AcceptBlock() : WriteToDisk failed"); @@ -1593,11 +2393,14 @@ bool CBlock::AcceptBlock() return error("AcceptBlock() : AddToBlockIndex failed"); // Relay inventory, but don't relay old inventory during initial block download + int nBlockEstimate = Checkpoints::GetTotalBlocksEstimate(); if (hashBestChain == hash) - CRITICAL_BLOCK(cs_vNodes) - BOOST_FOREACH(CNode* pnode, vNodes) - if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : 140700)) - pnode->PushInventory(CInv(MSG_BLOCK, hash)); + { + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate)) + pnode->PushInventory(CInv(MSG_BLOCK, hash)); + } // ppcoin: check pending sync-checkpoint Checkpoints::AcceptPendingSyncCheckpoint(); @@ -1605,6 +2408,116 @@ bool CBlock::AcceptBlock() return true; } +uint256 CBlockIndex::GetBlockTrust() const +{ + CBigNum bnTarget; + bnTarget.SetCompact(nBits); + + if (bnTarget <= 0) + return 0; + + // Return 1 for the first 12 blocks + if (pprev == NULL || pprev->nHeight < 12) + return 1; + + const CBlockIndex* currentIndex = pprev; + + if(IsProofOfStake()) + { + CBigNum bnNewTrust = (CBigNum(1)<<256) / (bnTarget+1); + + // Return 1/3 of score if parent block is not the PoW block + if (!pprev->IsProofOfWork()) + return (bnNewTrust / 3).getuint256(); + + int nPoWCount = 0; + + // Check last 12 blocks type + while (pprev->nHeight - currentIndex->nHeight < 12) + { + if (currentIndex->IsProofOfWork()) + nPoWCount++; + currentIndex = currentIndex->pprev; + } + + // Return 1/3 of score if less than 3 PoW blocks found + if (nPoWCount < 3) + return (bnNewTrust / 3).getuint256(); + + return bnNewTrust.getuint256(); + } + else + { + // Calculate work amount for block + CBigNum bnPoWTrust = CBigNum(nPoWBase) / (bnTarget+1); + + // Set nPowTrust to 1 if PoW difficulty is too low + if (bnPoWTrust < 1) + bnPoWTrust = 1; + + CBigNum bnLastBlockTrust = CBigNum(pprev->nChainTrust - pprev->pprev->nChainTrust); + + // Return nPoWTrust + 2/3 of previous block score if two parent blocks are not PoS blocks + if (!(pprev->IsProofOfStake() && pprev->pprev->IsProofOfStake())) + return (bnPoWTrust + 2 * bnLastBlockTrust / 3).getuint256(); + + int nPoSCount = 0; + + // Check last 12 blocks type + while (pprev->nHeight - currentIndex->nHeight < 12) + { + if (currentIndex->IsProofOfStake()) + nPoSCount++; + currentIndex = currentIndex->pprev; + } + + // Return nPoWTrust + 2/3 of previous block score if less than 7 PoS blocks found + if (nPoSCount < 7) + return (bnPoWTrust + 2 * bnLastBlockTrust / 3).getuint256(); + + bnTarget.SetCompact(pprev->nBits); + + if (bnTarget <= 0) + return 0; + + CBigNum bnNewTrust = (CBigNum(1)<<256) / (bnTarget+1); + + // Return nPoWTrust + full trust score for previous block nBits + return (bnPoWTrust + bnNewTrust).getuint256(); + } +} + +bool CBlockIndex::IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned int nRequired, unsigned int nToCheck) +{ + unsigned int nFound = 0; + for (unsigned int i = 0; i < nToCheck && nFound < nRequired && pstart != NULL; i++) + { + if (pstart->nVersion >= minVersion) + ++nFound; + pstart = pstart->pprev; + } + return (nFound >= nRequired); +} + +bool static ReserealizeBlockSignature(CBlock* pblock) +{ + if (pblock->IsProofOfWork()) + { + pblock->vchBlockSig.clear(); + return true; + } + + return CPubKey::ReserealizeSignature(pblock->vchBlockSig); +} + +bool static IsCanonicalBlockSignature(CBlock* pblock) +{ + if (pblock->IsProofOfWork()) + return pblock->vchBlockSig.empty(); + + return IsDERSignature(pblock->vchBlockSig); +} + bool ProcessBlock(CNode* pfrom, CBlock* pblock) { // Check for duplicate @@ -1614,61 +2527,83 @@ 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 + // Check that block isn't listed as unconditionally banned. + if (!Checkpoints::CheckBanned(hash)) { + if (pfrom) + pfrom->Misbehaving(100); + return error("ProcessBlock() : block %s is rejected by hard-coded banlist", hash.GetHex().substr(0,20).c_str()); + } + + // 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)) + 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()); + // Strip the garbage from newly received blocks, if we found some + if (!IsCanonicalBlockSignature(pblock)) { + if (!ReserealizeBlockSignature(pblock)) + printf("WARNING: ProcessBlock() : ReserealizeBlockSignature FAILED\n"); + } + // Preliminary checks - if (!pblock->CheckBlock()) + if (!pblock->CheckBlock(true, true, (pblock->nTime > Checkpoints::GetLastCheckpointTime()))) 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) + if (pblock->IsProofOfStake()) { - // Extra checks to prevent "fill up memory by spamming with bogus blocks" - int64 deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime; - if (deltaTime < 0) + uint256 hashProofOfStake = 0, targetProofOfStake = 0; + if (!CheckProofOfStake(pblock->vtx[1], pblock->nBits, hashProofOfStake, targetProofOfStake)) { - pfrom->Misbehaving(100); - return error("ProcessBlock() : block with timestamp before last checkpoint"); + printf("WARNING: ProcessBlock(): check proof-of-stake failed for block %s\n", hash.ToString().c_str()); + return false; // do not error here as we expect this during initial block download } + if (!mapProofOfStake.count(hash)) // add to mapProofOfStake + mapProofOfStake.insert(make_pair(hash, hashProofOfStake)); + } + + 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_t deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime; CBigNum bnNewBlock; bnNewBlock.SetCompact(pblock->nBits); CBigNum bnRequired; - if (pblock->IsProofOfWork()) - bnRequired.SetCompact(ComputeMinWork(pcheckpoint->nBits, deltaTime)); + + if (pblock->IsProofOfStake()) + bnRequired.SetCompact(ComputeMinStake(GetLastBlockIndex(pcheckpoint, true)->nBits, deltaTime, pblock->nTime)); else - bnRequired = bnNewBlock; // TODO: Computer Min Stake Target Allowed + bnRequired.SetCompact(ComputeMinWork(GetLastBlockIndex(pcheckpoint, false)->nBits, deltaTime)); if (bnNewBlock > bnRequired) { - pfrom->Misbehaving(100); + if (pfrom) + pfrom->Misbehaving(100); 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()) + if (pblock->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()); + if (setStakeSeenOrphan.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash)) + return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for orphan block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str()); else - setStakeSeenOrphan.insert(pblock2->GetProofOfStake()); + setStakeSeenOrphan.insert(pblock->GetProofOfStake()); } + CBlock* pblock2 = new CBlock(*pblock); mapOrphanBlocks.insert(make_pair(hash, pblock2)); mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2)); @@ -1678,7 +2613,8 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) 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))); + if (!IsInitialBlockDownload()) + pfrom->AskFor(CInv(MSG_BLOCK, WantedByOrphan(pblock2))); } return true; } @@ -1690,7 +2626,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) // Recursively process any orphan blocks that depended on this one vector vWorkQueue; vWorkQueue.push_back(hash); - for (int i = 0; i < vWorkQueue.size(); i++) + for (unsigned int i = 0; i < vWorkQueue.size(); i++) { uint256 hashPrev = vWorkQueue[i]; for (multimap::iterator mi = mapOrphanBlocksByPrev.lower_bound(hashPrev); @@ -1708,39 +2644,66 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) } printf("ProcessBlock: ACCEPTED\n"); - return true; -} - + // ppcoin: if responsible for sync-checkpoint send it + if (pfrom && !CSyncCheckpoint::strMasterPrivKey.empty()) + Checkpoints::SendSyncCheckpoint(Checkpoints::AutoSelectSyncCheckpoint()); + return true; +} +// ppcoin: check block signature +bool CBlock::CheckBlockSignature() const +{ + if (vchBlockSig.empty()) + return false; + txnouttype whichType; + vector vSolutions; + if (!Solver(vtx[1].vout[1].scriptPubKey, whichType, vSolutions)) + return false; + if (whichType == TX_PUBKEY) + { + valtype& vchPubKey = vSolutions[0]; + CPubKey key(vchPubKey); + if (!key.IsValid()) + return false; + return key.Verify(GetHash(), vchBlockSig); + } + return false; +} -bool CheckDiskSpace(uint64 nAdditionalBytes) +bool CheckDiskSpace(uint64_t nAdditionalBytes) { - uint64 nFreeBytesAvailable = filesystem::space(GetDataDir()).available; + uint64_t nFreeBytesAvailable = filesystem::space(GetDataDir()).available; - // Check for 15MB because database could create another 10MB log file at any time - if (nFreeBytesAvailable < (uint64)15000000 + nAdditionalBytes) + // Check for nMinDiskSpace bytes (currently 50MB) + if (nFreeBytesAvailable < nMinDiskSpace + nAdditionalBytes) { fShutdown = true; - string strMessage = _("Warning: Disk space is low "); + string strMessage = _("Warning: Disk space is low!"); strMiscWarning = strMessage; printf("*** %s\n", strMessage.c_str()); - ThreadSafeMessageBox(strMessage, "Bitcoin", wxOK | wxICON_EXCLAMATION); - CreateThread(Shutdown, NULL); + uiInterface.ThreadSafeMessageBox(strMessage, "NovaCoin", CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL); + StartShutdown(); return false; } return true; } +static filesystem::path BlockFilePath(unsigned int nFile) +{ + string strBlockFn = strprintf("blk%04u.dat", nFile); + return GetDataDir() / strBlockFn; +} + FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode) { - if (nFile == -1) + if ((nFile < 1) || (nFile == std::numeric_limits::max())) return NULL; - FILE* file = fopen(strprintf("%s/blk%04d.dat", GetDataDir().c_str(), nFile).c_str(), pszMode); + FILE* file = fopen(BlockFilePath(nFile).string().c_str(), pszMode); if (!file) return NULL; if (nBlockPos != 0 && !strchr(pszMode, 'a') && !strchr(pszMode, 'w')) @@ -1759,15 +2722,15 @@ static unsigned int nCurrentBlockFile = 1; FILE* AppendBlockFile(unsigned int& nFileRet) { nFileRet = 0; - loop + for ( ; ; ) { FILE* file = OpenBlockFile(nCurrentBlockFile, 0, "ab"); if (!file) return NULL; if (fseek(file, 0, SEEK_END) != 0) return NULL; - // FAT32 filesize max 4GB, fseek and ftell max 2GB, so we must stay under 2GB - if (ftell(file) < 0x7F000000 - MAX_SIZE) + // FAT32 file size max 4GB, fseek and ftell max 2GB, so we must stay under 2GB + if (ftell(file) < (long)(0x7F000000 - MAX_SIZE)) { nFileRet = nCurrentBlockFile; return file; @@ -1777,25 +2740,40 @@ FILE* AppendBlockFile(unsigned int& nFileRet) } } +void UnloadBlockIndex() +{ + mapBlockIndex.clear(); + setStakeSeen.clear(); + pindexGenesisBlock = NULL; + nBestHeight = 0; + nBestChainTrust = 0; + nBestInvalidTrust = 0; + hashBestChain = 0; + pindexBest = NULL; +} + bool LoadBlockIndex(bool fAllowNew) { if (fTestNet) { - hashGenesisBlock = uint256("0x00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"); - bnProofOfWorkLimit = CBigNum(~uint256(0) >> 28); - pchMessageStart[0] = 0xfa; - pchMessageStart[1] = 0xbf; - pchMessageStart[2] = 0xb5; - pchMessageStart[3] = 0xda; + pchMessageStart[0] = 0xcd; + pchMessageStart[1] = 0xf2; + pchMessageStart[2] = 0xc0; + pchMessageStart[3] = 0xef; + + bnProofOfWorkLimit = bnProofOfWorkLimitTestNet; // 16 bits PoW target limit for testnet + nStakeMinAge = 2 * nOneHour; // test net min age is 2 hours + nModifierInterval = 20 * 60; // test modifier interval is 20 minutes + nCoinbaseMaturity = 10; // test maturity is 10 blocks + nStakeTargetSpacing = 5 * 60; // test block spacing is 5 minutes } // // Load block index // - CTxDB txdb("cr"); + CTxDB txdb("cr+"); if (!txdb.LoadBlockIndex()) return false; - txdb.Close(); // // Init with genesis block @@ -1805,44 +2783,44 @@ bool LoadBlockIndex(bool fAllowNew) if (!fAllowNew) return false; - // Genesis Block: - // CBlock(hash=000000000019d6, ver=1, hashPrevBlock=00000000000000, hashMerkleRoot=4a5e1e, nTime=1231006505, nBits=1d00ffff, nNonce=2083236893, vtx=1) - // CTransaction(hash=4a5e1e, ver=1, vin.size=1, vout.size=1, nLockTime=0) - // CTxIn(COutPoint(000000, -1), coinbase 04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73) - // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) - // vMerkleTree: 4a5e1e - // Genesis block - const char* pszTimestamp = "MarketWatch 07/Nov/2011 Gold tops $1,790 to end at over six-week high"; + + // MainNet: + + //CBlock(hash=00000a060336cbb72fe969666d337b87198b1add2abaa59cca226820b32933a4, ver=1, hashPrevBlock=0000000000000000000000000000000000000000000000000000000000000000, hashMerkleRoot=4cb33b3b6a861dcbc685d3e614a9cafb945738d6833f182855679f2fad02057b, nTime=1360105017, nBits=1e0fffff, nNonce=1575379, vtx=1, vchBlockSig=) + // Coinbase(hash=4cb33b3b6a, nTime=1360105017, ver=1, vin.size=1, vout.size=1, nLockTime=0) + // CTxIn(COutPoint(0000000000, 4294967295), coinbase 04ffff001d020f274468747470733a2f2f626974636f696e74616c6b2e6f72672f696e6465782e7068703f746f7069633d3133343137392e6d736731353032313936236d736731353032313936) + // CTxOut(empty) + // vMerkleTree: 4cb33b3b6a + + // TestNet: + + //CBlock(hash=0000c763e402f2436da9ed36c7286f62c3f6e5dbafce9ff289bd43d7459327eb, ver=1, hashPrevBlock=0000000000000000000000000000000000000000000000000000000000000000, hashMerkleRoot=4cb33b3b6a861dcbc685d3e614a9cafb945738d6833f182855679f2fad02057b, nTime=1360105017, nBits=1f00ffff, nNonce=46534, vtx=1, vchBlockSig=) + // Coinbase(hash=4cb33b3b6a, nTime=1360105017, ver=1, vin.size=1, vout.size=1, nLockTime=0) + // CTxIn(COutPoint(0000000000, 4294967295), coinbase 04ffff001d020f274468747470733a2f2f626974636f696e74616c6b2e6f72672f696e6465782e7068703f746f7069633d3133343137392e6d736731353032313936236d736731353032313936) + // CTxOut(empty) + // vMerkleTree: 4cb33b3b6a + + const char* pszTimestamp = "https://bitcointalk.org/index.php?topic=134179.msg1502196#msg1502196"; CTransaction txNew; - txNew.nTime = 1339538219; + txNew.nTime = 1360105017; 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.vin[0].scriptSig = CScript() << 486604799 << CBigNum(9999) << vector((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp)); txNew.vout[0].SetEmpty(); CBlock block; block.vtx.push_back(txNew); block.hashPrevBlock = 0; block.hashMerkleRoot = block.BuildMerkleTree(); block.nVersion = 1; - block.nTime = 1339540307; + block.nTime = 1360105017; block.nBits = bnProofOfWorkLimit.GetCompact(); - block.nNonce = 1281822831; - - if (fTestNet) - { - block.nTime = 1296688602; - block.nBits = 0x1d07fff8; - block.nNonce = 384568319; - } + block.nNonce = !fTestNet ? 1575379 : 46534; //// debug print - 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("0x1557f46a17fcf8843dbe4c0c0edfd1d17eeff2c3c48d73a59d11f5d176e4b54d")); + assert(block.hashMerkleRoot == uint256("0x4cb33b3b6a861dcbc685d3e614a9cafb945738d6833f182855679f2fad02057b")); block.print(); - assert(block.GetHash() == hashGenesisBlock); + assert(block.GetHash() == (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet)); assert(block.CheckBlock()); // Start new block file @@ -1853,12 +2831,52 @@ bool LoadBlockIndex(bool fAllowNew) if (!block.AddToBlockIndex(nFile, nBlockPos)) return error("LoadBlockIndex() : genesis block not accepted"); - // ppcoin: initialize synchronized checkpoint - CTxDB txdbc; - if (!txdbc.WriteSyncCheckpoint(hashGenesisBlock)) + // initialize synchronized checkpoint + if (!Checkpoints::WriteSyncCheckpoint((!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet))) return error("LoadBlockIndex() : failed to init sync checkpoint"); - txdbc.Close(); - Checkpoints::hashSyncCheckpoint = hashGenesisBlock; + + // upgrade time set to zero if txdb initialized + { + if (!txdb.WriteModifierUpgradeTime(0)) + return error("LoadBlockIndex() : failed to init upgrade info"); + printf(" Upgrade Info: ModifierUpgradeTime txdb initialization\n"); + } + } + + { + CTxDB txdb("r+"); + 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 ((!fTestNet) && !Checkpoints::ResetSyncCheckpoint()) + return error("LoadBlockIndex() : failed to reset sync-checkpoint"); + } + + // upgrade time set to zero if blocktreedb initialized + if (txdb.ReadModifierUpgradeTime(nModifierUpgradeTime)) + { + if (nModifierUpgradeTime) + printf(" Upgrade Info: blocktreedb upgrade detected at timestamp %d\n", nModifierUpgradeTime); + else + printf(" Upgrade Info: no blocktreedb upgrade detected.\n"); + } + else + { + nModifierUpgradeTime = GetTime(); + printf(" Upgrade Info: upgrading blocktreedb at timestamp %u\n", nModifierUpgradeTime); + if (!txdb.WriteModifierUpgradeTime(nModifierUpgradeTime)) + return error("LoadBlockIndex() : failed to write upgrade info"); + } + +#ifndef USE_LEVELDB + txdb.Close(); +#endif } return true; @@ -1868,7 +2886,7 @@ bool LoadBlockIndex(bool fAllowNew) void PrintBlockTree() { - // precompute tree structure + // pre-compute tree structure map > mapNext; for (map::iterator mi = mapBlockIndex.begin(); mi != mapBlockIndex.end(); ++mi) { @@ -1911,20 +2929,21 @@ void PrintBlockTree() // print item CBlock block; block.ReadFromDisk(pindex); - printf("%d (%u,%u) %s %08lx %s tx %d", + printf("%d (%u,%u) %s %08x %s mint %7s tx %" PRIszu "", pindex->nHeight, pindex->nFile, pindex->nBlockPos, - block.GetHash().ToString().substr(0,20).c_str(), + block.GetHash().ToString().c_str(), block.nBits, DateTimeStrFormat("%x %H:%M:%S", block.GetBlockTime()).c_str(), + FormatMoney(pindex->nMint).c_str(), block.vtx.size()); PrintWallets(block); - // put the main timechain first + // put the main time-chain first vector& vNext = mapNext[pindex]; - for (int i = 0; i < vNext.size(); i++) + for (unsigned int i = 0; i < vNext.size(); i++) { if (vNext[i]->pnext) { @@ -1934,59 +2953,113 @@ void PrintBlockTree() } // iterate children - for (int i = 0; i < vNext.size(); i++) + for (unsigned int i = 0; i < vNext.size(); i++) vStack.push_back(make_pair(nCol+i, vNext[i])); } } +bool LoadExternalBlockFile(FILE* fileIn) +{ + int64_t nStart = GetTimeMillis(); - - - - - - - + int nLoaded = 0; + { + LOCK(cs_main); + try { + CAutoFile blkdat(fileIn, SER_DISK, CLIENT_VERSION); + unsigned int nPos = 0; + while (nPos != std::numeric_limits::max() && blkdat.good() && !fRequestShutdown) + { + unsigned char pchData[65536]; + do { + fseek(blkdat, nPos, SEEK_SET); + size_t nRead = fread(pchData, 1, sizeof(pchData), blkdat); + if (nRead <= 8) + { + nPos = std::numeric_limits::max(); + break; + } + void* nFind = memchr(pchData, pchMessageStart[0], nRead+1-sizeof(pchMessageStart)); + if (nFind) + { + if (memcmp(nFind, pchMessageStart, sizeof(pchMessageStart))==0) + { + nPos += ((unsigned char*)nFind - pchData) + sizeof(pchMessageStart); + break; + } + nPos += ((unsigned char*)nFind - pchData) + 1; + } + else + nPos += sizeof(pchData) - sizeof(pchMessageStart) + 1; + } while(!fRequestShutdown); + if (nPos == std::numeric_limits::max()) + break; + fseek(blkdat, nPos, SEEK_SET); + unsigned int nSize; + blkdat >> nSize; + if (nSize > 0 && nSize <= MAX_BLOCK_SIZE) + { + CBlock block; + blkdat >> block; + if (ProcessBlock(NULL,&block)) + { + nLoaded++; + nPos += 4 + nSize; + } + } + } + } + catch (const std::exception&) { + printf("%s() : Deserialize or I/O error caught during load\n", + BOOST_CURRENT_FUNCTION); + } + } + printf("Loaded %i blocks from external file in %" PRId64 "ms\n", nLoaded, GetTimeMillis() - nStart); + return nLoaded > 0; +} ////////////////////////////////////////////////////////////////////////////// // // CAlert // -map mapAlerts; -CCriticalSection cs_mapAlerts; +extern map mapAlerts; +extern CCriticalSection cs_mapAlerts; string GetWarnings(string strFor) { int nPriority = 0; string strStatusBar; string strRPC; + if (GetBoolArg("-testsafemode")) strRPC = "test"; // Misc warnings like out of disk space and clock is wrong - if (strMiscWarning != "") + if (!strMiscWarning.empty()) { nPriority = 1000; strStatusBar = strMiscWarning; } - // Longer invalid proof-of-work chain - if (pindexBest && nBestInvalidTrust > nBestChainTrust + pindexBest->GetBlockTrust() * 6) + // if detected unmet upgrade requirement enter safe mode + // Note: Modifier upgrade requires blockchain redownload if past protocol switch + if (IsFixedModifierInterval(nModifierUpgradeTime + nOneDay)) // 1 day margin { - nPriority = 2000; - strStatusBar = strRPC = "WARNING: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade."; + nPriority = 5000; + strStatusBar = strRPC = "WARNING: Blockchain redownload required approaching or past v.0.4.4.6u4 upgrade deadline."; } + // if detected invalid checkpoint enter safe mode 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."; + strStatusBar = strRPC = _("WARNING: Invalid checkpoint found! Displayed transactions may not be correct! You may need to upgrade, or notify developers."); } // Alerts - CRITICAL_BLOCK(cs_mapAlerts) { + LOCK(cs_mapAlerts); BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts) { const CAlert& alert = item.second; @@ -1994,6 +3067,8 @@ string GetWarnings(string strFor) { nPriority = alert.nPriority; strStatusBar = alert.strStatusBar; + if (nPriority > 1000) + strRPC = strStatusBar; } } } @@ -2006,53 +3081,6 @@ string GetWarnings(string strFor) return "error"; } -bool CAlert::ProcessAlert() -{ - if (!CheckSignature()) - return false; - if (!IsInEffect()) - return false; - - CRITICAL_BLOCK(cs_mapAlerts) - { - // Cancel previous alerts - for (map::iterator mi = mapAlerts.begin(); mi != mapAlerts.end();) - { - const CAlert& alert = (*mi).second; - if (Cancels(alert)) - { - printf("cancelling alert %d\n", alert.nID); - mapAlerts.erase(mi++); - } - else if (!alert.IsInEffect()) - { - printf("expiring alert %d\n", alert.nID); - mapAlerts.erase(mi++); - } - else - mi++; - } - - // Check if this alert has been cancelled - BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts) - { - const CAlert& alert = item.second; - if (alert.Cancels(*this)) - { - printf("alert already cancelled by %d\n", alert.nID); - return false; - } - } - - // Add to mapAlerts - mapAlerts.insert(make_pair(GetHash(), *this)); - } - - printf("accepted alert %d, AppliesToMe()=%d\n", nID, AppliesToMe()); - MainFrameRepaint(); - return true; -} - @@ -2070,8 +3098,21 @@ bool static AlreadyHave(CTxDB& txdb, const CInv& inv) { switch (inv.type) { - case MSG_TX: return mapTransactions.count(inv.hash) || mapOrphanTransactions.count(inv.hash) || txdb.ContainsTx(inv.hash); - case MSG_BLOCK: return mapBlockIndex.count(inv.hash) || mapOrphanBlocks.count(inv.hash); + case MSG_TX: + { + bool txInMap = false; + { + LOCK(mempool.cs); + txInMap = (mempool.exists(inv.hash)); + } + return txInMap || + mapOrphanTransactions.count(inv.hash) || + txdb.ContainsTx(inv.hash); + } + + case MSG_BLOCK: + return mapBlockIndex.count(inv.hash) || + mapOrphanBlocks.count(inv.hash); } // Don't know what it is, just say we already got one return true; @@ -2081,29 +3122,22 @@ bool static AlreadyHave(CTxDB& txdb, const CInv& inv) // The message start string is designed to be unlikely to occur in normal data. -// The characters are rarely used upper ascii, not valid as UTF-8, and produce +// The characters are rarely used upper ASCII, not valid as UTF-8, and produce // a large 4-byte int at any alignment. -unsigned char pchMessageStart[4] = { 0xf9, 0xbe, 0xb4, 0xd9 }; - +unsigned char pchMessageStart[4] = { 0xe4, 0xe8, 0xe9, 0xe5 }; bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) { - static map > mapReuseKey; + static map mapReuseKey; RandAddSeedPerfmon(); - if (fDebug) { - printf("%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str()); - printf("received: %s (%d bytes)\n", strCommand.c_str(), vRecv.size()); - } + if (fDebug) + printf("received: %s (%" PRIszu " bytes)\n", strCommand.c_str(), vRecv.size()); if (mapArgs.count("-dropmessagestest") && GetRand(atoi(mapArgs["-dropmessagestest"])) == 0) { printf("dropmessagestest DROPPING RECV MESSAGE\n"); return true; } - - - - if (strCommand == "version") { // Each connection can only send one version message @@ -2113,22 +3147,34 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) return false; } - int64 nTime; + int64_t nTime; CAddress addrMe; CAddress addrFrom; - uint64 nNonce = 1; + uint64_t nNonce = 1; vRecv >> pfrom->nVersion >> pfrom->nServices >> nTime >> addrMe; + if (pfrom->nVersion < MIN_PROTO_VERSION) + { + // Since February 20, 2012, the protocol is initiated at version 209, + // and earlier versions are no longer supported + printf("partner %s using obsolete version %i; disconnecting\n", pfrom->addr.ToString().c_str(), pfrom->nVersion); + pfrom->fDisconnect = true; + return false; + } + if (pfrom->nVersion == 10300) pfrom->nVersion = 300; - if (pfrom->nVersion >= 106 && !vRecv.empty()) + if (!vRecv.empty()) vRecv >> addrFrom >> nNonce; - if (pfrom->nVersion >= 106 && !vRecv.empty()) + if (!vRecv.empty()) vRecv >> pfrom->strSubVer; - if (pfrom->nVersion >= 209 && !vRecv.empty()) + if (!vRecv.empty()) vRecv >> pfrom->nStartingHeight; - if (pfrom->nVersion == 0) - return false; + if (pfrom->fInbound && addrMe.IsRoutable()) + { + pfrom->addrLocal = addrMe; + SeenLocal(addrMe); + } // Disconnect if we connected to ourself if (nNonce == nLocalHostNonce && nNonce > 1) @@ -2138,7 +3184,14 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) return true; } - // ppcoin: record my external IP reported by peer + if (pfrom->nVersion < 60010) + { + printf("partner %s using a buggy client %d, disconnecting\n", pfrom->addr.ToString().c_str(), pfrom->nVersion); + pfrom->fDisconnect = true; + return true; + } + + // record my external IP reported by peer if (addrFrom.IsRoutable() && addrMe.IsRoutable()) addrSeenByPeer = addrMe; @@ -2148,37 +3201,43 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) pfrom->fClient = !(pfrom->nServices & NODE_NETWORK); - AddTimeData(pfrom->addr.ip, nTime); + AddTimeData(pfrom->addr, nTime); // Change version - if (pfrom->nVersion >= 209) - pfrom->PushMessage("verack"); - pfrom->vSend.SetVersion(min(pfrom->nVersion, VERSION)); - if (pfrom->nVersion < 209) - pfrom->vRecv.SetVersion(min(pfrom->nVersion, VERSION)); + pfrom->PushMessage("verack"); + pfrom->vSend.SetVersion(min(pfrom->nVersion, PROTOCOL_VERSION)); if (!pfrom->fInbound) { // Advertise our address - if (addrLocalHost.IsRoutable() && !fUseProxy) + if (!fNoListen && !IsInitialBlockDownload()) { - CAddress addr(addrLocalHost); - addr.nTime = GetAdjustedTime(); - pfrom->PushAddress(addr); + CAddress addr = GetLocalAddress(&pfrom->addr); + if (addr.IsRoutable()) + pfrom->PushAddress(addr); } // Get recent addresses - if (pfrom->nVersion >= 31402 || mapAddresses.size() < 1000) + if (pfrom->fOneShot || pfrom->nVersion >= CADDR_TIME_VERSION || addrman.size() < 1000) { pfrom->PushMessage("getaddr"); pfrom->fGetAddr = true; } + addrman.Good(pfrom->addr); + } else { + if (((CNetAddr)pfrom->addr) == (CNetAddr)addrFrom) + { + addrman.Add(addrFrom, addrFrom); + addrman.Good(addrFrom); + } } // Ask the first connected node for block updates - static int nAskedForBlocks; - if (!pfrom->fClient && - (pfrom->nVersion < 32000 || pfrom->nVersion >= 32400) && + static int nAskedForBlocks = 0; + if (!pfrom->fClient && !pfrom->fOneShot && + (pfrom->nStartingHeight > (nBestHeight - 144)) && + (pfrom->nVersion < NOBLKS_VERSION_START || + pfrom->nVersion >= NOBLKS_VERSION_END) && (nAskedForBlocks < 1 || vNodes.size() <= 1)) { nAskedForBlocks++; @@ -2186,20 +3245,28 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } // Relay alerts - CRITICAL_BLOCK(cs_mapAlerts) + { + LOCK(cs_mapAlerts); BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts) item.second.RelayTo(pfrom); + } - // ppcoin: relay sync-checkpoint - CRITICAL_BLOCK(Checkpoints::cs_hashSyncCheckpoint) + // Relay sync-checkpoint + { + LOCK(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); + printf("receive version message: version %d, blocks=%d, us=%s, them=%s, peer=%s\n", pfrom->nVersion, pfrom->nStartingHeight, addrMe.ToString().c_str(), addrFrom.ToString().c_str(), pfrom->addr.ToString().c_str()); cPeerBlockCounts.input(pfrom->nStartingHeight); + + // ppcoin: ask for pending sync-checkpoint if any + if (!IsInitialBlockDownload()) + Checkpoints::AskForPendingSyncCheckpoint(pfrom); } @@ -2213,7 +3280,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) else if (strCommand == "verack") { - pfrom->vRecv.SetVersion(min(pfrom->nVersion, VERSION)); + pfrom->vRecv.SetVersion(min(pfrom->nVersion, PROTOCOL_VERSION)); } @@ -2223,48 +3290,43 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) vRecv >> vAddr; // Don't want addr from older versions unless seeding - if (pfrom->nVersion < 209) - return true; - if (pfrom->nVersion < 31402 && mapAddresses.size() > 1000) + if (pfrom->nVersion < CADDR_TIME_VERSION && addrman.size() > 1000) return true; if (vAddr.size() > 1000) { pfrom->Misbehaving(20); - return error("message addr size() = %d", vAddr.size()); + return error("message addr size() = %" PRIszu "", vAddr.size()); } // Store the new addresses - CAddrDB addrDB; - addrDB.TxnBegin(); - int64 nNow = GetAdjustedTime(); - int64 nSince = nNow - 10 * 60; + vector vAddrOk; + int64_t nNow = GetAdjustedTime(); + int64_t nSince = nNow - 10 * 60; BOOST_FOREACH(CAddress& addr, vAddr) { if (fShutdown) return true; - // ignore IPv6 for now, since it isn't implemented anyway - if (!addr.IsIPv4()) - continue; if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60) - addr.nTime = nNow - 5 * 24 * 60 * 60; - AddAddress(addr, 2 * 60 * 60, &addrDB); + addr.nTime = nNow - 5 * nOneDay; pfrom->AddAddressKnown(addr); + bool fReachable = IsReachable(addr); if (addr.nTime > nSince && !pfrom->fGetAddr && vAddr.size() <= 10 && addr.IsRoutable()) { // Relay to a limited number of other nodes - CRITICAL_BLOCK(cs_vNodes) { + LOCK(cs_vNodes); // Use deterministic randomness to send to the same nodes for 24 hours // at a time so the setAddrKnowns of the chosen nodes prevent repeats static uint256 hashSalt; if (hashSalt == 0) - RAND_bytes((unsigned char*)&hashSalt, sizeof(hashSalt)); - uint256 hashRand = hashSalt ^ (((int64)addr.ip)<<32) ^ ((GetTime()+addr.ip)/(24*60*60)); + hashSalt = GetRandHash(); + uint64_t hashAddr = addr.GetHash(); + uint256 hashRand = hashSalt ^ (hashAddr<<32) ^ ((GetTime()+hashAddr)/nOneDay); hashRand = Hash(BEGIN(hashRand), END(hashRand)); multimap mapMix; BOOST_FOREACH(CNode* pnode, vNodes) { - if (pnode->nVersion < 31402) + if (pnode->nVersion < CADDR_TIME_VERSION) continue; unsigned int nPointer; memcpy(&nPointer, &pnode, sizeof(nPointer)); @@ -2272,31 +3334,45 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) hashKey = Hash(BEGIN(hashKey), END(hashKey)); mapMix.insert(make_pair(hashKey, pnode)); } - int nRelayNodes = 2; + int nRelayNodes = fReachable ? 2 : 1; // limited relaying of addresses outside our network(s) for (multimap::iterator mi = mapMix.begin(); mi != mapMix.end() && nRelayNodes-- > 0; ++mi) ((*mi).second)->PushAddress(addr); } } + // Do not store addresses outside our network + if (fReachable) + vAddrOk.push_back(addr); } - addrDB.TxnCommit(); // Save addresses (it's ok if this fails) + addrman.Add(vAddrOk, pfrom->addr, 2 * nOneHour); if (vAddr.size() < 1000) pfrom->fGetAddr = false; + if (pfrom->fOneShot) + pfrom->fDisconnect = true; } - else if (strCommand == "inv") { vector vInv; vRecv >> vInv; - if (vInv.size() > 50000) + if (vInv.size() > MAX_INV_SZ) { pfrom->Misbehaving(20); - return error("message inv size() = %d", vInv.size()); + return error("message inv size() = %" PRIszu "", vInv.size()); } + // find last block in inv vector + size_t nLastBlock = std::numeric_limits::max(); + for (size_t nInv = 0; nInv < vInv.size(); nInv++) { + if (vInv[vInv.size() - 1 - nInv].type == MSG_BLOCK) { + nLastBlock = vInv.size() - 1 - nInv; + break; + } + } CTxDB txdb("r"); - BOOST_FOREACH(const CInv& inv, vInv) + for (size_t nInv = 0; nInv < vInv.size(); nInv++) { + const CInv &inv = vInv[nInv]; + if (fShutdown) return true; pfrom->AddInventoryKnown(inv); @@ -2307,8 +3383,16 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (!fAlreadyHave) pfrom->AskFor(inv); - else if (inv.type == MSG_BLOCK && mapOrphanBlocks.count(inv.hash)) + else if (inv.type == MSG_BLOCK && mapOrphanBlocks.count(inv.hash)) { pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(mapOrphanBlocks[inv.hash])); + } else if (nInv == nLastBlock) { + // In case we are on a very long side-chain, it is possible that we already have + // the last block in an inv bundle sent in response to getblocks. Try to detect + // this situation and push another getblocks to continue. + pfrom->PushGetBlocks(mapBlockIndex[inv.hash], uint256(0)); + if (fDebug) + printf("force request: %s\n", inv.ToString().c_str()); + } // Track requests for our stuff Inventory(inv.hash); @@ -2320,17 +3404,21 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) { vector vInv; vRecv >> vInv; - if (vInv.size() > 50000) + if (vInv.size() > MAX_INV_SZ) { pfrom->Misbehaving(20); - return error("message getdata size() = %d", vInv.size()); + return error("message getdata size() = %" PRIszu "", vInv.size()); } + if (fDebugNet || (vInv.size() != 1)) + printf("received getdata (%" PRIszu " invsz)\n", vInv.size()); + BOOST_FOREACH(const CInv& inv, vInv) { if (fShutdown) return true; - printf("received getdata for: %s\n", inv.ToString().c_str()); + if (fDebugNet || (vInv.size() == 1)) + printf("received getdata for: %s\n", inv.ToString().c_str()); if (inv.type == MSG_BLOCK) { @@ -2345,11 +3433,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // Trigger them to send a getblocks request for the next batch of inventory if (inv.hash == pfrom->hashContinue) { - // Bypass PushInventory, this must send even if redundant, - // and we want it right after the last block so they don't - // wait for other stuff first. + // ppcoin: send latest proof-of-work block to allow the + // download node to accept as orphan (proof-of-stake + // block might be rejected by stake connection check) vector vInv; - vInv.push_back(CInv(MSG_BLOCK, hashBestChain)); + vInv.push_back(CInv(MSG_BLOCK, GetLastBlockIndex(pindexBest, false)->GetBlockHash())); pfrom->PushMessage("inv", vInv); pfrom->hashContinue = 0; } @@ -2358,11 +3446,24 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) else if (inv.IsKnownType()) { // Send stream from relay memory - CRITICAL_BLOCK(cs_mapRelay) + bool pushed = false; { + LOCK(cs_mapRelay); map::iterator mi = mapRelay.find(inv); - if (mi != mapRelay.end()) + if (mi != mapRelay.end()) { pfrom->PushMessage(inv.GetCommand(), (*mi).second); + pushed = true; + } + } + if (!pushed && inv.type == MSG_TX) { + LOCK(mempool.cs); + if (mempool.exists(inv.hash)) { + CTransaction tx = mempool.lookup(inv.hash); + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss.reserve(1000); + ss << tx; + pfrom->PushMessage("tx", ss); + } } } @@ -2384,31 +3485,44 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // Send the rest of the chain if (pindex) pindex = pindex->pnext; - int nLimit = 500 + locator.GetDistanceBack(); - unsigned int nBytes = 0; + int nLimit = 500; printf("getblocks %d to %s limit %d\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,20).c_str(), nLimit); for (; pindex; pindex = pindex->pnext) { if (pindex->GetBlockHash() == hashStop) { - printf(" getblocks stopping at %d %s (%u bytes)\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,20).c_str(), nBytes); + printf(" getblocks stopping at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,20).c_str()); + // ppcoin: tell downloading node about the latest block if it's + // without risk being rejected due to stake connection check + if (hashStop != hashBestChain && pindex->GetBlockTime() + nStakeMinAge > pindexBest->GetBlockTime()) + pfrom->PushInventory(CInv(MSG_BLOCK, hashBestChain)); break; } pfrom->PushInventory(CInv(MSG_BLOCK, pindex->GetBlockHash())); - CBlock block; - block.ReadFromDisk(pindex, true); - nBytes += block.GetSerializeSize(SER_NETWORK); - if (--nLimit <= 0 || nBytes >= SendBufferSize()/2) + if (--nLimit <= 0) { // When this block is requested, we'll send an inv that'll make them // getblocks the next batch of inventory. - printf(" getblocks stopping at limit %d %s (%u bytes)\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,20).c_str(), nBytes); + printf(" getblocks stopping at limit %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,20).c_str()); pfrom->hashContinue = pindex->GetBlockHash(); break; } } } + else if (strCommand == "checkpoint") + { + CSyncCheckpoint checkpoint; + vRecv >> checkpoint; + if (checkpoint.ProcessSyncCheckpoint(pfrom)) + { + // Relay + pfrom->hashCheckpointKnown = checkpoint.hashCheckpoint; + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + checkpoint.RelayTo(pnode); + } + } else if (strCommand == "getheaders") { @@ -2434,8 +3548,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } vector vHeaders; - int nLimit = 2000 + locator.GetDistanceBack(); - printf("getheaders %d to %s limit %d\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,20).c_str(), nLimit); + int nLimit = 2000; + printf("getheaders %d to %s\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,20).c_str()); for (; pindex; pindex = pindex->pnext) { vHeaders.push_back(pindex->GetBlockHeader()); @@ -2449,7 +3563,9 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) else if (strCommand == "tx") { vector vWorkQueue; + vector vEraseQueue; CDataStream vMsg(vRecv); + CTxDB txdb("r"); CTransaction tx; vRecv >> tx; @@ -2457,44 +3573,55 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) pfrom->AddInventoryKnown(inv); bool fMissingInputs = false; - if (tx.AcceptToMemoryPool(true, &fMissingInputs)) + if (tx.AcceptToMemoryPool(txdb, true, &fMissingInputs)) { SyncWithWallets(tx, NULL, true); - RelayMessage(inv, vMsg); + RelayTransaction(tx, inv.hash); mapAlreadyAskedFor.erase(inv); vWorkQueue.push_back(inv.hash); + vEraseQueue.push_back(inv.hash); // Recursively process any orphan transactions that depended on this one - for (int i = 0; i < vWorkQueue.size(); i++) + for (unsigned int i = 0; i < vWorkQueue.size(); i++) { uint256 hashPrev = vWorkQueue[i]; - for (multimap::iterator mi = mapOrphanTransactionsByPrev.lower_bound(hashPrev); - mi != mapOrphanTransactionsByPrev.upper_bound(hashPrev); + for (set::iterator mi = mapOrphanTransactionsByPrev[hashPrev].begin(); + mi != mapOrphanTransactionsByPrev[hashPrev].end(); ++mi) { - const CDataStream& vMsg = *((*mi).second); - CTransaction tx; - CDataStream(vMsg) >> tx; - CInv inv(MSG_TX, tx.GetHash()); + const uint256& orphanTxHash = *mi; + CTransaction& orphanTx = mapOrphanTransactions[orphanTxHash]; + bool fMissingInputs2 = false; - if (tx.AcceptToMemoryPool(true)) + if (orphanTx.AcceptToMemoryPool(txdb, true, &fMissingInputs2)) { - printf(" accepted orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str()); + printf(" accepted orphan tx %s\n", orphanTxHash.ToString().substr(0,10).c_str()); SyncWithWallets(tx, NULL, true); - RelayMessage(inv, vMsg); - mapAlreadyAskedFor.erase(inv); - vWorkQueue.push_back(inv.hash); + RelayTransaction(orphanTx, orphanTxHash); + mapAlreadyAskedFor.erase(CInv(MSG_TX, orphanTxHash)); + vWorkQueue.push_back(orphanTxHash); + vEraseQueue.push_back(orphanTxHash); + } + else if (!fMissingInputs2) + { + // invalid orphan + vEraseQueue.push_back(orphanTxHash); + printf(" removed invalid orphan tx %s\n", orphanTxHash.ToString().substr(0,10).c_str()); } } } - BOOST_FOREACH(uint256 hash, vWorkQueue) + BOOST_FOREACH(uint256 hash, vEraseQueue) EraseOrphanTx(hash); } else if (fMissingInputs) { - printf("storing orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str()); - AddOrphanTx(vMsg); + AddOrphanTx(tx); + + // DoS prevention: do not allow mapOrphanTransactions to grow unbounded + unsigned int nEvicted = LimitOrphanTxSize(MAX_ORPHAN_TRANSACTIONS); + if (nEvicted > 0) + printf("mapOrphan overflow, removed %u tx\n", nEvicted); } if (tx.nDoS) pfrom->Misbehaving(tx.nDoS); } @@ -2504,11 +3631,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) { CBlock block; vRecv >> block; + uint256 hashBlock = block.GetHash(); - printf("received block %s\n", block.GetHash().ToString().substr(0,20).c_str()); + printf("received block %s\n", hashBlock.ToString().substr(0,20).c_str()); // block.print(); - CInv inv(MSG_BLOCK, block.GetHash()); + CInv inv(MSG_BLOCK, hashBlock); pfrom->AddInventoryKnown(inv); if (ProcessBlock(pfrom, &block)) @@ -2517,27 +3645,36 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } - else if (strCommand == "getaddr") + // This asymmetric behavior for inbound and outbound connections was introduced + // to prevent a fingerprinting attack: an attacker can send specific fake addresses + // to users' AddrMan and later request them by sending getaddr messages. + // Making users (which are behind NAT and can only make outgoing connections) ignore + // getaddr message mitigates the attack. + else if ((strCommand == "getaddr") && (pfrom->fInbound)) { - // Nodes rebroadcast an addr every 24 hours + // Don't return addresses older than nCutOff timestamp + int64_t nCutOff = GetTime() - (nNodeLifespan * nOneDay); pfrom->vAddrToSend.clear(); - int64 nSince = GetAdjustedTime() - 3 * 60 * 60; // in the last 3 hours - CRITICAL_BLOCK(cs_mapAddresses) - { - unsigned int nCount = 0; - BOOST_FOREACH(const PAIRTYPE(vector, CAddress)& item, mapAddresses) - { - const CAddress& addr = item.second; - if (addr.nTime > nSince) - nCount++; - } - BOOST_FOREACH(const PAIRTYPE(vector, CAddress)& item, mapAddresses) - { - const CAddress& addr = item.second; - if (addr.nTime > nSince && GetRand(nCount) < 2500) - pfrom->PushAddress(addr); - } + vector vAddr = addrman.GetAddr(); + BOOST_FOREACH(const CAddress &addr, vAddr) + if(addr.nTime > nCutOff) + pfrom->PushAddress(addr); + } + + + else if (strCommand == "mempool") + { + std::vector vtxid; + mempool.queryHashes(vtxid); + vector vInv; + for (unsigned int i = 0; i < vtxid.size(); i++) { + CInv inv(MSG_TX, vtxid[i]); + vInv.push_back(inv); + if (i == (MAX_INV_SZ - 1)) + break; } + if (vInv.size() > 0) + pfrom->PushMessage("inv", vInv); } @@ -2548,7 +3685,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (!GetBoolArg("-allowreceivebyip")) { - pfrom->PushMessage("reply", hashReply, (int)2, string("")); + pfrom->PushMessage("reply", hashReply, 2, string("")); return true; } @@ -2558,13 +3695,13 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) /// we have a chance to check the order here // Keep giving the same key to the same ip until they use it - if (!mapReuseKey.count(pfrom->addr.ip)) - pwalletMain->GetKeyFromPool(mapReuseKey[pfrom->addr.ip], true); + if (!mapReuseKey.count(pfrom->addr)) + pwalletMain->GetKeyFromPool(mapReuseKey[pfrom->addr], true); // Send back approval of order and pubkey to use CScript scriptPubKey; - scriptPubKey << mapReuseKey[pfrom->addr.ip] << OP_CHECKSIG; - pfrom->PushMessage("reply", hashReply, (int)0, scriptPubKey); + scriptPubKey << mapReuseKey[pfrom->addr] << OP_CHECKSIG; + pfrom->PushMessage("reply", hashReply, 0, scriptPubKey); } @@ -2574,8 +3711,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) vRecv >> hashReply; CRequestTracker tracker; - CRITICAL_BLOCK(pfrom->cs_mapRequests) { + LOCK(pfrom->cs_mapRequests); map::iterator mi = pfrom->mapRequests.find(hashReply); if (mi != pfrom->mapRequests.end()) { @@ -2590,6 +3727,20 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) else if (strCommand == "ping") { + uint64_t nonce = 0; + vRecv >> nonce; + // Echo the message back with the nonce. This allows for two useful features: + // + // 1) A remote node can quickly check if the connection is operational + // 2) Remote nodes can measure the latency of the network thread. If this node + // is overloaded it won't respond to pings quickly and the remote node can + // avoid sending us more work, like chain download requests. + // + // The nonce stops the remote getting confused between different pings: without + // it, if the remote node sends a ping once per second and this node takes 5 + // seconds to respond to each, the 5th ping the remote sends would appear to + // return very quickly. + pfrom->PushMessage("pong", nonce); } @@ -2598,30 +3749,31 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) CAlert alert; vRecv >> alert; - if (alert.ProcessAlert()) + uint256 alertHash = alert.GetHash(); + if (pfrom->setKnown.count(alertHash) == 0) { - // Relay - pfrom->setKnown.insert(alert.GetHash()); - CRITICAL_BLOCK(cs_vNodes) - BOOST_FOREACH(CNode* pnode, vNodes) - alert.RelayTo(pnode); + if (alert.ProcessAlert()) + { + // Relay + pfrom->setKnown.insert(alertHash); + { + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + alert.RelayTo(pnode); + } + } + else { + // Small DoS penalty so peers that send us lots of + // duplicate/expired/invalid-signature/whatever alerts + // eventually get banned. + // This isn't a Misbehaving(100) (immediate ban) because the + // peer might be an older or different implementation with + // a different signature key, etc. + pfrom->Misbehaving(10); + } } } - 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 { @@ -2655,14 +3807,18 @@ bool ProcessMessages(CNode* pfrom) // (x) data // - loop + for ( ; ; ) { + // Don't bother if send buffer is too full to respond anyway + if (pfrom->vSend.size() >= SendBufferSize()) + break; + // Scan for message start CDataStream::iterator pstart = search(vRecv.begin(), vRecv.end(), BEGIN(pchMessageStart), END(pchMessageStart)); int nHeaderSize = vRecv.GetSerializeSize(CMessageHeader()); if (vRecv.end() - pstart < nHeaderSize) { - if (vRecv.size() > nHeaderSize) + if ((int)vRecv.size() > nHeaderSize) { printf("\n\nPROCESSMESSAGE MESSAGESTART NOT FOUND\n\n"); vRecv.erase(vRecv.begin(), vRecv.end() - nHeaderSize); @@ -2670,7 +3826,7 @@ bool ProcessMessages(CNode* pfrom) break; } if (pstart - vRecv.begin() > 0) - printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", pstart - vRecv.begin()); + printf("\n\nPROCESSMESSAGE SKIPPED %" PRIpdd " BYTES\n\n", pstart - vRecv.begin()); vRecv.erase(vRecv.begin(), pstart); // Read header @@ -2688,7 +3844,7 @@ bool ProcessMessages(CNode* pfrom) unsigned int nMessageSize = hdr.nMessageSize; if (nMessageSize > MAX_SIZE) { - printf("ProcessMessage(%s, %u bytes) : nMessageSize > MAX_SIZE\n", strCommand.c_str(), nMessageSize); + printf("ProcessMessages(%s, %u bytes) : nMessageSize > MAX_SIZE\n", strCommand.c_str(), nMessageSize); continue; } if (nMessageSize > vRecv.size()) @@ -2699,17 +3855,14 @@ bool ProcessMessages(CNode* pfrom) } // Checksum - if (vRecv.GetVersion() >= 209) + uint256 hash = Hash(vRecv.begin(), vRecv.begin() + nMessageSize); + unsigned int nChecksum = 0; + memcpy(&nChecksum, &hash, sizeof(nChecksum)); + if (nChecksum != hdr.nChecksum) { - uint256 hash = Hash(vRecv.begin(), vRecv.begin() + nMessageSize); - unsigned int nChecksum = 0; - memcpy(&nChecksum, &hash, sizeof(nChecksum)); - if (nChecksum != hdr.nChecksum) - { - printf("ProcessMessage(%s, %u bytes) : CHECKSUM ERROR nChecksum=%08x hdr.nChecksum=%08x\n", - strCommand.c_str(), nMessageSize, nChecksum, hdr.nChecksum); - continue; - } + printf("ProcessMessages(%s, %u bytes) : CHECKSUM ERROR nChecksum=%08x hdr.nChecksum=%08x\n", + strCommand.c_str(), nMessageSize, nChecksum, hdr.nChecksum); + continue; } // Copy message to its own buffer @@ -2720,8 +3873,10 @@ bool ProcessMessages(CNode* pfrom) bool fRet = false; try { - CRITICAL_BLOCK(cs_main) + { + LOCK(cs_main); fRet = ProcessMessage(pfrom, strCommand, vMsg); + } if (fShutdown) return true; } @@ -2729,23 +3884,23 @@ bool ProcessMessages(CNode* pfrom) { if (strstr(e.what(), "end of data")) { - // Allow exceptions from underlength message on vRecv - printf("ProcessMessage(%s, %u bytes) : Exception '%s' caught, normally caused by a message being shorter than its stated length\n", strCommand.c_str(), nMessageSize, e.what()); + // Allow exceptions from under-length message on vRecv + printf("ProcessMessages(%s, %u bytes) : Exception '%s' caught, normally caused by a message being shorter than its stated length\n", strCommand.c_str(), nMessageSize, e.what()); } else if (strstr(e.what(), "size too large")) { - // Allow exceptions from overlong size - printf("ProcessMessage(%s, %u bytes) : Exception '%s' caught\n", strCommand.c_str(), nMessageSize, e.what()); + // Allow exceptions from over-long size + printf("ProcessMessages(%s, %u bytes) : Exception '%s' caught\n", strCommand.c_str(), nMessageSize, e.what()); } else { - PrintExceptionContinue(&e, "ProcessMessage()"); + PrintExceptionContinue(&e, "ProcessMessages()"); } } catch (std::exception& e) { - PrintExceptionContinue(&e, "ProcessMessage()"); + PrintExceptionContinue(&e, "ProcessMessages()"); } catch (...) { - PrintExceptionContinue(NULL, "ProcessMessage()"); + PrintExceptionContinue(NULL, "ProcessMessages()"); } if (!fRet) @@ -2759,81 +3914,46 @@ bool ProcessMessages(CNode* pfrom) bool SendMessages(CNode* pto, bool fSendTrickle) { - CRITICAL_BLOCK(cs_main) - { + TRY_LOCK(cs_main, lockMain); + if (lockMain) { + // Current time in microseconds + int64_t nNow = GetTimeMicros(); + // Don't send anything until we get their version message if (pto->nVersion == 0) return true; - // Keep-alive ping - if (pto->nLastSend && GetTime() - pto->nLastSend > 30 * 60 && pto->vSend.empty()) - pto->PushMessage("ping"); + // Keep-alive ping. We send a nonce of zero because we don't use it anywhere + // right now. + if (pto->nLastSend && GetTime() - pto->nLastSend > nPingInterval && pto->vSend.empty()) { + uint64_t nonce = 0; + pto->PushMessage("ping", nonce); + } + + // Start block sync + if (pto->fStartSync) { + pto->fStartSync = false; + pto->PushGetBlocks(pindexBest, uint256(0)); + } // Resend wallet transactions that haven't gotten in a block yet ResendWalletTransactions(); // Address refresh broadcast - static int64 nLastRebroadcast; - if (GetTime() - nLastRebroadcast > 24 * 60 * 60) - { - nLastRebroadcast = GetTime(); - CRITICAL_BLOCK(cs_vNodes) - { - BOOST_FOREACH(CNode* pnode, vNodes) - { - // Periodically clear setAddrKnown to allow refresh broadcasts - pnode->setAddrKnown.clear(); - - // Rebroadcast our address - if (addrLocalHost.IsRoutable() && !fUseProxy) - { - CAddress addr(addrLocalHost); - addr.nTime = GetAdjustedTime(); - pnode->PushAddress(addr); - } - } - } + if (!IsInitialBlockDownload() && pto->nNextLocalAddrSend < nNow) { + AdvertiseLocal(pto); + pto->nNextLocalAddrSend = PoissonNextSend(nNow, nOneDay); } - // Clear out old addresses periodically so it's not too much work at once - static int64 nLastClear; - if (nLastClear == 0) - nLastClear = GetTime(); - if (GetTime() - nLastClear > 10 * 60 && vNodes.size() >= 3) - { - nLastClear = GetTime(); - CRITICAL_BLOCK(cs_mapAddresses) - { - CAddrDB addrdb; - int64 nSince = GetAdjustedTime() - 14 * 24 * 60 * 60; - for (map, CAddress>::iterator mi = mapAddresses.begin(); - mi != mapAddresses.end();) - { - const CAddress& addr = (*mi).second; - if (addr.nTime < nSince) - { - if (mapAddresses.size() < 1000 || GetTime() > nLastClear + 20) - break; - addrdb.EraseAddress(addr); - mapAddresses.erase(mi++); - } - else - mi++; - } - } - } - - // // Message: addr // - if (fSendTrickle) - { + if (pto->nNextAddrSend < nNow) { + pto->nNextAddrSend = PoissonNextSend(nNow, 30); vector vAddr; vAddr.reserve(pto->vAddrToSend.size()); BOOST_FOREACH(const CAddress& addr, pto->vAddrToSend) { - // returns true if wasn't already contained in the set if (pto->setAddrKnown.insert(addr).second) { vAddr.push_back(addr); @@ -2850,14 +3970,18 @@ bool SendMessages(CNode* pto, bool fSendTrickle) pto->PushMessage("addr", vAddr); } - // // Message: inventory // vector vInv; vector vInvWait; - CRITICAL_BLOCK(pto->cs_inventory) { + bool fSendTrickle = false; + if (pto->nNextInvSend < nNow) { + fSendTrickle = true; + pto->nNextInvSend = PoissonNextSend(nNow, 5); + } + LOCK(pto->cs_inventory); vInv.reserve(pto->vInventoryToSend.size()); vInvWait.reserve(pto->vInventoryToSend.size()); BOOST_FOREACH(const CInv& inv, pto->vInventoryToSend) @@ -2871,20 +3995,11 @@ bool SendMessages(CNode* pto, bool fSendTrickle) // 1/4 of tx invs blast to all immediately static uint256 hashSalt; if (hashSalt == 0) - RAND_bytes((unsigned char*)&hashSalt, sizeof(hashSalt)); + hashSalt = GetRandHash(); uint256 hashRand = inv.hash ^ hashSalt; hashRand = Hash(BEGIN(hashRand), END(hashRand)); bool fTrickleWait = ((hashRand & 3) != 0); - // always trickle our own transactions - if (!fTrickleWait) - { - CWalletTx wtx; - if (GetTransaction(inv.hash, wtx)) - if (wtx.fFromMe) - fTrickleWait = true; - } - if (fTrickleWait) { vInvWait.push_back(inv); @@ -2913,22 +4028,22 @@ bool SendMessages(CNode* pto, bool fSendTrickle) // Message: getdata // vector vGetData; - int64 nNow = GetTime() * 1000000; CTxDB txdb("r"); while (!pto->mapAskFor.empty() && (*pto->mapAskFor.begin()).first <= nNow) { const CInv& inv = (*pto->mapAskFor.begin()).second; if (!AlreadyHave(txdb, inv)) { - printf("sending getdata: %s\n", inv.ToString().c_str()); + if (fDebugNet) + printf("sending getdata: %s\n", inv.ToString().c_str()); vGetData.push_back(inv); if (vGetData.size() >= 1000) { pto->PushMessage("getdata", vGetData); vGetData.clear(); } + mapAlreadyAskedFor[inv] = nNow; } - mapAlreadyAskedFor[inv] = nNow; pto->mapAskFor.erase(pto->mapAskFor.begin()); } if (!vGetData.empty()) @@ -2939,601 +4054,23 @@ bool SendMessages(CNode* pto, bool fSendTrickle) } - - - - - - - - - - - - -////////////////////////////////////////////////////////////////////////////// -// -// BitcoinMiner -// - -int static FormatHashBlocks(void* pbuffer, unsigned int len) -{ - unsigned char* pdata = (unsigned char*)pbuffer; - unsigned int blocks = 1 + ((len + 8) / 64); - unsigned char* pend = pdata + 64 * blocks; - memset(pdata + len, 0, 64 * blocks - len); - pdata[len] = 0x80; - unsigned int bits = len * 8; - pend[-1] = (bits >> 0) & 0xff; - pend[-2] = (bits >> 8) & 0xff; - pend[-3] = (bits >> 16) & 0xff; - pend[-4] = (bits >> 24) & 0xff; - return blocks; -} - -static const unsigned int pSHA256InitState[8] = -{0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; - -void SHA256Transform(void* pstate, void* pinput, const void* pinit) -{ - SHA256_CTX ctx; - unsigned char data[64]; - - SHA256_Init(&ctx); - - for (int i = 0; i < 16; i++) - ((uint32_t*)data)[i] = ByteReverse(((uint32_t*)pinput)[i]); - - for (int i = 0; i < 8; i++) - ctx.h[i] = ((uint32_t*)pinit)[i]; - - SHA256_Update(&ctx, data, sizeof(data)); - for (int i = 0; i < 8; i++) - ((uint32_t*)pstate)[i] = ctx.h[i]; -} - -// -// ScanHash scans nonces looking for a hash with at least some zero bits. -// It operates on big endian data. Caller does the byte reversing. -// All input buffers are 16-byte aligned. nNonce is usually preserved -// between calls, but periodically or if nNonce is 0xffff0000 or above, -// the block is rebuilt and nNonce starts over at zero. -// -unsigned int static ScanHash_CryptoPP(char* pmidstate, char* pdata, char* phash1, char* phash, unsigned int& nHashesDone) -{ - unsigned int& nNonce = *(unsigned int*)(pdata + 12); - for (;;) - { - // Crypto++ SHA-256 - // Hash pdata using pmidstate as the starting state into - // preformatted buffer phash1, then hash phash1 into phash - nNonce++; - SHA256Transform(phash1, pdata, pmidstate); - SHA256Transform(phash, phash1, pSHA256InitState); - - // Return the nonce if the hash has at least some zero bits, - // caller will check if it has enough to reach the target - if (((unsigned short*)phash)[14] == 0) - return nNonce; - - // If nothing found after trying for a while, return -1 - if ((nNonce & 0xffff) == 0) - { - nHashesDone = 0xffff+1; - return -1; - } - } -} - -// Some explaining would be appreciated -class COrphan +class CMainCleanup { public: - CTransaction* ptx; - set setDependsOn; - double dPriority; - - COrphan(CTransaction* ptxIn) - { - ptx = ptxIn; - dPriority = 0; - } - - void print() const - { - printf("COrphan(hash=%s, dPriority=%.1f)\n", ptx->GetHash().ToString().substr(0,10).c_str(), dPriority); - BOOST_FOREACH(uint256 hash, setDependsOn) - printf(" setDependsOn %s\n", hash.ToString().substr(0,10).c_str()); - } -}; - - -CBlock* CreateNewBlock(CWallet* pwallet) -{ - CReserveKey reservekey(pwallet); - - // Create new block - auto_ptr pblock(new CBlock()); - if (!pblock.get()) - return NULL; - - // Create coinbase tx - CTransaction txNew; - txNew.vin.resize(1); - txNew.vin[0].prevout.SetNull(); - txNew.vout.resize(1); - txNew.vout[0].scriptPubKey << reservekey.GetReservedKey() << OP_CHECKSIG; - - // 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; - 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) - CRITICAL_BLOCK(cs_mapTransactions) - { - CTxDB txdb("r"); - - // Priority order to process transactions - list vOrphan; // list memory doesn't move - map > mapDependers; - multimap mapPriority; - for (map::iterator mi = mapTransactions.begin(); mi != mapTransactions.end(); ++mi) - { - CTransaction& tx = (*mi).second; - if (tx.IsCoinBase() || tx.IsCoinStake() || !tx.IsFinal()) - continue; - - COrphan* porphan = NULL; - double dPriority = 0; - BOOST_FOREACH(const CTxIn& txin, tx.vin) - { - // Read prev transaction - CTransaction txPrev; - CTxIndex txindex; - if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex)) - { - // Has to wait for dependencies - if (!porphan) - { - // Use list for automatic deletion - vOrphan.push_back(COrphan(&tx)); - porphan = &vOrphan.back(); - } - mapDependers[txin.prevout.hash].push_back(porphan); - porphan->setDependsOn.insert(txin.prevout.hash); - continue; - } - int64 nValueIn = txPrev.vout[txin.prevout.n].nValue; - - // Read block header - int nConf = txindex.GetDepthInMainChain(); - - dPriority += (double)nValueIn * nConf; - - if (fDebug && GetBoolArg("-printpriority")) - printf("priority nValueIn=%-12I64d nConf=%-5d dPriority=%-20.1f\n", nValueIn, nConf, dPriority); - } - - // Priority is sum(valuein * age) / txsize - dPriority /= ::GetSerializeSize(tx, SER_NETWORK); - - if (porphan) - porphan->dPriority = dPriority; - else - mapPriority.insert(make_pair(-dPriority, &(*mi).second)); - - if (fDebug && GetBoolArg("-printpriority")) - { - printf("priority %-20.1f %s\n%s", dPriority, tx.GetHash().ToString().substr(0,10).c_str(), tx.ToString().c_str()); - if (porphan) - porphan->print(); - printf("\n"); - } - } - - // Collect transactions into block - map mapTestPool; - uint64 nBlockSize = 1000; - int nBlockSigOps = 100; - while (!mapPriority.empty()) - { - // Take highest priority transaction off priority queue - double dPriority = -(*mapPriority.begin()).first; - CTransaction& tx = *(*mapPriority.begin()).second; - mapPriority.erase(mapPriority.begin()); - - // Size limits - unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); - if (nBlockSize + nTxSize >= MAX_BLOCK_SIZE_GEN) - continue; - int nTxSigOps = tx.GetSigOpCount(); - if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) - continue; - - // Timestamp limit - if (tx.nTime > GetAdjustedTime()) - continue; - - // ppcoin: simplify transaction fee - allow free = false - int64 nMinFee = tx.GetMinFee(nBlockSize, false, true); - - // Connecting shouldn't fail due to dependency on other memory pool transactions - // because we're already processing them in order of dependency - map mapTestPoolTmp(mapTestPool); - if (!tx.ConnectInputs(txdb, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, nFees, false, true, nMinFee)) - continue; - swap(mapTestPool, mapTestPoolTmp); - - // Added - pblock->vtx.push_back(tx); - nBlockSize += nTxSize; - nBlockSigOps += nTxSigOps; - - // Add transactions that depend on this one to the priority queue - uint256 hash = tx.GetHash(); - if (mapDependers.count(hash)) - { - BOOST_FOREACH(COrphan* porphan, mapDependers[hash]) - { - if (!porphan->setDependsOn.empty()) - { - porphan->setDependsOn.erase(hash); - if (porphan->setDependsOn.empty()) - mapPriority.insert(make_pair(-porphan->dPriority, porphan->ptx)); - } - } - } - } - } - if (pblock->IsProofOfWork()) - pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits); - - // Fill in header - pblock->hashPrevBlock = pindexPrev->GetBlockHash(); - pblock->hashMerkleRoot = pblock->BuildMerkleTree(); - pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); - pblock->nTime = max(pblock->GetBlockTime(), pblock->GetMaxTransactionTime()); - pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift); - pblock->nNonce = 0; - - return pblock.release(); -} - - -void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce) -{ - // Update nExtraNonce - static uint256 hashPrevBlock; - if (hashPrevBlock != pblock->hashPrevBlock) - { - nExtraNonce = 0; - hashPrevBlock = pblock->hashPrevBlock; - } - ++nExtraNonce; - pblock->vtx[0].vin[0].scriptSig = CScript() << pblock->nTime << CBigNum(nExtraNonce); - pblock->hashMerkleRoot = pblock->BuildMerkleTree(); -} - - -void FormatHashBuffers(CBlock* pblock, char* pmidstate, char* pdata, char* phash1) -{ - // - // Prebuild hash buffers - // - struct - { - struct unnamed2 - { - int nVersion; - uint256 hashPrevBlock; - uint256 hashMerkleRoot; - unsigned int nTime; - unsigned int nBits; - unsigned int nNonce; - } - block; - unsigned char pchPadding0[64]; - uint256 hash1; - unsigned char pchPadding1[64]; - } - tmp; - memset(&tmp, 0, sizeof(tmp)); - - tmp.block.nVersion = pblock->nVersion; - tmp.block.hashPrevBlock = pblock->hashPrevBlock; - tmp.block.hashMerkleRoot = pblock->hashMerkleRoot; - tmp.block.nTime = pblock->nTime; - tmp.block.nBits = pblock->nBits; - tmp.block.nNonce = pblock->nNonce; - - FormatHashBlocks(&tmp.block, sizeof(tmp.block)); - FormatHashBlocks(&tmp.hash1, sizeof(tmp.hash1)); - - // Byte swap all the input buffer - for (int i = 0; i < sizeof(tmp)/4; i++) - ((unsigned int*)&tmp)[i] = ByteReverse(((unsigned int*)&tmp)[i]); - - // Precalc the first half of the first hash, which stays constant - SHA256Transform(pmidstate, &tmp.block, pSHA256InitState); - - memcpy(pdata, &tmp.block, 128); - memcpy(phash1, &tmp.hash1, 64); -} - - -bool CheckWork(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey) -{ - uint256 hash = pblock->GetHash(); - uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256(); - - if (hash > hashTarget && pblock->IsProofOfWork()) - return error("BitcoinMiner : proof-of-work not meeting target"); - - //// debug print - printf("BitcoinMiner:\n"); - 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()); - - // Found a solution - CRITICAL_BLOCK(cs_main) - { - if (pblock->hashPrevBlock != hashBestChain) - return error("BitcoinMiner : generated block is stale"); - - // Remove key from key pool - reservekey.KeepKey(); - - // Track how many getdata requests this block gets - CRITICAL_BLOCK(wallet.cs_wallet) - wallet.mapRequestCount[pblock->GetHash()] = 0; - - // Process this block the same as if we had received it from another node - if (!ProcessBlock(NULL, pblock)) - return error("BitcoinMiner : ProcessBlock, block not accepted"); - } - - return true; -} - -void static ThreadBitcoinMiner(void* parg); - -void static BitcoinMiner(CWallet *pwallet) -{ - printf("BitcoinMiner started\n"); - SetThreadPriority(THREAD_PRIORITY_LOWEST); - - // Each thread has its own key and counter - CReserveKey reservekey(pwallet); - unsigned int nExtraNonce = 0; - - while (fGenerateBitcoins) - { - if (AffinityBugWorkaround(ThreadBitcoinMiner)) - return; - if (fShutdown) - return; - while (vNodes.empty() || IsInitialBlockDownload()) - { - Sleep(1000); - if (fShutdown) - return; - if (!fGenerateBitcoins) - return; - } - - - // - // Create new block - // - unsigned int nTransactionsUpdatedLast = nTransactionsUpdated; - CBlockIndex* pindexPrev = pindexBest; - - 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()) - { - // should be able to sign block - assert here for now - assert(pblock->SignBlock(*pwalletMain)); - 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()); - - - // - // Prebuild hash buffers - // - char pmidstatebuf[32+16]; char* pmidstate = alignup<16>(pmidstatebuf); - char pdatabuf[128+16]; char* pdata = alignup<16>(pdatabuf); - char phash1buf[64+16]; char* phash1 = alignup<16>(phash1buf); - - FormatHashBuffers(pblock.get(), pmidstate, pdata, phash1); - - unsigned int& nBlockTime = *(unsigned int*)(pdata + 64 + 4); - unsigned int& nBlockNonce = *(unsigned int*)(pdata + 64 + 12); - - - // - // Search - // - int64 nStart = GetTime(); - uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256(); - uint256 hashbuf[2]; - uint256& hash = *alignup<16>(hashbuf); - loop - { - unsigned int nHashesDone = 0; - unsigned int nNonceFound; - - // Crypto++ SHA-256 - nNonceFound = ScanHash_CryptoPP(pmidstate, pdata + 64, phash1, - (char*)&hash, nHashesDone); - - // Check if something found - if (nNonceFound != -1) - { - for (int i = 0; i < sizeof(hash)/4; i++) - ((unsigned int*)&hash)[i] = ByteReverse(((unsigned int*)&hash)[i]); - - if (hash <= hashTarget) - { - // Found a solution - pblock->nNonce = ByteReverse(nNonceFound); - assert(hash == pblock->GetHash()); - // should be able to sign block - assert here for now - assert(pblock->SignBlock(*pwalletMain)); - - SetThreadPriority(THREAD_PRIORITY_NORMAL); - CheckWork(pblock.get(), *pwalletMain, reservekey); - SetThreadPriority(THREAD_PRIORITY_LOWEST); - break; - } - } - - // Meter hashes/sec - static int64 nHashCounter; - if (nHPSTimerStart == 0) - { - nHPSTimerStart = GetTimeMillis(); - nHashCounter = 0; - } - else - nHashCounter += nHashesDone; - if (GetTimeMillis() - nHPSTimerStart > 4000) - { - static CCriticalSection cs; - CRITICAL_BLOCK(cs) - { - if (GetTimeMillis() - nHPSTimerStart > 4000) - { - dHashesPerSec = 1000.0 * nHashCounter / (GetTimeMillis() - nHPSTimerStart); - nHPSTimerStart = GetTimeMillis(); - nHashCounter = 0; - string strStatus = strprintf(" %.0f khash/s", dHashesPerSec/1000.0); - UIThreadCall(boost::bind(CalledSetStatusBar, strStatus, 0)); - static int64 nLogTime; - if (GetTime() - nLogTime > 30 * 60) - { - nLogTime = GetTime(); - printf("%s ", DateTimeStrFormat("%x %H:%M", GetTime()).c_str()); - printf("hashmeter %3d CPUs %6.0f khash/s\n", vnThreadsRunning[3], dHashesPerSec/1000.0); - } - } - } - } - - // Check for stop or if block needs to be rebuilt - if (fShutdown) - return; - if (!fGenerateBitcoins) - return; - if (fLimitProcessors && vnThreadsRunning[3] > nLimitProcessors) - return; - if (vNodes.empty()) - break; - if (nBlockNonce >= 0xffff0000) - break; - if (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60) - break; - if (pindexPrev != pindexBest) - break; - - // Update nTime every few seconds - pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); - pblock->nTime = max(pblock->GetBlockTime(), pblock->GetMaxTransactionTime()); - pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift); - nBlockTime = ByteReverse(pblock->nTime); - if (pblock->GetBlockTime() >= (int64)pblock->vtx[0].nTime + nMaxClockDrift) - break; // need to update coinbase timestamp - } - } -} - -void static ThreadBitcoinMiner(void* parg) -{ - CWallet* pwallet = (CWallet*)parg; - try - { - vnThreadsRunning[3]++; - BitcoinMiner(pwallet); - vnThreadsRunning[3]--; - } - catch (std::exception& e) { - vnThreadsRunning[3]--; - PrintException(&e, "ThreadBitcoinMiner()"); - } catch (...) { - vnThreadsRunning[3]--; - PrintException(NULL, "ThreadBitcoinMiner()"); - } - UIThreadCall(boost::bind(CalledSetStatusBar, "", 0)); - nHPSTimerStart = 0; - if (vnThreadsRunning[3] == 0) - dHashesPerSec = 0; - printf("ThreadBitcoinMiner exiting, %d threads remaining\n", vnThreadsRunning[3]); -} - - -void GenerateBitcoins(bool fGenerate, CWallet* pwallet) -{ - if (fGenerateBitcoins != fGenerate) - { - fGenerateBitcoins = fGenerate; - WriteSetting("fGenerateBitcoins", fGenerateBitcoins); - MainFrameRepaint(); - } - if (fGenerateBitcoins) - { - int nProcessors = boost::thread::hardware_concurrency(); - printf("%d processors\n", nProcessors); - if (nProcessors < 1) - nProcessors = 1; - if (fLimitProcessors && nProcessors > nLimitProcessors) - nProcessors = nLimitProcessors; - int nAddThreads = nProcessors - vnThreadsRunning[3]; - printf("Starting %d BitcoinMiner threads\n", nAddThreads); - for (int i = 0; i < nAddThreads; i++) - { - if (!CreateThread(ThreadBitcoinMiner, pwallet)) - printf("Error: CreateThread(ThreadBitcoinMiner) failed\n"); - Sleep(10); - } - } -} + CMainCleanup() {} + ~CMainCleanup() { + // block headers + std::map::iterator it1 = mapBlockIndex.begin(); + for (; it1 != mapBlockIndex.end(); it1++) + delete (*it1).second; + mapBlockIndex.clear(); + + // orphan blocks + std::map::iterator it2 = mapOrphanBlocks.begin(); + for (; it2 != mapOrphanBlocks.end(); it2++) + delete (*it2).second; + mapOrphanBlocks.clear(); + + // orphan transactions + } +} instance_of_cmaincleanup;