Merge branch '0.4.x' into 0.5.0.x
authorLuke Dashjr <luke-jr+git@utopios.org>
Wed, 21 Mar 2012 17:39:57 +0000 (13:39 -0400)
committerLuke Dashjr <luke-jr+git@utopios.org>
Wed, 21 Mar 2012 17:49:00 +0000 (13:49 -0400)
Conflicts:
src/main.cpp

1  2 
src/main.cpp
src/main.h
src/script.cpp

diff --cc src/main.cpp
@@@ -441,9 -424,22 +431,22 @@@ bool CTransaction::AcceptToMemoryPool(C
          {
              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");
++            return error("AcceptToMemoryPool() : transaction with out-of-bounds SigOpCount");
+         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");
@@@ -885,6 -881,118 +894,118 @@@ bool CTransaction::FetchInputs(CTxDB& t
      // 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 DoS(100, 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
          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());
 +                return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %d %d prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str()));
-             }
  
              // If prev is coinbase, check that it's matured
              if (txPrev.IsCoinBase())
                      if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile)
                          return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight);
  
-             // 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 (!(fBlock && (nBestHeight < Checkpoints::GetTotalBlocksEstimate())))
-                 // Verify signature
-                 if (!VerifySignature(txPrev, *this, i))
-                     return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()));
              // Check for conflicts (double-spend)
 +            // 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 (!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 error("ConnectInputs() : txin values out of range");
 +                return DoS(100, 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());
++            // 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 (!(fBlock && (nBestHeight < Checkpoints::GetTotalBlocksEstimate())))
++            {
++                // Verify signature
++                if (!VerifySignature(txPrev, *this, i, fStrictPayToScriptHash, 0))
++                {
++                    // only during transition phase for P2SH: do not invoke anti-DoS code for
++                    // potentially old clients relaying bad P2SH transactions
++                    if (fStrictPayToScriptHash && VerifySignature(txPrev, *this, i, false, 0))
++                        return error("ConnectInputs() : %s P2SH 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;
  
          // Tally transaction fees
          int64 nTxFee = nValueIn - GetValueOut();
          if (nTxFee < 0)
 -            return error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str());
 +            return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str()));
-         if (nTxFee < nMinFee)
-             return false;
          nFees += nTxFee;
          if (!MoneyRange(nFees))
 -            return error("ConnectInputs() : nFees out of range");
 +            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;
  }
  
@@@ -1104,15 -1159,42 +1187,42 @@@ bool CBlock::ConnectBlock(CTxDB& txdb, 
  
      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");
++            return DoS(100, 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");
++                    return DoS(100, 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)
      {
diff --cc src/main.h
Simple merge
diff --cc src/script.cpp
Simple merge