diff options
author | Luke Dashjr <luke-jr+git@utopios.org> | 2012-03-21 13:30:56 -0400 |
---|---|---|
committer | Luke Dashjr <luke-jr+git@utopios.org> | 2012-03-21 13:30:56 -0400 |
commit | cdc6b8d6a6ee4f0bfa721561c340cf8f6b81bc6a (patch) | |
tree | 9599899491f5f6e7d06a85931a5dd85231d25605 /src/main.cpp | |
parent | 0e6c6e3fd1ab971c652e48fa04bac097e44e76fe (diff) | |
parent | 2f2ac3fece6c7f576c5957f05f342f2830301b54 (diff) | |
download | bitcoin-cdc6b8d6a6ee4f0bfa721561c340cf8f6b81bc6a.tar.xz |
Merge branch 'bip16_0.4.x' into 0.4.x
Diffstat (limited to 'src/main.cpp')
-rw-r--r-- | src/main.cpp | 284 |
1 files changed, 202 insertions, 82 deletions
diff --git a/src/main.cpp b/src/main.cpp index ebf51ae146..759e514ca8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -374,15 +374,6 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi 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() : nonstandard transaction"); - // Rather not work on nonstandard transactions (unless -testnet) if (!fTestNet && !IsStandard()) return error("AcceptToMemoryPool() : nonstandard transaction type"); @@ -426,17 +417,29 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi if (fCheckInputs) { - // Check against previous transactions + MapPrevTx mapInputs; map<uint256, CTxIndex> mapUnused; - int64 nFees = 0; bool fInvalid = false; - if (!ConnectInputs(txdb, mapUnused, CDiskTxPos(1,1,1), pindexBest, nFees, false, false, 0, fInvalid)) + if (!FetchInputs(txdb, mapUnused, false, false, mapInputs, fInvalid)) { if (fInvalid) return error("AcceptToMemoryPool() : FetchInputs found invalid tx %s", hash.ToString().substr(0,10).c_str()); - return error("AcceptToMemoryPool() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str()); + if (pfMissingInputs) + *pfMissingInputs = true; + return error("AcceptToMemoryPool() : FetchInputs failed %s", hash.ToString().substr(0,10).c_str()); } + // 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() : nonstandard transaction"); + + int64 nFees = GetValueIn(mapInputs)-GetValueOut(); + // Don't accept it if it can't get into a block if (nFees < GetMinFee(1000, true, true)) return error("AcceptToMemoryPool() : not enough fees"); @@ -465,6 +468,13 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi dFreeCount += nSize; } } + + // Check against previous transactions + // This is done last to help prevent CPU exhaustion denial-of-service attacks. + if (!ConnectInputs(mapInputs, mapUnused, CDiskTxPos(1,1,1), pindexBest, false, false)) + { + return error("AcceptToMemoryPool() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str()); + } } // Store transaction in memory @@ -862,9 +872,8 @@ bool CTransaction::DisconnectInputs(CTxDB& txdb) } -bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPool, CDiskTxPos posThisTx, - CBlockIndex* pindexBlock, int64& nFees, bool fBlock, bool fMiner, int64 nMinFee, - bool& fInvalid) +bool CTransaction::FetchInputs(CTxDB& txdb, const map<uint256, CTxIndex>& 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) @@ -872,70 +881,143 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPoo // 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 (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 + CRITICAL_BLOCK(cs_mapTransactions) + { + if (!mapTransactions.count(prevout.hash)) + return error("FetchInputs() : %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("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's are valid: + for (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 error("FetchInputs() : %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 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 CTransaction::GetValueIn(const MapPrevTx& inputs) const +{ + if (IsCoinBase()) + return 0; + + int64 nResult = 0; + for (int i = 0; i < vin.size(); i++) + { + nResult += GetOutputFor(vin[i], inputs).nValue; + } + return nResult; + +} + +int CTransaction::GetP2SHSigOpCount(const MapPrevTx& inputs) const +{ + if (IsCoinBase()) + return 0; + + int nSigOps = 0; + for (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 CTransaction::ConnectInputs(MapPrevTx inputs, + map<uint256, CTxIndex>& mapTestPool, const CDiskTxPos& posThisTx, + const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, bool fStrictPayToScriptHash) +{ // 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; + int64 nFees = 0; for (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()) - { - // Revisit this if/when transaction replacement is implemented and allows - // adding inputs: - fInvalid = true; return error("ConnectInputs() : %s prevout.n out of range %d %d %d prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str()); - } // If prev is coinbase, check that it's matured if (txPrev.IsCoinBase()) - for (CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev) + for (const CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev) if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile) return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight); - // Verify signature - if (!VerifySignature(txPrev, *this, i)) - return error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()); - - // Check for conflicts + // Check for conflicts (double-spend) 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()); @@ -944,6 +1026,10 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPoo if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn)) return error("ConnectInputs() : txin values out of range"); + // Verify signature + if (!VerifySignature(txPrev, *this, i, fStrictPayToScriptHash, 0)) + return error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()); + // Mark outpoints as spent txindex.vSpent[prevout.n] = posThisTx; @@ -961,24 +1047,11 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPoo int64 nTxFee = nValueIn - GetValueOut(); if (nTxFee < 0) return error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str()); - if (nTxFee < nMinFee) - return false; nFees += nTxFee; if (!MoneyRange(nFees)) return 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; } @@ -1004,7 +1077,7 @@ bool CTransaction::ClientConnectInputs() return false; // Verify signature - if (!VerifySignature(txPrev, *this, i)) + if (!VerifySignature(txPrev, *this, i, true, 0)) return error("ConnectInputs() : VerifySignature failed"); ///// this is redundant with the mapNextTx stuff, not sure which I want to get rid of @@ -1077,20 +1150,51 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) return false; } + // P2SH didn't become active until Apr 1 2012 (Feb 15 on testnet) + int64 nEvalSwitchTime = fTestNet ? 1329264000 : 1333238400; + bool fStrictPayToScriptHash = (pindex->nTime >= nEvalSwitchTime); + //// issue here: it doesn't know the version unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - 1 + GetSizeOfCompactSize(vtx.size()); map<uint256, CTxIndex> mapQueuedChanges; int64 nFees = 0; + int nSigOps = 0; BOOST_FOREACH(CTransaction& tx, vtx) { + nSigOps += tx.GetSigOpCount(); + if (nSigOps > MAX_BLOCK_SIGOPS) + return error("ConnectBlock() : too many sigops"); + CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos); nTxPos += ::GetSerializeSize(tx, SER_DISK); bool fInvalid; - if (!tx.ConnectInputs(txdb, mapQueuedChanges, posThisTx, pindex, nFees, true, false, 0, fInvalid)) - return false; + MapPrevTx mapInputs; + if (!tx.IsCoinBase()) + { + if (!tx.FetchInputs(txdb, mapQueuedChanges, true, false, mapInputs, fInvalid)) + return false; + + 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 += tx.GetP2SHSigOpCount(mapInputs); + if (nSigOps > MAX_BLOCK_SIGOPS) + return error("ConnectBlock() : too many sigops"); + } + + nFees += tx.GetValueIn(mapInputs)-tx.GetValueOut(); + + if (!tx.ConnectInputs(mapInputs, mapQueuedChanges, posThisTx, pindex, true, false, fStrictPayToScriptHash)) + return false; + } + + mapQueuedChanges[tx.GetHash()] = CTxIndex(posThisTx, tx.vout.size()); } + // Write queued txindex changes for (map<uint256, CTxIndex>::iterator mi = mapQueuedChanges.begin(); mi != mapQueuedChanges.end(); ++mi) { @@ -1353,7 +1457,7 @@ bool CBlock::CheckBlock() const // Check that it's not full of nonstandard transactions if (GetSigOpCount() > MAX_BLOCK_SIGOPS) - return error("CheckBlock() : too many nonstandard transactions"); + return error("CheckBlock() : out-of-bounds SigOpCount"); // Check merkleroot if (hashMerkleRoot != BuildMerkleTree()) @@ -2863,6 +2967,8 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); if (nBlockSize + nTxSize >= MAX_BLOCK_SIZE_GEN) continue; + + // Legacy limits on sigOps: int nTxSigOps = tx.GetSigOpCount(); if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) continue; @@ -2875,14 +2981,28 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) // because we're already processing them in order of dependency map<uint256, CTxIndex> mapTestPoolTmp(mapTestPool); bool fInvalid; - if (!tx.ConnectInputs(txdb, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, nFees, false, true, nMinFee, fInvalid)) + MapPrevTx mapInputs; + if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs, fInvalid)) + continue; + + int64 nTxFees = tx.GetValueIn(mapInputs)-tx.GetValueOut(); + if (nTxFees < nMinFee) + continue; + + nTxSigOps += tx.GetP2SHSigOpCount(mapInputs); + if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) + continue; + + if (!tx.ConnectInputs(mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true)) continue; + mapTestPoolTmp[tx.GetHash()] = CTxIndex(CDiskTxPos(1,1,1), tx.vout.size()); swap(mapTestPool, mapTestPoolTmp); // Added pblock->vtx.push_back(tx); nBlockSize += nTxSize; nBlockSigOps += nTxSigOps; + nFees += nTxFees; // Add transactions that depend on this one to the priority queue uint256 hash = tx.GetHash(); |