New reject p2p message

This commit is contained in:
Gavin Andresen 2013-10-28 16:36:11 +10:00
parent 17faf56262
commit 358ce2664d
2 changed files with 146 additions and 50 deletions

View File

@ -18,6 +18,7 @@
#include "util.h"
#include <inttypes.h>
#include <sstream>
#include <stdint.h>
#include <boost/algorithm/string/replace.hpp>
@ -667,24 +668,30 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
{
// Basic checks that don't depend on any context
if (tx.vin.empty())
return state.DoS(10, error("CheckTransaction() : vin empty"));
return state.DoS(10, error("CheckTransaction() : vin empty"),
REJECT_INVALID, "vin empty");
if (tx.vout.empty())
return state.DoS(10, error("CheckTransaction() : vout empty"));
return state.DoS(10, error("CheckTransaction() : vout empty"),
REJECT_INVALID, "vout empty");
// Size limits
if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
return state.DoS(100, error("CTransaction::CheckTransaction() : size limits failed"));
return state.DoS(100, error("CTransaction::CheckTransaction() : size limits failed"),
REJECT_INVALID, "oversize");
// Check for negative or overflow output values
int64_t nValueOut = 0;
BOOST_FOREACH(const CTxOut& txout, tx.vout)
{
if (txout.nValue < 0)
return state.DoS(100, error("CheckTransaction() : txout.nValue negative"));
return state.DoS(100, error("CheckTransaction() : txout.nValue negative"),
REJECT_INVALID, "vout negative");
if (txout.nValue > MAX_MONEY)
return state.DoS(100, error("CheckTransaction() : txout.nValue too high"));
return state.DoS(100, error("CheckTransaction() : txout.nValue too high"),
REJECT_INVALID, "vout too large");
nValueOut += txout.nValue;
if (!MoneyRange(nValueOut))
return state.DoS(100, error("CTransaction::CheckTransaction() : txout total out of range"));
return state.DoS(100, error("CTransaction::CheckTransaction() : txout total out of range"),
REJECT_INVALID, "txout total too large");
}
// Check for duplicate inputs
@ -692,20 +699,23 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
if (vInOutPoints.count(txin.prevout))
return state.DoS(100, error("CTransaction::CheckTransaction() : duplicate inputs"));
return state.DoS(100, error("CTransaction::CheckTransaction() : duplicate inputs"),
REJECT_INVALID, "duplicate inputs");
vInOutPoints.insert(txin.prevout);
}
if (tx.IsCoinBase())
{
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
return state.DoS(100, error("CheckTransaction() : coinbase script size"));
return state.DoS(100, error("CheckTransaction() : coinbase script size"),
REJECT_INVALID, "coinbase script too large");
}
else
{
BOOST_FOREACH(const CTxIn& txin, tx.vin)
if (txin.prevout.IsNull())
return state.DoS(10, error("CheckTransaction() : prevout is null"));
return state.DoS(10, error("CheckTransaction() : prevout is null"),
REJECT_INVALID, "prevout null");
}
return true;
@ -758,13 +768,15 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// Coinbase is only valid in a block, not as a loose transaction
if (tx.IsCoinBase())
return state.DoS(100, error("AcceptToMemoryPool: : coinbase as individual tx"));
return state.DoS(100, error("AcceptToMemoryPool: : coinbase as individual tx"),
REJECT_INVALID, "coinbase");
// Rather not work on nonstandard transactions (unless -testnet/-regtest)
string reason;
if (Params().NetworkID() == CChainParams::MAIN && !IsStandardTx(tx, reason))
return error("AcceptToMemoryPool: : nonstandard transaction: %s",
reason.c_str());
return state.DoS(0,
error("AcceptToMemoryPool : nonstandard transaction: %s", reason.c_str()),
REJECT_NONSTANDARD, reason);
// is it already in the memory pool?
uint256 hash = tx.GetHash();
@ -828,7 +840,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// are the actual inputs available?
if (!view.HaveInputs(tx))
return state.Invalid(error("AcceptToMemoryPool: : inputs already spent"));
return state.Invalid(error("AcceptToMemoryPool : inputs already spent"),
REJECT_DUPLICATE, "inputs spent");
// Bring the best block into scope
view.GetBestBlock();
@ -851,9 +864,9 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// Don't accept it if it can't get into a block
int64_t txMinFee = GetMinFee(tx, true, GMF_RELAY);
if (fLimitFree && nFees < txMinFee)
return error("AcceptToMemoryPool: : not enough fees %s, %"PRId64" < %"PRId64,
hash.ToString().c_str(),
nFees, txMinFee);
return state.DoS(0, error("AcceptToMemoryPool : not enough fees %s, %"PRId64" < %"PRId64,
hash.ToString().c_str(), nFees, txMinFee),
REJECT_INSUFFICIENTFEE, "insufficient fee");
// Continuously rate-limit free transactions
// This mitigates 'penny-flooding' -- sending thousands of free transactions just to
@ -873,7 +886,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// -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)
return error("AcceptToMemoryPool: : free transaction rejected by rate limiter");
return state.DoS(0, error("AcceptToMemoryPool : free transaction rejected by rate limiter"),
REJECT_INSUFFICIENTFEE, "insufficient priority");
LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize);
dFreeCount += nSize;
}
@ -1509,26 +1523,32 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, CCoinsViewCach
// 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));
return state.Invalid(
error("CheckInputs() : tried to spend coinbase at depth %d", nSpendHeight - coins.nHeight),
REJECT_INVALID, "premature spend of coinbase");
}
// 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"));
return state.DoS(100, error("CheckInputs() : txin values out of range"),
REJECT_INVALID, "input values out of range");
}
if (nValueIn < GetValueOut(tx))
return state.DoS(100, error("CheckInputs() : %s value in < value out", tx.GetHash().ToString().c_str()));
return state.DoS(100, error("CheckInputs() : %s value in < value out", tx.GetHash().ToString().c_str()),
REJECT_INVALID, "in < out");
// Tally transaction fees
int64_t nTxFee = nValueIn - GetValueOut(tx);
if (nTxFee < 0)
return state.DoS(100, error("CheckInputs() : %s nTxFee < 0", tx.GetHash().ToString().c_str()));
return state.DoS(100, error("CheckInputs() : %s nTxFee < 0", tx.GetHash().ToString().c_str()),
REJECT_INVALID, "fee < 0");
nFees += nTxFee;
if (!MoneyRange(nFees))
return state.DoS(100, error("CheckInputs() : nFees out of range"));
return state.DoS(100, error("CheckInputs() : nFees out of range"),
REJECT_INVALID, "fee out of range");
// The first loop above does all the inexpensive checks.
// Only if ALL inputs pass do we perform expensive ECDSA signature checks.
@ -1553,9 +1573,9 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, CCoinsViewCach
// 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.Invalid(false, REJECT_NONSTANDARD, "non-canonical");
}
return state.DoS(100,false);
return state.DoS(100,false, REJECT_NONSTANDARD, "non-canonical");
}
}
}
@ -1723,7 +1743,8 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C
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"));
return state.DoS(100, error("ConnectBlock() : tried to overwrite transaction"),
REJECT_INVALID, "BIP30");
}
}
@ -1752,12 +1773,14 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C
nInputs += tx.vin.size();
nSigOps += GetLegacySigOpCount(tx);
if (nSigOps > MAX_BLOCK_SIGOPS)
return state.DoS(100, error("ConnectBlock() : too many sigops"));
return state.DoS(100, error("ConnectBlock() : too many sigops"),
REJECT_INVALID, "too many sigops");
if (!tx.IsCoinBase())
{
if (!view.HaveInputs(tx))
return state.DoS(100, error("ConnectBlock() : inputs missing/spent"));
return state.DoS(100, error("ConnectBlock() : inputs missing/spent"),
REJECT_INVALID, "inputs missing/spent");
if (fStrictPayToScriptHash)
{
@ -1766,7 +1789,8 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C
// an incredibly-expensive-to-validate block.
nSigOps += GetP2SHSigOpCount(tx, view);
if (nSigOps > MAX_BLOCK_SIGOPS)
return state.DoS(100, error("ConnectBlock() : too many sigops"));
return state.DoS(100, error("ConnectBlock() : too many sigops"),
REJECT_INVALID, "too many sigops");
}
nFees += view.GetValueIn(tx)-GetValueOut(tx);
@ -1790,7 +1814,10 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C
LogPrintf("- 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=%"PRId64" vs limit=%"PRId64")", GetValueOut(block.vtx[0]), GetBlockValue(pindex->nHeight, nFees)));
return state.DoS(100,
error("ConnectBlock() : coinbase pays too much (actual=%"PRId64" vs limit=%"PRId64")",
GetValueOut(block.vtx[0]), GetBlockValue(pindex->nHeight, nFees)),
REJECT_INVALID, "coinbase too large");
if (!control.Wait())
return state.DoS(100, false);
@ -2161,22 +2188,27 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo
// Size limits
if (block.vtx.empty() || block.vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
return state.DoS(100, error("CheckBlock() : size limits failed"));
return state.DoS(100, error("CheckBlock() : size limits failed"),
REJECT_INVALID, "block size too large");
// Check proof of work matches claimed amount
if (fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits))
return state.DoS(50, error("CheckBlock() : proof of work failed"));
return state.DoS(50, error("CheckBlock() : proof of work failed"),
REJECT_INVALID, "invalid pow");
// Check timestamp
if (block.GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60)
return state.Invalid(error("CheckBlock() : block timestamp too far in the future"));
return state.Invalid(error("CheckBlock() : block timestamp too far in the future"),
REJECT_INVALID, "time in future");
// First transaction must be coinbase, the rest must not be
if (block.vtx.empty() || !block.vtx[0].IsCoinBase())
return state.DoS(100, error("CheckBlock() : first tx is not coinbase"));
return state.DoS(100, error("CheckBlock() : first tx is not coinbase"),
REJECT_INVALID, "no coinbase");
for (unsigned int i = 1; i < block.vtx.size(); i++)
if (block.vtx[i].IsCoinBase())
return state.DoS(100, error("CheckBlock() : more than one coinbase"));
return state.DoS(100, error("CheckBlock() : more than one coinbase"),
REJECT_INVALID, "duplicate coinbase");
// Check transactions
BOOST_FOREACH(const CTransaction& tx, block.vtx)
@ -2195,7 +2227,8 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo
uniqueTx.insert(block.GetTxHash(i));
}
if (uniqueTx.size() != block.vtx.size())
return state.DoS(100, error("CheckBlock() : duplicate transaction"), true);
return state.DoS(100, error("CheckBlock() : duplicate transaction"),
REJECT_INVALID, "duplicate transaction", true);
unsigned int nSigOps = 0;
BOOST_FOREACH(const CTransaction& tx, block.vtx)
@ -2203,11 +2236,13 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo
nSigOps += GetLegacySigOpCount(tx);
}
if (nSigOps > MAX_BLOCK_SIGOPS)
return state.DoS(100, error("CheckBlock() : out-of-bounds SigOpCount"));
return state.DoS(100, error("CheckBlock() : out-of-bounds SigOpCount"),
REJECT_INVALID, "sig op count", true);
// Check merkle root
if (fCheckMerkleRoot && block.hashMerkleRoot != block.vMerkleTree.back())
return state.DoS(100, error("CheckBlock() : hashMerkleRoot mismatch"));
return state.DoS(100, error("CheckBlock() : hashMerkleRoot mismatch"),
REJECT_INVALID, "bad merkle root", true);
return true;
}
@ -2231,20 +2266,24 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CDiskBlockPos* dbp)
// Check proof of work
if (block.nBits != GetNextWorkRequired(pindexPrev, &block))
return state.DoS(100, error("AcceptBlock() : incorrect proof of work"));
return state.DoS(100, error("AcceptBlock() : incorrect proof of work"),
REJECT_INVALID, "bad pow");
// Check timestamp against prev
if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast())
return state.Invalid(error("AcceptBlock() : block's timestamp is too early"));
return state.Invalid(error("AcceptBlock() : block's timestamp is too early"),
REJECT_INVALID, "timestamp too early");
// Check that all transactions are finalized
BOOST_FOREACH(const CTransaction& tx, block.vtx)
if (!IsFinalTx(tx, nHeight, block.GetBlockTime()))
return state.DoS(10, error("AcceptBlock() : contains a non-final transaction"));
return state.DoS(10, error("AcceptBlock() : contains a non-final transaction"),
REJECT_INVALID, "non-final tx");
// Check that the block chain matches the known block chain up to a checkpoint
if (!Checkpoints::CheckBlock(nHeight, hash))
return state.DoS(100, error("AcceptBlock() : rejected by checkpoint lock-in at %d", nHeight));
return state.DoS(100, error("AcceptBlock() : rejected by checkpoint lock-in at %d", nHeight),
REJECT_CHECKPOINT, "checkpoint mismatch");
// Reject block.nVersion=1 blocks when 95% (75% on testnet) of the network has upgraded:
if (block.nVersion < 2)
@ -2252,7 +2291,8 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CDiskBlockPos* dbp)
if ((!TestNet() && CBlockIndex::IsSuperMajority(2, pindexPrev, 950, 1000)) ||
(TestNet() && CBlockIndex::IsSuperMajority(2, pindexPrev, 75, 100)))
{
return state.Invalid(error("AcceptBlock() : rejected nVersion=1 block"));
return state.Invalid(error("AcceptBlock() : rejected nVersion=1 block"),
REJECT_OBSOLETE, "version 1 blocks obsolete");
}
}
// Enforce block.nVersion=2 rule that the coinbase starts with serialized block height
@ -2265,7 +2305,8 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CDiskBlockPos* dbp)
CScript expect = CScript() << nHeight;
if (block.vtx[0].vin[0].scriptSig.size() < expect.size() ||
!std::equal(expect.begin(), expect.end(), block.vtx[0].vin[0].scriptSig.begin()))
return state.DoS(100, error("AcceptBlock() : block height mismatch in coinbase"));
return state.DoS(100, error("AcceptBlock() : block height mismatch in coinbase"),
REJECT_INVALID, "height incorrect in coinbase");
}
}
}
@ -2355,7 +2396,8 @@ bool ProcessBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDiskBl
int64_t deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime;
if (deltaTime < 0)
{
return state.DoS(100, error("ProcessBlock() : block with timestamp before last checkpoint"));
return state.DoS(100, error("ProcessBlock() : block with timestamp before last checkpoint"),
REJECT_CHECKPOINT, "timestamp before checkpoint");
}
CBigNum bnNewBlock;
bnNewBlock.SetCompact(pblock->nBits);
@ -2363,7 +2405,8 @@ bool ProcessBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDiskBl
bnRequired.SetCompact(ComputeMinWork(pcheckpoint->nBits, deltaTime));
if (bnNewBlock > bnRequired)
{
return state.DoS(100, error("ProcessBlock() : block with too little proof-of-work"));
return state.DoS(100, error("ProcessBlock() : block with too little proof-of-work"),
REJECT_INVALID, "invalid pow");
}
}
@ -3061,7 +3104,6 @@ bool static AlreadyHave(const CInv& inv)
void static ProcessGetData(CNode* pfrom)
{
std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin();
@ -3193,6 +3235,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
// Each connection can only send one version message
if (pfrom->nVersion != 0)
{
pfrom->PushMessage("reject", strCommand, REJECT_DUPLICATE, string("Duplicate version message"));
pfrom->Misbehaving(1);
return false;
}
@ -3206,6 +3249,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
{
// disconnect from peers older than this proto version
LogPrintf("partner %s using obsolete version %i; disconnecting\n", pfrom->addr.ToString().c_str(), pfrom->nVersion);
pfrom->PushMessage("reject", strCommand, REJECT_OBSOLETE,
strprintf("Version must be %d or greater", MIN_PEER_PROTO_VERSION));
pfrom->fDisconnect = true;
return false;
}
@ -3589,9 +3634,13 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
}
int nDoS = 0;
if (state.IsInvalid(nDoS))
{
pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
state.GetRejectReason(), inv.hash);
if (nDoS > 0)
pfrom->Misbehaving(nDoS);
}
}
else if (strCommand == "block" && !fImporting && !fReindex) // Ignore blocks received while importing
@ -3612,9 +3661,13 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
mapAlreadyAskedFor.erase(inv);
int nDoS = 0;
if (state.IsInvalid(nDoS))
{
pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
state.GetRejectReason(), inv.hash);
if (nDoS > 0)
pfrom->Misbehaving(nDoS);
}
}
else if (strCommand == "getaddr")
@ -3809,6 +3862,29 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
}
else if (strCommand == "reject")
{
if (fDebug)
{
string strMsg; unsigned char ccode; string strReason;
vRecv >> strMsg >> ccode >> strReason;
ostringstream ss;
ss << strMsg << " code " << itostr(ccode) << ": " << strReason;
if (strMsg == "block" || strMsg == "tx")
{
uint256 hash;
vRecv >> hash;
ss << ": hash " << hash.ToString();
}
// Truncate to reasonable length and sanitize before printing:
string s = ss.str();
if (s.size() > 111) s.erase(111, string::npos);
LogPrint("net", "Reject %s\n", SanitizeString(s).c_str());
}
}
else
{
// Ignore unknown commands for extensibility
@ -3907,6 +3983,7 @@ bool ProcessMessages(CNode* pfrom)
}
catch (std::ios_base::failure& e)
{
pfrom->PushMessage("reject", strCommand, REJECT_MALFORMED, string("error parsing message"));
if (strstr(e.what(), "end of data"))
{
// Allow exceptions from under-length message on vRecv

View File

@ -67,6 +67,16 @@ static const int fHaveUPnP = true;
static const int fHaveUPnP = false;
#endif
/** "reject" message codes **/
static const unsigned char REJECT_MALFORMED = 0x01;
static const unsigned char REJECT_INVALID = 0x10;
static const unsigned char REJECT_OBSOLETE = 0x11;
static const unsigned char REJECT_DUPLICATE = 0x12;
static const unsigned char REJECT_NONSTANDARD = 0x40;
static const unsigned char REJECT_DUST = 0x41;
static const unsigned char REJECT_INSUFFICIENTFEE = 0x42;
static const unsigned char REJECT_CHECKPOINT = 0x43;
extern CScript COINBASE_FLAGS;
@ -926,19 +936,26 @@ private:
MODE_ERROR, // run-time error
} mode;
int nDoS;
std::string strRejectReason;
unsigned char chRejectCode;
bool corruptionPossible;
public:
CValidationState() : mode(MODE_VALID), nDoS(0) {}
bool DoS(int level, bool ret = false, bool corruptionIn = false) {
bool DoS(int level, bool ret = false,
unsigned char chRejectCodeIn=0, std::string strRejectReasonIn="",
bool corruptionIn=false) {
chRejectCode = chRejectCodeIn;
strRejectReason = strRejectReasonIn;
corruptionPossible = corruptionIn;
if (mode == MODE_ERROR)
return ret;
nDoS += level;
mode = MODE_INVALID;
corruptionPossible = corruptionIn;
return ret;
}
bool Invalid(bool ret = false) {
return DoS(0, ret);
bool Invalid(bool ret = false,
unsigned char _chRejectCode=0, std::string _strRejectReason="") {
return DoS(0, ret, _chRejectCode, _strRejectReason);
}
bool Error() {
mode = MODE_ERROR;
@ -967,6 +984,8 @@ public:
bool CorruptionPossible() {
return corruptionPossible;
}
unsigned char GetRejectCode() const { return chRejectCode; }
std::string GetRejectReason() const { return strRejectReason; }
};
/** An in-memory indexed chain of blocks. */