Changeset View
Changeset View
Standalone View
Standalone View
src/net_processing.cpp
Show First 20 Lines • Show All 1,183 Lines • ▼ Show 20 Lines | |||||
} | } | ||||
// overloaded variant of above to operate on CNode*s | // overloaded variant of above to operate on CNode*s | ||||
static void Misbehaving(CNode *node, int howmuch, const std::string &reason) | static void Misbehaving(CNode *node, int howmuch, const std::string &reason) | ||||
EXCLUSIVE_LOCKS_REQUIRED(cs_main) { | EXCLUSIVE_LOCKS_REQUIRED(cs_main) { | ||||
Misbehaving(node->GetId(), howmuch, reason); | Misbehaving(node->GetId(), howmuch, reason); | ||||
} | } | ||||
static bool TxRelayMayResultInDisconnect(const CValidationState &state) { | |||||
return (state.GetDoS() > 0); | |||||
} | |||||
/** | |||||
* Potentially ban a node based on the contents of a CValidationState object | |||||
* TODO: net_processing should make the punish decision based on the reason | |||||
* a tx/block was invalid, rather than just the nDoS score handed back by | |||||
* validation. | |||||
* | |||||
* @parameter via_compact_block: this bool is passed in because net_processing | |||||
* should punish peers differently depending on whether the data was provided in | |||||
* a compact block message or not. If the compact block had a valid header, but | |||||
* contained invalid txs, the peer should not be punished. See BIP 152. | |||||
*/ | |||||
static bool MaybePunishNode(NodeId nodeid, const CValidationState &state, | |||||
bool via_compact_block, | |||||
const std::string &message = "") { | |||||
int nDoS = state.GetDoS(); | |||||
if (nDoS > 0 && !via_compact_block) { | |||||
LOCK(cs_main); | |||||
Misbehaving(nodeid, nDoS, message); | |||||
return true; | |||||
} | |||||
if (message != "") { | |||||
LogPrint(BCLog::NET, "peer=%d: %s\n", nodeid, message); | |||||
} | |||||
return false; | |||||
} | |||||
////////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////////// | ||||
// | // | ||||
// blockchain -> download logic notification | // blockchain -> download logic notification | ||||
// | // | ||||
// To prevent fingerprinting attacks, only send blocks/headers outside of the | // To prevent fingerprinting attacks, only send blocks/headers outside of the | ||||
// active chain if they are no more than a month older (both in time, and in | // active chain if they are no more than a month older (both in time, and in | ||||
// best equivalent proof of work) than the best header chain we know about and | // best equivalent proof of work) than the best header chain we know about and | ||||
▲ Show 20 Lines • Show All 189 Lines • ▼ Show 20 Lines | |||||
void PeerLogicValidation::BlockChecked(const CBlock &block, | void PeerLogicValidation::BlockChecked(const CBlock &block, | ||||
const CValidationState &state) { | const CValidationState &state) { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
const uint256 hash(block.GetHash()); | const uint256 hash(block.GetHash()); | ||||
std::map<uint256, std::pair<NodeId, bool>>::iterator it = | std::map<uint256, std::pair<NodeId, bool>>::iterator it = | ||||
mapBlockSource.find(hash); | mapBlockSource.find(hash); | ||||
int nDoS = 0; | if (state.IsInvalid()) { | ||||
if (state.IsInvalid(nDoS)) { | |||||
// Don't send reject message with code 0 or an internal reject code. | // Don't send reject message with code 0 or an internal reject code. | ||||
if (it != mapBlockSource.end() && State(it->second.first) && | if (it != mapBlockSource.end() && State(it->second.first) && | ||||
state.GetRejectCode() > 0 && | state.GetRejectCode() > 0 && | ||||
state.GetRejectCode() < REJECT_INTERNAL) { | state.GetRejectCode() < REJECT_INTERNAL) { | ||||
CBlockReject reject = { | CBlockReject reject = { | ||||
uint8_t(state.GetRejectCode()), | uint8_t(state.GetRejectCode()), | ||||
state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), | state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), | ||||
hash}; | hash}; | ||||
State(it->second.first)->rejects.push_back(reject); | State(it->second.first)->rejects.push_back(reject); | ||||
if (nDoS > 0 && it->second.second) { | MaybePunishNode(/*nodeid=*/it->second.first, state, | ||||
Misbehaving(it->second.first, nDoS, state.GetRejectReason()); | /*via_compact_block=*/!it->second.second); | ||||
} | |||||
} | } | ||||
} | } | ||||
// Check that: | // Check that: | ||||
// 1. The block is valid | // 1. The block is valid | ||||
// 2. We're not in initial block download | // 2. We're not in initial block download | ||||
// 3. This is currently the best block we're aware of. We haven't updated | // 3. This is currently the best block we're aware of. We haven't updated | ||||
// the tip yet so we have no way to check this directly here. Instead we | // the tip yet so we have no way to check this directly here. Instead we | ||||
// just check that there are currently no other blocks in flight. | // just check that there are currently no other blocks in flight. | ||||
▲ Show 20 Lines • Show All 471 Lines • ▼ Show 20 Lines | const CBlockIndex *pindexLast = nullptr; | ||||
received_new_header = true; | received_new_header = true; | ||||
} | } | ||||
} | } | ||||
CValidationState state; | CValidationState state; | ||||
CBlockHeader first_invalid_header; | CBlockHeader first_invalid_header; | ||||
if (!ProcessNewBlockHeaders(config, headers, state, &pindexLast, | if (!ProcessNewBlockHeaders(config, headers, state, &pindexLast, | ||||
&first_invalid_header)) { | &first_invalid_header)) { | ||||
int nDoS; | if (state.IsInvalid()) { | ||||
if (state.IsInvalid(nDoS)) { | // The lock is required here due to LookupBlockIndex | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
if (nDoS > 0) { | |||||
Misbehaving(pfrom, nDoS, state.GetRejectReason()); | |||||
} | |||||
if (punish_duplicate_invalid && | if (punish_duplicate_invalid && | ||||
LookupBlockIndex(first_invalid_header.GetHash())) { | LookupBlockIndex(first_invalid_header.GetHash())) { | ||||
// Goal: don't allow outbound peers to use up our outbound | // Goal: don't allow outbound peers to use up our outbound | ||||
// connection slots if they are on incompatible chains. | // connection slots if they are on incompatible chains. | ||||
// | // | ||||
// We ask the caller to set punish_invalid appropriately based | // We ask the caller to set punish_invalid appropriately based | ||||
// on the peer and the method of header delivery (compact blocks | // on the peer and the method of header delivery (compact blocks | ||||
// are allowed to be invalid in some circumstances, under BIP | // are allowed to be invalid in some circumstances, under BIP | ||||
Show All 19 Lines | if (!ProcessNewBlockHeaders(config, headers, state, &pindexLast, | ||||
// TODO: update the DoS logic (or, rather, rewrite the | // TODO: update the DoS logic (or, rather, rewrite the | ||||
// DoS-interface between validation and net_processing) so that | // DoS-interface between validation and net_processing) so that | ||||
// the interface is cleaner, and so that we disconnect on all | // the interface is cleaner, and so that we disconnect on all | ||||
// the reasons that a peer's headers chain is incompatible with | // the reasons that a peer's headers chain is incompatible with | ||||
// ours (eg block->nVersion softforks, MTP violations, etc), and | // ours (eg block->nVersion softforks, MTP violations, etc), and | ||||
// not just the duplicate-invalid case. | // not just the duplicate-invalid case. | ||||
pfrom->fDisconnect = true; | pfrom->fDisconnect = true; | ||||
} | } | ||||
return error("invalid header received"); | MaybePunishNode(pfrom->GetId(), state, /*via_compact_block*/ false, | ||||
"invalid header received"); | |||||
return false; | |||||
} | } | ||||
} | } | ||||
{ | { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
CNodeState *nodestate = State(pfrom->GetId()); | CNodeState *nodestate = State(pfrom->GetId()); | ||||
if (nodestate->nUnconnectingHeaders > 0) { | if (nodestate->nUnconnectingHeaders > 0) { | ||||
LogPrint(BCLog::NET, | LogPrint(BCLog::NET, | ||||
▲ Show 20 Lines • Show All 157 Lines • ▼ Show 20 Lines | while (!done && !orphan_work_set.empty()) { | ||||
if (orphan_it == mapOrphanTransactions.end()) { | if (orphan_it == mapOrphanTransactions.end()) { | ||||
continue; | continue; | ||||
} | } | ||||
const CTransactionRef porphanTx = orphan_it->second.tx; | const CTransactionRef porphanTx = orphan_it->second.tx; | ||||
const CTransaction &orphanTx = *porphanTx; | const CTransaction &orphanTx = *porphanTx; | ||||
NodeId fromPeer = orphan_it->second.fromPeer; | NodeId fromPeer = orphan_it->second.fromPeer; | ||||
bool fMissingInputs2 = false; | bool fMissingInputs2 = false; | ||||
// Use a dummy CValidationState so someone can't setup nodes to | // Use a new CValidationState because orphans come from different peers | ||||
// counter-DoS based on orphan resolution (that is, feeding | // (and we call MaybePunishNode based on the source peer from the orphan | ||||
// people an invalid transaction based on LegitTxX in order to | // map, not based on the peer that relayed the previous transaction). | ||||
// get anyone relaying LegitTxX banned) | |||||
CValidationState orphan_state; | CValidationState orphan_state; | ||||
auto it = rejectCountPerNode.find(fromPeer); | auto it = rejectCountPerNode.find(fromPeer); | ||||
if (it != rejectCountPerNode.end() && | if (it != rejectCountPerNode.end() && | ||||
it->second > MAX_NON_STANDARD_ORPHAN_PER_NODE) { | it->second > MAX_NON_STANDARD_ORPHAN_PER_NODE) { | ||||
continue; | continue; | ||||
} | } | ||||
Show All 10 Lines | while (!done && !orphan_work_set.empty()) { | ||||
for (const auto &elem : it_by_prev->second) { | for (const auto &elem : it_by_prev->second) { | ||||
orphan_work_set.insert(elem->first); | orphan_work_set.insert(elem->first); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
EraseOrphanTx(orphanTxId); | EraseOrphanTx(orphanTxId); | ||||
done = true; | done = true; | ||||
} else if (!fMissingInputs2) { | } else if (!fMissingInputs2) { | ||||
int nDos = 0; | if (orphan_state.IsInvalid()) { | ||||
if (orphan_state.IsInvalid(nDos)) { | |||||
rejectCountPerNode[fromPeer]++; | |||||
if (nDos > 0) { | |||||
// Punish peer that gave us an invalid orphan tx | // Punish peer that gave us an invalid orphan tx | ||||
Misbehaving(fromPeer, nDos, "invalid-orphan-tx"); | MaybePunishNode(fromPeer, orphan_state, | ||||
/*via_compact_block*/ false); | |||||
LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s\n", | LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s\n", | ||||
orphanTxId.ToString()); | orphanTxId.ToString()); | ||||
} | } | ||||
EraseOrphanTx(orphanTxId); | |||||
} | |||||
// Has inputs but not accepted to mempool | // Has inputs but not accepted to mempool | ||||
// Probably non-standard or insufficient fee | // Probably non-standard or insufficient fee | ||||
LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", | LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", | ||||
orphanTxId.ToString()); | orphanTxId.ToString()); | ||||
if (!orphan_state.CorruptionPossible()) { | if (!orphan_state.CorruptionPossible()) { | ||||
// Do not use rejection cache for witness | // Do not use rejection cache for witness | ||||
// transactions or witness-stripped transactions, as | // transactions or witness-stripped transactions, as | ||||
// they can have been malleated. See | // they can have been malleated. See | ||||
▲ Show 20 Lines • Show All 850 Lines • ▼ Show 20 Lines | if (strCommand == NetMsgType::TX) { | ||||
// Always relay transactions received from whitelisted peers, | // Always relay transactions received from whitelisted peers, | ||||
// even if they were already in the mempool or rejected from it | // even if they were already in the mempool or rejected from it | ||||
// due to policy, allowing the node to function as a gateway for | // due to policy, allowing the node to function as a gateway for | ||||
// nodes hidden behind it. | // nodes hidden behind it. | ||||
// | // | ||||
// Never relay transactions that we would assign a non-zero DoS | // Never relay transactions that we would assign a non-zero DoS | ||||
// score for, as we expect peers to do the same with us in that | // score for, as we expect peers to do the same with us in that | ||||
// case. | // case. | ||||
int nDoS = 0; | if (!state.IsInvalid() || | ||||
if (!state.IsInvalid(nDoS) || nDoS == 0) { | !TxRelayMayResultInDisconnect(state)) { | ||||
LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", | LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", | ||||
tx.GetId().ToString(), pfrom->GetId()); | tx.GetId().ToString(), pfrom->GetId()); | ||||
RelayTransaction(tx.GetId(), *connman); | RelayTransaction(tx.GetId(), *connman); | ||||
} else { | } else { | ||||
LogPrintf("Not relaying invalid transaction %s from " | LogPrintf("Not relaying invalid transaction %s from " | ||||
"whitelisted peer=%d (%s)\n", | "whitelisted peer=%d (%s)\n", | ||||
tx.GetId().ToString(), pfrom->GetId(), | tx.GetId().ToString(), pfrom->GetId(), | ||||
FormatStateMessage(state)); | FormatStateMessage(state)); | ||||
Show All 13 Lines | if (strCommand == NetMsgType::TX) { | ||||
// submitted a DoSy tx. | // submitted a DoSy tx. | ||||
// | // | ||||
// Note that recentRejects doesn't just record DoSy or invalid | // Note that recentRejects doesn't just record DoSy or invalid | ||||
// transactions, but any tx not accepted by the mempool, which may be | // transactions, but any tx not accepted by the mempool, which may be | ||||
// due to node policy (vs. consensus). So we can't blanket penalize a | // due to node policy (vs. consensus). So we can't blanket penalize a | ||||
// peer simply for relaying a tx that our recentRejects has caught, | // peer simply for relaying a tx that our recentRejects has caught, | ||||
// regardless of false positives. | // regardless of false positives. | ||||
int nDoS = 0; | if (state.IsInvalid()) { | ||||
if (state.IsInvalid(nDoS)) { | |||||
LogPrint(BCLog::MEMPOOLREJ, | LogPrint(BCLog::MEMPOOLREJ, | ||||
"%s from peer=%d was not accepted: %s\n", | "%s from peer=%d was not accepted: %s\n", | ||||
tx.GetHash().ToString(), pfrom->GetId(), | tx.GetHash().ToString(), pfrom->GetId(), | ||||
FormatStateMessage(state)); | FormatStateMessage(state)); | ||||
// Never send AcceptToMemoryPool's internal codes over P2P. | // Never send AcceptToMemoryPool's internal codes over P2P | ||||
if (enable_bip61 && state.GetRejectCode() > 0 && | if (enable_bip61 && state.GetRejectCode() > 0 && | ||||
state.GetRejectCode() < REJECT_INTERNAL) { | state.GetRejectCode() < REJECT_INTERNAL) { | ||||
connman->PushMessage( | connman->PushMessage( | ||||
pfrom, msgMaker.Make(NetMsgType::REJECT, strCommand, | pfrom, msgMaker.Make(NetMsgType::REJECT, strCommand, | ||||
uint8_t(state.GetRejectCode()), | uint8_t(state.GetRejectCode()), | ||||
state.GetRejectReason().substr( | state.GetRejectReason().substr( | ||||
0, MAX_REJECT_MESSAGE_LENGTH), | 0, MAX_REJECT_MESSAGE_LENGTH), | ||||
inv.hash)); | inv.hash)); | ||||
} | } | ||||
if (nDoS > 0) { | MaybePunishNode(pfrom->GetId(), state, /*via_compact_block*/ false); | ||||
Misbehaving(pfrom, nDoS, state.GetRejectReason()); | |||||
} | |||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
if (strCommand == NetMsgType::CMPCTBLOCK) { | if (strCommand == NetMsgType::CMPCTBLOCK) { | ||||
// Ignore cmpctblock received while importing | // Ignore cmpctblock received while importing | ||||
if (fImporting || fReindex) { | if (fImporting || fReindex) { | ||||
LogPrint(BCLog::NET, | LogPrint(BCLog::NET, | ||||
Show All 27 Lines | if (strCommand == NetMsgType::CMPCTBLOCK) { | ||||
received_new_header = true; | received_new_header = true; | ||||
} | } | ||||
} | } | ||||
const CBlockIndex *pindex = nullptr; | const CBlockIndex *pindex = nullptr; | ||||
CValidationState state; | CValidationState state; | ||||
if (!ProcessNewBlockHeaders(config, {cmpctblock.header}, state, | if (!ProcessNewBlockHeaders(config, {cmpctblock.header}, state, | ||||
&pindex)) { | &pindex)) { | ||||
int nDoS; | if (state.IsInvalid() && received_new_header) { | ||||
if (state.IsInvalid(nDoS)) { | // In this situation, the block header is known to be invalid. | ||||
if (nDoS > 0) { | // If we never created a CBlockIndex entry for it, then the | ||||
LogPrintf("Peer %d sent us invalid header via cmpctblock\n", | // header must be bad just by inspection (and is not one that | ||||
pfrom->GetId()); | // looked okay but the block later turned out to be invalid for | ||||
LOCK(cs_main); | // some other reason). | ||||
Misbehaving(pfrom, nDoS, state.GetRejectReason()); | // We should punish compact block peers that give us an invalid | ||||
} else { | // header (other than a "duplicate-invalid" one, see | ||||
LogPrint(BCLog::NET, | // ProcessHeadersMessage), so set via_compact_block to false | ||||
"Peer %d sent us invalid header via cmpctblock\n", | // here. | ||||
pfrom->GetId()); | // TODO: when we switch from DoS scores to reasons that | ||||
} | // tx/blocks are invalid, this call should set | ||||
// via_compact_block to true, since MaybePunishNode will have | |||||
// sufficient information to act correctly. | |||||
MaybePunishNode(pfrom->GetId(), state, | |||||
/*via_compact_block*/ false, | |||||
"invalid header via cmpctblock"); | |||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
// When we succeed in decoding a block's txids from a cmpctblock | // When we succeed in decoding a block's txids from a cmpctblock | ||||
// message we typically jump to the BLOCKTXN handling code, with a | // message we typically jump to the BLOCKTXN handling code, with a | ||||
// dummy (empty) BLOCKTXN message, to re-use the logic there in | // dummy (empty) BLOCKTXN message, to re-use the logic there in | ||||
// completing processing of the putative block (without cs_main). | // completing processing of the putative block (without cs_main). | ||||
▲ Show 20 Lines • Show All 1,927 Lines • Show Last 20 Lines |