From 97ba3b787c8b3cc61078191c6b8dac86c7a81689 Mon Sep 17 00:00:00 2001 From: Miguel Freitas Date: Tue, 16 Jul 2013 23:36:25 -0300 Subject: [PATCH] cleaning block/tx checking --- src/main.cpp | 421 +++------------------------------------------------ src/main.h | 7 - 2 files changed, 17 insertions(+), 411 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b6fee81c..18139af5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -724,35 +724,6 @@ bool CTxMemPool::accept(CValidationState &state, CTransaction &tx, bool fLimitFr return false; } - // Check for conflicts with in-memory transactions - CTransaction* ptxOld = NULL; - /* - for (unsigned int i = 0; i < tx.vin.size(); i++) - { - COutPoint outpoint = tx.vin[i].prevout; - if (mapNextTx.count(outpoint)) - { - // Disable replacement feature for now - return false; - - // Allow replacing with a newer version of the same transaction - if (i != 0) - return false; - ptxOld = mapNextTx[outpoint].ptx; - if (IsFinalTx(*ptxOld)) - return false; - if (!tx.IsNewerThan(*ptxOld)) - return false; - for (unsigned int i = 0; i < tx.vin.size(); i++) - { - COutPoint outpoint = tx.vin[i].prevout; - if (!mapNextTx.count(outpoint) || mapNextTx[outpoint].ptx != ptxOld) - return false; - } - break; - } - } -*/ { CCoinsView dummy; CCoinsViewCache view(dummy); @@ -763,25 +734,10 @@ bool CTxMemPool::accept(CValidationState &state, CTransaction &tx, bool fLimitFr view.SetBackend(viewMemPool); // do we already have it? + // [MF] TODO: use hash(tx.userName) if (view.HaveCoins(hash)) return false; - // do all inputs exist? - // Note that this does not check for the presence of actual outputs (see the next check for that), - // only helps filling in pfMissingInputs (to determine missing vs spent). - /* - BOOST_FOREACH(const CTxIn txin, tx.vin) { - if (!view.HaveCoins(txin.prevout.hash)) { - if (pfMissingInputs) - *pfMissingInputs = true; - return false; - } - } - */ - // are the actual inputs available? - if (!view.HaveInputs(tx)) - return state.Invalid(error("CTxMemPool::accept() : inputs already spent")); - // Bring the best block into scope view.GetBestBlock(); @@ -789,14 +745,6 @@ bool CTxMemPool::accept(CValidationState &state, CTransaction &tx, bool fLimitFr view.SetBackend(dummy); } - // Check for non-standard pay-to-script-hash in inputs - if (!TestNet() && !AreInputsStandard(tx, view)) - 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. - unsigned int nSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); // Continuously rate-limit free transactions @@ -821,30 +769,15 @@ bool CTxMemPool::accept(CValidationState &state, CTransaction &tx, bool fLimitFr 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 (!CheckInputs(tx, state, view, true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC)) - { - return error("CTxMemPool::accept() : ConnectInputs failed %s", hash.ToString().c_str()); - } } // Store transaction in memory { LOCK(cs); - if (ptxOld) - { - printf("CTxMemPool::accept() : replacing tx %s with new version\n", ptxOld->GetHash().ToString().c_str()); - remove(*ptxOld); - } addUnchecked(hash, tx); } ///// are we sure this is ok when loading transactions or restoring block txes - // If updated, erase old tx from wallet - if (ptxOld) - EraseFromWallets(ptxOld->GetHash()); SyncWithWallets(hash, tx, NULL, true); printf("CTxMemPool::accept() : accepted %s (poolsz %"PRIszu")\n", @@ -1428,24 +1361,6 @@ void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCach bool CCoinsViewCache::HaveInputs(const CTransaction& tx) { - if (!tx.IsSpamMessage()) { - /* [MF] - // first check whether information about the prevout hash is available - for (unsigned int i = 0; i < tx.vin.size(); i++) { - const COutPoint &prevout = tx.vin[i].prevout; - if (!HaveCoins(prevout.hash)) - return false; - } - - // then check whether the actual outputs are available - for (unsigned int i = 0; i < tx.vin.size(); i++) { - const COutPoint &prevout = tx.vin[i].prevout; - const CCoins &coins = GetCoins(prevout.hash); - if (!coins.IsAvailable(prevout.n)) - return false; - } - */ - } return true; } @@ -1466,90 +1381,6 @@ bool VerifySignature(const CCoins& txFrom, const CTransaction& txTo, unsigned in return true; } -bool CheckInputs(const CTransaction& tx, CValidationState &state, CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, std::vector *pvChecks) -{ - if (!tx.IsSpamMessage()) - { - /* [MF] - if (pvChecks) - pvChecks->reserve(tx.vin.size()); - - // This doesn't trigger the DoS code on purpose; if it did, it would make it easier - // for an attacker to attempt to split the network. - if (!inputs.HaveInputs(tx)) - return state.Invalid(error("CheckInputs() : %s inputs unavailable", tx.GetHash().ToString().c_str())); - - // While checking, GetBestBlock() refers to the parent block. - // This is also true for mempool checks. - int nSpendHeight = inputs.GetBestBlock()->nHeight + 1; - int64 nValueIn = 0; - int64 nFees = 0; - for (unsigned int i = 0; i < tx.vin.size(); i++) - { - const COutPoint &prevout = tx.vin[i].prevout; - const CCoins &coins = inputs.GetCoins(prevout.hash); - - // If prev is coinbase, check that it's matured - if (coins.IsCoinBase()) { - if (nSpendHeight - coins.nHeight < COINBASE_MATURITY) - return state.Invalid(error("CheckInputs() : tried to spend coinbase at depth %d", nSpendHeight - coins.nHeight)); - } - - // Check for negative or overflow input values - nValueIn += coins.vout[prevout.n].nValue; - if (!MoneyRange(coins.vout[prevout.n].nValue) || !MoneyRange(nValueIn)) - return state.DoS(100, error("CheckInputs() : txin values out of range")); - - } - - if (nValueIn < GetValueOut(tx)) - return state.DoS(100, error("CheckInputs() : %s value in < value out", tx.GetHash().ToString().c_str())); - - // Tally transaction fees - int64 nTxFee = nValueIn - GetValueOut(tx); - if (nTxFee < 0) - return state.DoS(100, error("CheckInputs() : %s nTxFee < 0", tx.GetHash().ToString().c_str())); - nFees += nTxFee; - if (!MoneyRange(nFees)) - return state.DoS(100, error("CheckInputs() : nFees out of range")); - - // 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. - - // Skip ECDSA signature verification when connecting blocks - // before the last block chain 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) { - for (unsigned int i = 0; i < tx.vin.size(); i++) { - const COutPoint &prevout = tx.vin[i].prevout; - const CCoins &coins = inputs.GetCoins(prevout.hash); - - // Verify signature - CScriptCheck check(coins, tx, i, flags, 0); - if (pvChecks) { - pvChecks->push_back(CScriptCheck()); - check.swap(pvChecks->back()); - } else if (!check()) { - if (flags & SCRIPT_VERIFY_STRICTENC) { - // For now, check whether the failure was caused by non-canonical - // encodings or not; if so, don't trigger DoS protection. - CScriptCheck check(coins, tx, i, flags & (~SCRIPT_VERIFY_STRICTENC), 0); - if (check()) - return state.Invalid(); - } - return state.DoS(100,false); - } - } - } - */ - } - - return true; -} - - - bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool* pfClean) { assert(pindex == view.GetBestBlock()); @@ -1683,81 +1514,23 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C return true; } - bool fScriptChecks = pindex->nHeight >= Checkpoints::GetTotalBlocksEstimate(); - // 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 = (!pindex->phashBlock) || // Enforce on CreateNewBlock invocations which don't have a hash. - !((pindex->nHeight==91842 && pindex->GetBlockHash() == uint256("0x00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")) || - (pindex->nHeight==91880 && pindex->GetBlockHash() == uint256("0x00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721"))); - if (fEnforceBIP30) { - for (unsigned int i = 0; i < block.vtx.size(); i++) { - uint256 hash = block.GetTxHash(i); - if (view.HaveCoins(hash) && !view.GetCoins(hash).IsPruned()) - return state.DoS(100, error("ConnectBlock() : tried to overwrite transaction")); - } + for (unsigned int i = 0; i < block.vtx.size(); i++) { + uint256 hash = block.GetTxHash(i); + if (view.HaveCoins(hash)) + return state.DoS(100, error("ConnectBlock() : tried to overwrite transaction")); } - // BIP16 didn't become active until Apr 1 2012 - int64 nBIP16SwitchTime = 1333238400; - bool fStrictPayToScriptHash = (pindex->nTime >= nBIP16SwitchTime); - - unsigned int flags = SCRIPT_VERIFY_NOCACHE | - (fStrictPayToScriptHash ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE); - CBlockUndo blockundo; - CCheckQueueControl control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : NULL); - int64 nStart = GetTimeMicros(); - int64 nFees = 0; - int nInputs = 0; - unsigned int nSigOps = 0; CDiskTxPos pos(pindex->GetBlockPos(), GetSizeOfCompactSize(block.vtx.size())); std::vector > vPos; vPos.reserve(block.vtx.size()); for (unsigned int i = 0; i < block.vtx.size(); i++) { const CTransaction &tx = block.vtx[i]; - /* [MF] - nInputs += tx.vin.size(); - nSigOps += GetLegacySigOpCount(tx); - if (nSigOps > MAX_BLOCK_SIGOPS) - return state.DoS(100, error("ConnectBlock() : too many sigops")); - if (!tx.IsCoinBase()) - { - if (!view.HaveInputs(tx)) - return state.DoS(100, error("ConnectBlock() : inputs missing/spent")); - - if (fStrictPayToScriptHash) - { - // 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 += GetP2SHSigOpCount(tx, view); - if (nSigOps > MAX_BLOCK_SIGOPS) - return state.DoS(100, error("ConnectBlock() : too many sigops")); - } - - nFees += view.GetValueIn(tx)-GetValueOut(tx); - - std::vector vChecks; - if (!CheckInputs(tx, state, view, fScriptChecks, flags, nScriptCheckThreads ? &vChecks : NULL)) - return false; - control.Add(vChecks); - } -*/ CTxUndo txundo; UpdateCoins(tx, state, view, txundo, pindex->nHeight, block.GetTxHash(i)); if (!tx.IsSpamMessage()) @@ -1768,16 +1541,7 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C } int64 nTime = GetTimeMicros() - nStart; if (fBenchmark) - printf("- Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin)\n", (unsigned)block.vtx.size(), 0.001 * nTime, 0.001 * nTime / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * nTime / (nInputs-1)); - - if (GetValueOut(block.vtx[0]) > GetBlockValue(pindex->nHeight, nFees)) - return state.DoS(100, error("ConnectBlock() : coinbase pays too much (actual=%"PRI64d" vs limit=%"PRI64d")", GetValueOut(block.vtx[0]), GetBlockValue(pindex->nHeight, nFees))); - - if (!control.Wait()) - return state.DoS(100, false); - int64 nTime2 = GetTimeMicros() - nStart; - if (fBenchmark) - printf("- Verify %u txins: %.2fms (%.3fms/txin)\n", nInputs - 1, 0.001 * nTime2, nInputs <= 1 ? 0 : 0.001 * nTime2 / (nInputs-1)); + printf("- Connect %u transactions: %.2fms (%.3fms/tx)\n", (unsigned)block.vtx.size(), 0.001 * nTime, 0.001 * nTime / block.vtx.size()); if (fJustCheck) return true; @@ -2180,14 +1944,6 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo if (uniqueTx.size() != block.vtx.size()) return state.DoS(100, error("CheckBlock() : duplicate transaction")); - unsigned int nSigOps = 0; - BOOST_FOREACH(const CTransaction& tx, block.vtx) - { - nSigOps += GetLegacySigOpCount(tx); - } - if (nSigOps > MAX_BLOCK_SIGOPS) - return state.DoS(100, error("CheckBlock() : out-of-bounds SigOpCount")); - // Check merkle root if (fCheckMerkleRoot && block.hashMerkleRoot != block.BuildMerkleTree()) return state.DoS(100, error("CheckBlock() : hashMerkleRoot mismatch")); @@ -4186,6 +3942,8 @@ CBlockTemplate* CreateNewBlock(CReserveKey& reservekey) return NULL; txNew.pubKey << pubkey << OP_CHECKSIG; + // [MF] TODO: fix nNonce + // Add our coinbase tx as first transaction pblock->vtx.push_back(txNew); pblocktemplate->vTxFees.push_back(-1); // updated at end @@ -4207,7 +3965,6 @@ CBlockTemplate* CreateNewBlock(CReserveKey& reservekey) nBlockMinSize = std::min(nBlockMaxSize, nBlockMinSize); // Collect memory pool transactions into the block - int64 nFees = 0; { LOCK2(cs_main, mempool.cs); CBlockIndex* pindexPrev = pindexBest; @@ -4216,193 +3973,49 @@ CBlockTemplate* CreateNewBlock(CReserveKey& reservekey) // Priority order to process transactions list vOrphan; // list memory doesn't move map > mapDependers; - bool fPrintPriority = GetBoolArg("-printpriority", false); - // This vector will be sorted into a priority queue: - vector vecPriority; - vecPriority.reserve(mempool.mapTx.size()); + // Collect transactions into block + uint64 nBlockSize = 1000; + uint64 nBlockTx = 0; + for (map::iterator mi = mempool.mapTx.begin(); mi != mempool.mapTx.end(); ++mi) { CTransaction& tx = (*mi).second; if (tx.IsSpamMessage()) continue; - COrphan* porphan = NULL; - double dPriority = 0; - int64 nTotalIn = 0; - bool fMissingInputs = false; - /* [MF] - BOOST_FOREACH(const CTxIn& txin, tx.vin) - { - // Read prev transaction - if (!view.HaveCoins(txin.prevout.hash)) - { - // This should never happen; all transactions in the memory - // pool should connect to either transactions in the chain - // or other transactions in the memory pool. - if (!mempool.mapTx.count(txin.prevout.hash)) - { - printf("ERROR: mempool transaction missing input\n"); - if (fDebug) assert("mempool transaction missing input" == 0); - fMissingInputs = true; - if (porphan) - vOrphan.pop_back(); - break; - } - - // 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); - nTotalIn += mempool.mapTx[txin.prevout.hash].vout[txin.prevout.n].nValue; - continue; - } - const CCoins &coins = view.GetCoins(txin.prevout.hash); - - int64 nValueIn = coins.vout[txin.prevout.n].nValue; - nTotalIn += nValueIn; - - int nConf = pindexPrev->nHeight - coins.nHeight + 1; - - dPriority += (double)nValueIn * nConf; + // This should never happen; all transactions in the memory are new + if( view.HaveCoins(tx.GetHash()) ) { + printf("ERROR: mempool transaction already exists\n"); + if (fDebug) assert("mempool transaction already exists" == 0); } - */ - if (fMissingInputs) continue; - - // Priority is sum(valuein * age) / txsize - unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); - dPriority /= nTxSize; - - // This is a more accurate fee-per-kilobyte than is used by the client code, because the - // client code rounds up the size to the nearest 1K. That's good, because it gives an - // incentive to create smaller transactions. - double dFeePerKb = double(nTotalIn-GetValueOut(tx)) / (double(nTxSize)/1000.0); - - if (porphan) - { - porphan->dPriority = dPriority; - porphan->dFeePerKb = dFeePerKb; - } - else - vecPriority.push_back(TxPriority(dPriority, dFeePerKb, &(*mi).second)); - } - - // Collect transactions into block - uint64 nBlockSize = 1000; - uint64 nBlockTx = 0; - int nBlockSigOps = 100; - bool fSortedByFee = (nBlockPrioritySize <= 0); - - TxPriorityCompare comparer(fSortedByFee); - std::make_heap(vecPriority.begin(), vecPriority.end(), comparer); - - while (!vecPriority.empty()) - { - // Take highest priority transaction off the priority queue: - double dPriority = vecPriority.front().get<0>(); - double dFeePerKb = vecPriority.front().get<1>(); - CTransaction& tx = *(vecPriority.front().get<2>()); - - std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer); - vecPriority.pop_back(); // Size limits unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); if (nBlockSize + nTxSize >= nBlockMaxSize) continue; - // Legacy limits on sigOps: - unsigned int nTxSigOps = GetLegacySigOpCount(tx); - if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) - continue; - - // Skip free transactions if we're past the minimum block size: - if (fSortedByFee && (nBlockSize + nTxSize >= nBlockMinSize)) - continue; - - // Prioritize by fee once past the priority size or we run out of high-priority - // transactions: - if (!fSortedByFee && - ((nBlockSize + nTxSize >= nBlockPrioritySize) || !AllowFree(dPriority))) - { - fSortedByFee = true; - comparer = TxPriorityCompare(fSortedByFee); - std::make_heap(vecPriority.begin(), vecPriority.end(), comparer); - } - - if (!view.HaveInputs(tx)) - continue; - - int64 nTxFees = view.GetValueIn(tx)-GetValueOut(tx); - - nTxSigOps += GetP2SHSigOpCount(tx, view); - if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) - continue; - CValidationState state; - if (!CheckInputs(tx, state, view, true, SCRIPT_VERIFY_P2SH)) - continue; - CTxUndo txundo; uint256 hash = tx.GetHash(); UpdateCoins(tx, state, view, txundo, pindexPrev->nHeight+1, hash); // Added pblock->vtx.push_back(tx); - pblocktemplate->vTxFees.push_back(nTxFees); - pblocktemplate->vTxSigOps.push_back(nTxSigOps); nBlockSize += nTxSize; ++nBlockTx; - nBlockSigOps += nTxSigOps; - nFees += nTxFees; - - if (fPrintPriority) - { - printf("priority %.1f feeperkb %.1f txid %s\n", - dPriority, dFeePerKb, tx.GetHash().ToString().c_str()); - } - - // Add transactions that depend on this one to the priority queue - if (mapDependers.count(hash)) - { - BOOST_FOREACH(COrphan* porphan, mapDependers[hash]) - { - if (!porphan->setDependsOn.empty()) - { - porphan->setDependsOn.erase(hash); - if (porphan->setDependsOn.empty()) - { - vecPriority.push_back(TxPriority(porphan->dPriority, porphan->dFeePerKb, porphan->ptx)); - std::push_heap(vecPriority.begin(), vecPriority.end(), comparer); - } - } - } - } } nLastBlockTx = nBlockTx; nLastBlockSize = nBlockSize; printf("CreateNewBlock(): total size %"PRI64u"\n", nBlockSize); - /* [MF] - pblock->vtx[0].vout[0].nValue = GetBlockValue(pindexPrev->nHeight+1, nFees); - */ - pblocktemplate->vTxFees[0] = -nFees; - // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash(); + pblock->nHeight = pindexPrev->nHeight + 1; UpdateTime(*pblock, pindexPrev); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock); pblock->nNonce = 0; - /* [MF] - pblock->vtx[0].vin[0].scriptSig = CScript() << OP_0 << OP_0; - */ - pblocktemplate->vTxSigOps[0] = GetLegacySigOpCount(pblock->vtx[0]); CBlockIndex indexDummy(*pblock); indexDummy.pprev = pindexPrev; diff --git a/src/main.h b/src/main.h index ae13a1a1..9b315050 100644 --- a/src/main.h +++ b/src/main.h @@ -306,13 +306,6 @@ inline bool AllowFree(double dPriority) return dPriority > COIN * 144 / 250; } -// Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts) -// This does not modify the UTXO set. If pvChecks is not NULL, script checks are pushed onto it -// instead of being performed inline. -bool CheckInputs(const CTransaction& tx, CValidationState &state, CCoinsViewCache &view, bool fScriptChecks = true, - unsigned int flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, - std::vector *pvChecks = NULL); - // Apply the effects of this transaction on the UTXO set represented by view bool UpdateCoins(const CTransaction& tx, CCoinsViewCache &view, CTxUndo &txundo, int nHeight, const uint256 &txhash);