Transaction/Block denial-of-service detection/response

This commit is contained in:
Gavin Andresen 2011-09-06 16:59:38 -04:00
parent 15f3ad4dbd
commit 3e52aaf212
2 changed files with 41 additions and 29 deletions

View File

@ -296,24 +296,24 @@ bool CTransaction::CheckTransaction() const
{ {
// Basic checks that don't depend on any context // Basic checks that don't depend on any context
if (vin.empty()) if (vin.empty())
return error("CTransaction::CheckTransaction() : vin empty"); return DoS(10, error("CTransaction::CheckTransaction() : vin empty"));
if (vout.empty()) if (vout.empty())
return error("CTransaction::CheckTransaction() : vout empty"); return DoS(10, error("CTransaction::CheckTransaction() : vout empty"));
// Size limits // Size limits
if (::GetSerializeSize(*this, SER_NETWORK) > MAX_BLOCK_SIZE) if (::GetSerializeSize(*this, SER_NETWORK) > MAX_BLOCK_SIZE)
return error("CTransaction::CheckTransaction() : size limits failed"); return DoS(100, error("CTransaction::CheckTransaction() : size limits failed"));
// Check for negative or overflow output values // Check for negative or overflow output values
int64 nValueOut = 0; int64 nValueOut = 0;
BOOST_FOREACH(const CTxOut& txout, vout) BOOST_FOREACH(const CTxOut& txout, vout)
{ {
if (txout.nValue < 0) if (txout.nValue < 0)
return error("CTransaction::CheckTransaction() : txout.nValue negative"); return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue negative"));
if (txout.nValue > MAX_MONEY) if (txout.nValue > MAX_MONEY)
return error("CTransaction::CheckTransaction() : txout.nValue too high"); return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue too high"));
nValueOut += txout.nValue; nValueOut += txout.nValue;
if (!MoneyRange(nValueOut)) if (!MoneyRange(nValueOut))
return error("CTransaction::CheckTransaction() : txout total out of range"); return DoS(100, error("CTransaction::CheckTransaction() : txout total out of range"));
} }
// Check for duplicate inputs // Check for duplicate inputs
@ -328,13 +328,13 @@ bool CTransaction::CheckTransaction() const
if (IsCoinBase()) if (IsCoinBase())
{ {
if (vin[0].scriptSig.size() < 2 || vin[0].scriptSig.size() > 100) if (vin[0].scriptSig.size() < 2 || vin[0].scriptSig.size() > 100)
return error("CTransaction::CheckTransaction() : coinbase script size"); return DoS(100, error("CTransaction::CheckTransaction() : coinbase script size"));
} }
else else
{ {
BOOST_FOREACH(const CTxIn& txin, vin) BOOST_FOREACH(const CTxIn& txin, vin)
if (txin.prevout.IsNull()) if (txin.prevout.IsNull())
return error("CTransaction::CheckTransaction() : prevout is null"); return DoS(10, error("CTransaction::CheckTransaction() : prevout is null"));
} }
return true; return true;
@ -350,7 +350,7 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi
// Coinbase is only valid in a block, not as a loose transaction // Coinbase is only valid in a block, not as a loose transaction
if (IsCoinBase()) if (IsCoinBase())
return error("AcceptToMemoryPool() : coinbase as individual tx"); return DoS(100, error("AcceptToMemoryPool() : coinbase as individual tx"));
// To help v0.1.5 clients who would see it as a negative number // To help v0.1.5 clients who would see it as a negative number
if ((int64)nLockTime > INT_MAX) if ((int64)nLockTime > INT_MAX)
@ -363,7 +363,7 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi
// 34 bytes because a TxOut is: // 34 bytes because a TxOut is:
// 20-byte address + 8 byte bitcoin amount + 5 bytes of ops + 1 byte script length // 20-byte address + 8 byte bitcoin amount + 5 bytes of ops + 1 byte script length
if (GetSigOpCount() > nSize / 34 || nSize < 100) if (GetSigOpCount() > nSize / 34 || nSize < 100)
return error("AcceptToMemoryPool() : nonstandard transaction"); return DoS(10, error("AcceptToMemoryPool() : transaction with out-of-bounds SigOpCount"));
// Rather not work on nonstandard transactions (unless -testnet) // Rather not work on nonstandard transactions (unless -testnet)
if (!fTestNet && !IsStandard()) if (!fTestNet && !IsStandard())
@ -848,26 +848,28 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPoo
} }
if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size()) if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size())
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 prev is coinbase, check that it's matured
if (txPrev.IsCoinBase()) if (txPrev.IsCoinBase())
for (CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev) for (CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev)
if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile) if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile)
return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight); return DoS(10, error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight));
// Verify signature // Verify signature
if (!VerifySignature(txPrev, *this, i)) if (!VerifySignature(txPrev, *this, i))
return error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()); return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()));
// Check for conflicts // 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()) 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()); 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 // Check for negative or overflow input values
nValueIn += txPrev.vout[prevout.n].nValue; nValueIn += txPrev.vout[prevout.n].nValue;
if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn)) 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"));
// Mark outpoints as spent // Mark outpoints as spent
txindex.vSpent[prevout.n] = posThisTx; txindex.vSpent[prevout.n] = posThisTx;
@ -880,17 +882,17 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPoo
} }
if (nValueIn < GetValueOut()) if (nValueIn < GetValueOut())
return error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str()); return DoS(100, error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str()));
// Tally transaction fees // Tally transaction fees
int64 nTxFee = nValueIn - GetValueOut(); int64 nTxFee = nValueIn - GetValueOut();
if (nTxFee < 0) 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) if (nTxFee < nMinFee)
return false; return false;
nFees += nTxFee; nFees += nTxFee;
if (!MoneyRange(nFees)) if (!MoneyRange(nFees))
return error("ConnectInputs() : nFees out of range"); return DoS(100, error("ConnectInputs() : nFees out of range"));
} }
if (fBlock) if (fBlock)
@ -1233,11 +1235,11 @@ bool CBlock::CheckBlock() const
// Size limits // 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) > MAX_BLOCK_SIZE)
return error("CheckBlock() : size limits failed"); return DoS(100, error("CheckBlock() : size limits failed"));
// Check proof of work matches claimed amount // Check proof of work matches claimed amount
if (!CheckProofOfWork(GetHash(), nBits)) if (!CheckProofOfWork(GetHash(), nBits))
return error("CheckBlock() : proof of work failed"); return DoS(50, error("CheckBlock() : proof of work failed"));
// Check timestamp // Check timestamp
if (GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60) if (GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60)
@ -1245,23 +1247,23 @@ bool CBlock::CheckBlock() const
// First transaction must be coinbase, the rest must not be // First transaction must be coinbase, the rest must not be
if (vtx.empty() || !vtx[0].IsCoinBase()) if (vtx.empty() || !vtx[0].IsCoinBase())
return error("CheckBlock() : first tx is not coinbase"); return DoS(100, error("CheckBlock() : first tx is not coinbase"));
for (int i = 1; i < vtx.size(); i++) for (int i = 1; i < vtx.size(); i++)
if (vtx[i].IsCoinBase()) if (vtx[i].IsCoinBase())
return error("CheckBlock() : more than one coinbase"); return DoS(100, error("CheckBlock() : more than one coinbase"));
// Check transactions // Check transactions
BOOST_FOREACH(const CTransaction& tx, vtx) BOOST_FOREACH(const CTransaction& tx, vtx)
if (!tx.CheckTransaction()) if (!tx.CheckTransaction())
return error("CheckBlock() : CheckTransaction failed"); return DoS(tx.nDoS, error("CheckBlock() : CheckTransaction failed"));
// Check that it's not full of nonstandard transactions // Check that it's not full of nonstandard transactions
if (GetSigOpCount() > MAX_BLOCK_SIGOPS) if (GetSigOpCount() > MAX_BLOCK_SIGOPS)
return error("CheckBlock() : too many nonstandard transactions"); return DoS(100, error("CheckBlock() : out-of-bounds SigOpCount"));
// Check merkleroot // Check merkleroot
if (hashMerkleRoot != BuildMerkleTree()) if (hashMerkleRoot != BuildMerkleTree())
return error("CheckBlock() : hashMerkleRoot mismatch"); return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch"));
return true; return true;
} }
@ -1276,13 +1278,13 @@ bool CBlock::AcceptBlock()
// Get prev block index // Get prev block index
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashPrevBlock); map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashPrevBlock);
if (mi == mapBlockIndex.end()) if (mi == mapBlockIndex.end())
return error("AcceptBlock() : prev block not found"); return DoS(10, error("AcceptBlock() : prev block not found"));
CBlockIndex* pindexPrev = (*mi).second; CBlockIndex* pindexPrev = (*mi).second;
int nHeight = pindexPrev->nHeight+1; int nHeight = pindexPrev->nHeight+1;
// Check proof of work // Check proof of work
if (nBits != GetNextWorkRequired(pindexPrev)) if (nBits != GetNextWorkRequired(pindexPrev))
return error("AcceptBlock() : incorrect proof of work"); return DoS(100, error("AcceptBlock() : incorrect proof of work"));
// Check timestamp against prev // Check timestamp against prev
if (GetBlockTime() <= pindexPrev->GetMedianTimePast()) if (GetBlockTime() <= pindexPrev->GetMedianTimePast())
@ -1291,7 +1293,7 @@ bool CBlock::AcceptBlock()
// Check that all transactions are finalized // Check that all transactions are finalized
BOOST_FOREACH(const CTransaction& tx, vtx) BOOST_FOREACH(const CTransaction& tx, vtx)
if (!tx.IsFinal(nHeight, GetBlockTime())) if (!tx.IsFinal(nHeight, GetBlockTime()))
return error("AcceptBlock() : contains a non-final transaction"); return DoS(10, error("AcceptBlock() : contains a non-final transaction"));
// Check that the block chain matches the known block chain up to a checkpoint // Check that the block chain matches the known block chain up to a checkpoint
if (!fTestNet) if (!fTestNet)
@ -1304,7 +1306,7 @@ bool CBlock::AcceptBlock()
(nHeight == 118000 && hash != uint256("0x000000000000774a7f8a7a12dc906ddb9e17e75d684f15e00f8767f9e8f36553")) || (nHeight == 118000 && hash != uint256("0x000000000000774a7f8a7a12dc906ddb9e17e75d684f15e00f8767f9e8f36553")) ||
(nHeight == 134444 && hash != uint256("0x00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe")) || (nHeight == 134444 && hash != uint256("0x00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe")) ||
(nHeight == 140700 && hash != uint256("0x000000000000033b512028abb90e1626d8b346fd0ed598ac0a3c371138dce2bd"))) (nHeight == 140700 && hash != uint256("0x000000000000033b512028abb90e1626d8b346fd0ed598ac0a3c371138dce2bd")))
return error("AcceptBlock() : rejected by checkpoint lockin at %d", nHeight); return DoS(100, error("AcceptBlock() : rejected by checkpoint lockin at %d", nHeight));
// Write block to history file // Write block to history file
if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK))) if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK)))
@ -2126,6 +2128,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
printf("storing orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str()); printf("storing orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
AddOrphanTx(vMsg); AddOrphanTx(vMsg);
} }
if (tx.nDoS) pfrom->Misbehaving(tx.nDoS);
} }
@ -2142,6 +2145,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
if (ProcessBlock(pfrom, &block)) if (ProcessBlock(pfrom, &block))
mapAlreadyAskedFor.erase(inv); mapAlreadyAskedFor.erase(inv);
if (block.nDoS) pfrom->Misbehaving(block.nDoS);
} }

View File

@ -399,6 +399,9 @@ public:
std::vector<CTxOut> vout; std::vector<CTxOut> vout;
unsigned int nLockTime; unsigned int nLockTime;
// Denial-of-service detection:
mutable int nDoS;
bool DoS(int nDoSIn, bool fIn) const { nDoS += nDoSIn; return fIn; }
CTransaction() CTransaction()
{ {
@ -420,6 +423,7 @@ public:
vin.clear(); vin.clear();
vout.clear(); vout.clear();
nLockTime = 0; nLockTime = 0;
nDoS = 0; // Denial-of-service prevention
} }
bool IsNull() const bool IsNull() const
@ -786,6 +790,9 @@ public:
// memory only // memory only
mutable std::vector<uint256> vMerkleTree; mutable std::vector<uint256> vMerkleTree;
// Denial-of-service detection:
mutable int nDoS;
bool DoS(int nDoSIn, bool fIn) const { nDoS += nDoSIn; return fIn; }
CBlock() CBlock()
{ {
@ -819,6 +826,7 @@ public:
nNonce = 0; nNonce = 0;
vtx.clear(); vtx.clear();
vMerkleTree.clear(); vMerkleTree.clear();
nDoS = 0;
} }
bool IsNull() const bool IsNull() const