aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/consensus/validation.h13
-rw-r--r--src/init.cpp2
-rw-r--r--src/main.cpp131
-rw-r--r--src/main.h11
4 files changed, 80 insertions, 77 deletions
diff --git a/src/consensus/validation.h b/src/consensus/validation.h
index 719e090a42..d6051edc38 100644
--- a/src/consensus/validation.h
+++ b/src/consensus/validation.h
@@ -30,14 +30,17 @@ private:
std::string strRejectReason;
unsigned int chRejectCode;
bool corruptionPossible;
+ std::string strDebugMessage;
public:
CValidationState() : mode(MODE_VALID), nDoS(0), chRejectCode(0), corruptionPossible(false) {}
bool DoS(int level, bool ret = false,
- unsigned int chRejectCodeIn=0, std::string strRejectReasonIn="",
- bool corruptionIn=false) {
+ unsigned int chRejectCodeIn=0, const std::string &strRejectReasonIn="",
+ bool corruptionIn=false,
+ const std::string &strDebugMessageIn="") {
chRejectCode = chRejectCodeIn;
strRejectReason = strRejectReasonIn;
corruptionPossible = corruptionIn;
+ strDebugMessage = strDebugMessageIn;
if (mode == MODE_ERROR)
return ret;
nDoS += level;
@@ -45,8 +48,9 @@ public:
return ret;
}
bool Invalid(bool ret = false,
- unsigned int _chRejectCode=0, std::string _strRejectReason="") {
- return DoS(0, ret, _chRejectCode, _strRejectReason);
+ unsigned int _chRejectCode=0, const std::string &_strRejectReason="",
+ const std::string &_strDebugMessage="") {
+ return DoS(0, ret, _chRejectCode, _strRejectReason, false, _strDebugMessage);
}
bool Error(const std::string& strRejectReasonIn) {
if (mode == MODE_VALID)
@@ -75,6 +79,7 @@ public:
}
unsigned int GetRejectCode() const { return chRejectCode; }
std::string GetRejectReason() const { return strRejectReason; }
+ std::string GetDebugMessage() const { return strDebugMessage; }
};
#endif // BITCOIN_CONSENSUS_VALIDATION_H
diff --git a/src/init.cpp b/src/init.cpp
index ef1c39db25..085e04fdfd 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -373,7 +373,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", 1));
strUsage += HelpMessageOpt("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", 0));
}
- string debugCategories = "addrman, alert, bench, coindb, db, lock, rand, rpc, selectcoins, mempool, net, proxy, prune"; // Don't translate these and qt below
+ string debugCategories = "addrman, alert, bench, coindb, db, lock, rand, rpc, selectcoins, mempool, mempoolrej, net, proxy, prune"; // Don't translate these and qt below
if (mode == HMM_BITCOIN_QT)
debugCategories += ", qt";
strUsage += HelpMessageOpt("-debug=<category>", strprintf(_("Output debugging information (default: %u, supplying <category> is optional)"), 0) + ". " +
diff --git a/src/main.cpp b/src/main.cpp
index bfae0d7f8c..9f42819a0a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -696,30 +696,24 @@ 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"),
- REJECT_INVALID, "bad-txns-vin-empty");
+ return state.DoS(10, false, REJECT_INVALID, "bad-txns-vin-empty");
if (tx.vout.empty())
- return state.DoS(10, error("CheckTransaction(): vout empty"),
- REJECT_INVALID, "bad-txns-vout-empty");
+ return state.DoS(10, false, REJECT_INVALID, "bad-txns-vout-empty");
// Size limits
if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
- return state.DoS(100, error("CheckTransaction(): size limits failed"),
- REJECT_INVALID, "bad-txns-oversize");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-oversize");
// Check for negative or overflow output values
CAmount nValueOut = 0;
BOOST_FOREACH(const CTxOut& txout, tx.vout)
{
if (txout.nValue < 0)
- return state.DoS(100, error("CheckTransaction(): txout.nValue negative"),
- REJECT_INVALID, "bad-txns-vout-negative");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative");
if (txout.nValue > MAX_MONEY)
- return state.DoS(100, error("CheckTransaction(): txout.nValue too high"),
- REJECT_INVALID, "bad-txns-vout-toolarge");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge");
nValueOut += txout.nValue;
if (!MoneyRange(nValueOut))
- return state.DoS(100, error("CheckTransaction(): txout total out of range"),
- REJECT_INVALID, "bad-txns-txouttotal-toolarge");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge");
}
// Check for duplicate inputs
@@ -727,23 +721,20 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
if (vInOutPoints.count(txin.prevout))
- return state.DoS(100, error("CheckTransaction(): duplicate inputs"),
- REJECT_INVALID, "bad-txns-inputs-duplicate");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-duplicate");
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"),
- REJECT_INVALID, "bad-cb-length");
+ return state.DoS(100, false, REJECT_INVALID, "bad-cb-length");
}
else
{
BOOST_FOREACH(const CTxIn& txin, tx.vin)
if (txin.prevout.IsNull())
- return state.DoS(10, error("CheckTransaction(): prevout is null"),
- REJECT_INVALID, "bad-txns-prevout-null");
+ return state.DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null");
}
return true;
@@ -778,6 +769,14 @@ CAmount GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowF
return nMinFee;
}
+/** Convert CValidationState to a human-readable message for logging */
+static std::string FormatStateMessage(const CValidationState &state)
+{
+ return strprintf("%s%s (code %i)",
+ state.GetRejectReason(),
+ state.GetDebugMessage().empty() ? "" : ", "+state.GetDebugMessage(),
+ state.GetRejectCode());
+}
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
bool* pfMissingInputs, bool fRejectAbsurdFee)
@@ -787,31 +786,27 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
*pfMissingInputs = false;
if (!CheckTransaction(tx, state))
- return error("AcceptToMemoryPool: CheckTransaction failed");
+ return false;
// 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"),
- REJECT_INVALID, "coinbase");
+ return state.DoS(100, false, REJECT_INVALID, "coinbase");
// Rather not work on nonstandard transactions (unless -testnet/-regtest)
string reason;
if (fRequireStandard && !IsStandardTx(tx, reason))
- return state.DoS(0,
- error("AcceptToMemoryPool: nonstandard transaction: %s", reason),
- REJECT_NONSTANDARD, reason);
+ return state.DoS(0, false, REJECT_NONSTANDARD, reason);
// Only accept nLockTime-using transactions that can be mined in the next
// block; we don't want our mempool filled up with transactions that can't
// be mined yet.
if (!CheckFinalTx(tx))
- return state.DoS(0, error("AcceptToMemoryPool: non-final"),
- REJECT_NONSTANDARD, "non-final");
+ return state.DoS(0, false, REJECT_NONSTANDARD, "non-final");
// is it already in the memory pool?
uint256 hash = tx.GetHash();
if (pool.exists(hash))
- return false;
+ return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-in-mempool");
// Check for conflicts with in-memory transactions
{
@@ -822,7 +817,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
if (pool.mapNextTx.count(outpoint))
{
// Disable replacement feature for now
- return false;
+ return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict");
}
}
}
@@ -839,7 +834,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// do we already have it?
if (view.HaveCoins(hash))
- return false;
+ return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-known");
// do all inputs exist?
// Note that this does not check for the presence of actual outputs (see the next check for that),
@@ -848,14 +843,13 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
if (!view.HaveCoins(txin.prevout.hash)) {
if (pfMissingInputs)
*pfMissingInputs = true;
- return false;
+ return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid()
}
}
// are the actual inputs available?
if (!view.HaveInputs(tx))
- return state.Invalid(error("AcceptToMemoryPool: inputs already spent"),
- REJECT_DUPLICATE, "bad-txns-inputs-spent");
+ return state.Invalid(false, REJECT_DUPLICATE, "bad-txns-inputs-spent");
// Bring the best block into scope
view.GetBestBlock();
@@ -868,7 +862,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// Check for non-standard pay-to-script-hash in inputs
if (fRequireStandard && !AreInputsStandard(tx, view))
- return error("AcceptToMemoryPool: nonstandard transaction input");
+ return state.Invalid(false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs");
// Check that the transaction doesn't have an excessive number of
// sigops, making it impossible to mine. Since the coinbase transaction
@@ -878,10 +872,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
unsigned int nSigOps = GetLegacySigOpCount(tx);
nSigOps += GetP2SHSigOpCount(tx, view);
if (nSigOps > MAX_STANDARD_TX_SIGOPS)
- return state.DoS(0,
- error("AcceptToMemoryPool: too many sigops %s, %d > %d",
- hash.ToString(), nSigOps, MAX_STANDARD_TX_SIGOPS),
- REJECT_NONSTANDARD, "bad-txns-too-many-sigops");
+ return state.DoS(0, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops", false,
+ strprintf("%d > %d", nSigOps, MAX_STANDARD_TX_SIGOPS));
CAmount nValueOut = tx.GetValueOut();
CAmount nFees = nValueIn-nValueOut;
@@ -893,9 +885,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// Don't accept it if it can't get into a block
CAmount txMinFee = GetMinRelayFee(tx, nSize, true);
if (fLimitFree && nFees < txMinFee)
- return state.DoS(0, error("AcceptToMemoryPool: not enough fees %s, %d < %d",
- hash.ToString(), nFees, txMinFee),
- REJECT_INSUFFICIENTFEE, "insufficient fee");
+ return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "insufficient fee", false,
+ strprintf("%d < %d", nFees, txMinFee));
// Require that free transactions have sufficient priority to be mined in the next block.
if (GetBoolArg("-relaypriority", true) && nFees < ::minRelayTxFee.GetFee(nSize) && !AllowFree(view.GetPriority(tx, chainActive.Height() + 1))) {
@@ -920,24 +911,20 @@ 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 state.DoS(0, error("AcceptToMemoryPool: free transaction rejected by rate limiter"),
- REJECT_INSUFFICIENTFEE, "rate limited free transaction");
+ return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "rate limited free transaction");
LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize);
dFreeCount += nSize;
}
if (fRejectAbsurdFee && nFees > ::minRelayTxFee.GetFee(nSize) * 10000)
- return state.Invalid(error("AcceptToMemoryPool: absurdly high fees %s, %d > %d",
- hash.ToString(),
- nFees, ::minRelayTxFee.GetFee(nSize) * 10000),
- REJECT_HIGHFEE, "absurdly-high-fee");
+ return state.Invalid(false,
+ REJECT_HIGHFEE, "absurdly-high-fee",
+ strprintf("%d > %d", nFees, ::minRelayTxFee.GetFee(nSize) * 10000));
// Check against previous transactions
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true))
- {
- return error("AcceptToMemoryPool: ConnectInputs failed %s", hash.ToString());
- }
+ return false;
// Check again against just the consensus-critical mandatory script
// verification flags, in case of bugs in the standard flags that cause
@@ -950,7 +937,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// can be exploited as a DoS attack.
if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true))
{
- return error("AcceptToMemoryPool: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s", hash.ToString());
+ return error("%s: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s, %s",
+ __func__, hash.ToString(), FormatStateMessage(state));
}
// Store transaction in memory
@@ -1241,7 +1229,7 @@ void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state
if (state.IsInvalid(nDoS)) {
std::map<uint256, NodeId>::iterator it = mapBlockSource.find(pindex->GetBlockHash());
if (it != mapBlockSource.end() && State(it->second)) {
- assert(state.GetRejectCode() < 0x100);
+ assert (state.GetRejectCode() < REJECT_INTERNAL); // Blocks are never rejected with internal reject codes
CBlockReject reject = {(unsigned char)state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), pindex->GetBlockHash()};
State(it->second)->rejects.push_back(reject);
if (nDoS > 0)
@@ -1292,7 +1280,7 @@ void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCach
bool CScriptCheck::operator()() {
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
if (!VerifyScript(scriptSig, scriptPubKey, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, cacheStore), &error)) {
- return ::error("CScriptCheck(): %s:%d VerifySignature failed: %s", ptxTo->GetHash().ToString(), nIn, ScriptErrorString(error));
+ return false;
}
return true;
}
@@ -1310,7 +1298,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
// 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()));
+ return state.Invalid(false, 0, "", "Inputs unavailable");
CAmount nValueIn = 0;
CAmount nFees = 0;
@@ -1323,33 +1311,29 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
// 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),
- REJECT_INVALID, "bad-txns-premature-spend-of-coinbase");
+ return state.Invalid(false,
+ REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
+ strprintf("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"),
- REJECT_INVALID, "bad-txns-inputvalues-outofrange");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
}
if (nValueIn < tx.GetValueOut())
- return state.DoS(100, error("CheckInputs(): %s value in (%s) < value out (%s)",
- tx.GetHash().ToString(), FormatMoney(nValueIn), FormatMoney(tx.GetValueOut())),
- REJECT_INVALID, "bad-txns-in-belowout");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false,
+ strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(tx.GetValueOut())));
// Tally transaction fees
CAmount nTxFee = nValueIn - tx.GetValueOut();
if (nTxFee < 0)
- return state.DoS(100, error("CheckInputs(): %s nTxFee < 0", tx.GetHash().ToString()),
- REJECT_INVALID, "bad-txns-fee-negative");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-negative");
nFees += nTxFee;
if (!MoneyRange(nFees))
- return state.DoS(100, error("CheckInputs(): nFees out of range"),
- REJECT_INVALID, "bad-txns-fee-outofrange");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
return true;
}
}// namespace Consensus
@@ -1795,7 +1779,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
std::vector<CScriptCheck> vChecks;
if (!CheckInputs(tx, state, view, fScriptChecks, flags, false, nScriptCheckThreads ? &vChecks : NULL))
- return false;
+ return error("ConnectBlock(): CheckInputs on %s failed with %s",
+ tx.GetHash().ToString(), FormatStateMessage(state));
control.Add(vChecks);
}
@@ -2622,7 +2607,9 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo
// Check transactions
BOOST_FOREACH(const CTransaction& tx, block.vtx)
if (!CheckTransaction(tx, state))
- return error("CheckBlock(): CheckTransaction failed");
+ return error("CheckBlock(): CheckTransaction of %s failed with %s",
+ tx.GetHash().ToString(),
+ FormatStateMessage(state));
unsigned int nSigOps = 0;
BOOST_FOREACH(const CTransaction& tx, block.vtx)
@@ -4357,11 +4344,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
int nDoS = 0;
if (state.IsInvalid(nDoS))
{
- LogPrint("mempool", "%s from peer=%d %s was not accepted into the memory pool: %s\n", tx.GetHash().ToString(),
+ LogPrint("mempoolrej", "%s from peer=%d %s was not accepted into the memory pool: %s\n", tx.GetHash().ToString(),
pfrom->id, pfrom->cleanSubVer,
- state.GetRejectReason());
- pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
- state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash);
+ FormatStateMessage(state));
+ if (state.GetRejectCode() < REJECT_INTERNAL) // Never send AcceptToMemoryPool's internal codes over P2P
+ pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
+ state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash);
if (nDoS > 0)
Misbehaving(pfrom->GetId(), nDoS);
}
@@ -4441,6 +4429,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
ProcessNewBlock(state, pfrom, &block, forceProcessing, NULL);
int nDoS;
if (state.IsInvalid(nDoS)) {
+ assert (state.GetRejectCode() < REJECT_INTERNAL); // Blocks are never rejected with internal reject codes
pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash);
if (nDoS > 0) {
diff --git a/src/main.h b/src/main.h
index a78a589e16..e3479b4b3b 100644
--- a/src/main.h
+++ b/src/main.h
@@ -455,7 +455,16 @@ extern CBlockTreeDB *pblocktree;
*/
int GetSpendHeight(const CCoinsViewCache& inputs);
-/** local "reject" message codes for RPC which can not be triggered by p2p trasactions */
+/** Reject codes greater or equal to this can be returned by AcceptToMemPool
+ * for transactions, to signal internal conditions. They cannot and should not
+ * be sent over the P2P network.
+ */
+static const unsigned int REJECT_INTERNAL = 0x100;
+/** Too high fee. Can not be triggered by P2P transactions */
static const unsigned int REJECT_HIGHFEE = 0x100;
+/** Transaction is already known (either in mempool or blockchain) */
+static const unsigned int REJECT_ALREADY_KNOWN = 0x101;
+/** Transaction conflicts with a transaction already known */
+static const unsigned int REJECT_CONFLICT = 0x102;
#endif // BITCOIN_MAIN_H