Changeset View
Changeset View
Standalone View
Standalone View
src/net_processing.cpp
Show First 20 Lines • Show All 76 Lines • ▼ Show 20 Lines | |||||
/** SHA256("main address relay")[0:8] */ | /** SHA256("main address relay")[0:8] */ | ||||
static constexpr uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL; | static constexpr uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL; | ||||
/// Age after which a stale block will no longer be served if requested as | /// Age after which a stale block will no longer be served if requested as | ||||
/// protection against fingerprinting. Set to one month, denominated in seconds. | /// protection against fingerprinting. Set to one month, denominated in seconds. | ||||
static constexpr int STALE_RELAY_AGE_LIMIT = 30 * 24 * 60 * 60; | static constexpr int STALE_RELAY_AGE_LIMIT = 30 * 24 * 60 * 60; | ||||
/// Age after which a block is considered historical for purposes of rate | /// Age after which a block is considered historical for purposes of rate | ||||
/// limiting block relay. Set to one week, denominated in seconds. | /// limiting block relay. Set to one week, denominated in seconds. | ||||
static constexpr int HISTORICAL_BLOCK_AGE = 7 * 24 * 60 * 60; | static constexpr int HISTORICAL_BLOCK_AGE = 7 * 24 * 60 * 60; | ||||
/** Maximum number of in-flight transactions from a peer */ | |||||
static constexpr int32_t MAX_PEER_TX_IN_FLIGHT = 100; | |||||
/** Maximum number of announced transactions from a peer */ | |||||
static constexpr int32_t MAX_PEER_TX_ANNOUNCEMENTS = 2 * MAX_INV_SZ; | |||||
/** How many microseconds to delay requesting transactions from inbound peers */ | |||||
static constexpr int64_t INBOUND_PEER_TX_DELAY = 2 * 1000000; | |||||
/** How long to wait (in microseconds) before downloading a transaction from an | |||||
* additional peer */ | |||||
static constexpr int64_t GETDATA_TX_INTERVAL = 60 * 1000000; | |||||
/** Maximum delay (in microseconds) for transaction requests to avoid biasing | |||||
* some peers over others. */ | |||||
static constexpr int64_t MAX_GETDATA_RANDOM_DELAY = 2 * 1000000; | |||||
static_assert(INBOUND_PEER_TX_DELAY >= MAX_GETDATA_RANDOM_DELAY, | |||||
"To preserve security, MAX_GETDATA_RANDOM_DELAY should not " | |||||
"exceed INBOUND_PEER_DELAY"); | |||||
/** Limit to avoid sending big packets. Not used in processing incoming GETDATA | |||||
* for compatibility */ | |||||
static const unsigned int MAX_GETDATA_SZ = 1000; | |||||
/// How many non standard orphan do we consider from a node before ignoring it. | /// How many non standard orphan do we consider from a node before ignoring it. | ||||
static constexpr uint32_t MAX_NON_STANDARD_ORPHAN_PER_NODE = 5; | static constexpr uint32_t MAX_NON_STANDARD_ORPHAN_PER_NODE = 5; | ||||
struct COrphanTx { | struct COrphanTx { | ||||
// When modifying, adapt the copy of this definition in tests/DoS_tests. | // When modifying, adapt the copy of this definition in tests/DoS_tests. | ||||
CTransactionRef tx; | CTransactionRef tx; | ||||
NodeId fromPeer; | NodeId fromPeer; | ||||
▲ Show 20 Lines • Show All 196 Lines • ▼ Show 20 Lines | struct ChainSyncTimeoutState { | ||||
bool m_protect; | bool m_protect; | ||||
}; | }; | ||||
ChainSyncTimeoutState m_chain_sync; | ChainSyncTimeoutState m_chain_sync; | ||||
//! Time of last new block announcement | //! Time of last new block announcement | ||||
int64_t m_last_block_announcement; | int64_t m_last_block_announcement; | ||||
/* | |||||
* State associated with transaction download. | |||||
* | |||||
* Tx download algorithm: | |||||
* | |||||
* When inv comes in, queue up (process_time, txid) inside the peer's | |||||
* CNodeState (m_tx_process_time) as long as m_tx_announced for the peer | |||||
* isn't too big (MAX_PEER_TX_ANNOUNCEMENTS). | |||||
* | |||||
* The process_time for a transaction is set to nNow for outbound peers, | |||||
* nNow + 2 seconds for inbound peers. This is the time at which we'll | |||||
* consider trying to request the transaction from the peer in | |||||
* SendMessages(). The delay for inbound peers is to allow outbound peers | |||||
* a chance to announce before we request from inbound peers, to prevent | |||||
* an adversary from using inbound connections to blind us to a | |||||
* transaction (InvBlock). | |||||
* | |||||
* When we call SendMessages() for a given peer, | |||||
* we will loop over the transactions in m_tx_process_time, looking | |||||
* at the transactions whose process_time <= nNow. We'll request each | |||||
* such transaction that we don't have already and that hasn't been | |||||
* requested from another peer recently, up until we hit the | |||||
* MAX_PEER_TX_IN_FLIGHT limit for the peer. Then we'll update | |||||
* g_already_asked_for for each requested txid, storing the time of the | |||||
* GETDATA request. We use g_already_asked_for to coordinate transaction | |||||
* requests amongst our peers. | |||||
* | |||||
* For transactions that we still need but we have already recently | |||||
* requested from some other peer, we'll reinsert (process_time, txid) | |||||
* back into the peer's m_tx_process_time at the point in the future at | |||||
* which the most recent GETDATA request would time out (ie | |||||
* GETDATA_TX_INTERVAL + the request time stored in g_already_asked_for). | |||||
* We add an additional delay for inbound peers, again to prefer | |||||
* attempting download from outbound peers first. | |||||
* We also add an extra small random delay up to 2 seconds | |||||
* to avoid biasing some peers over others. (e.g., due to fixed ordering | |||||
* of peer processing in ThreadMessageHandler). | |||||
* | |||||
* When we receive a transaction from a peer, we remove the txid from the | |||||
* peer's m_tx_in_flight set and from their recently announced set | |||||
* (m_tx_announced). We also clear g_already_asked_for for that entry, so | |||||
* that if somehow the transaction is not accepted but also not added to | |||||
* the reject filter, then we will eventually redownload from other | |||||
* peers. | |||||
*/ | |||||
struct TxDownloadState { | |||||
/** | |||||
* Track when to attempt download of announced transactions (process | |||||
* time in micros -> txid) | |||||
*/ | |||||
std::multimap<int64_t, TxId> m_tx_process_time; | |||||
//! Store all the transactions a peer has recently announced | |||||
std::set<uint256> m_tx_announced; | |||||
//! Store transactions which were requested by us | |||||
std::set<uint256> m_tx_in_flight; | |||||
}; | |||||
TxDownloadState m_tx_download; | |||||
CNodeState(CAddress addrIn, std::string addrNameIn) | CNodeState(CAddress addrIn, std::string addrNameIn) | ||||
: address(addrIn), name(addrNameIn) { | : address(addrIn), name(addrNameIn) { | ||||
fCurrentlyConnected = false; | fCurrentlyConnected = false; | ||||
nMisbehavior = 0; | nMisbehavior = 0; | ||||
fShouldBan = false; | fShouldBan = false; | ||||
pindexBestKnownBlock = nullptr; | pindexBestKnownBlock = nullptr; | ||||
hashLastUnknownBlock.SetNull(); | hashLastUnknownBlock.SetNull(); | ||||
pindexLastCommonBlock = nullptr; | pindexLastCommonBlock = nullptr; | ||||
Show All 10 Lines | CNodeState(CAddress addrIn, std::string addrNameIn) | ||||
fPreferHeaderAndIDs = false; | fPreferHeaderAndIDs = false; | ||||
fProvidesHeaderAndIDs = false; | fProvidesHeaderAndIDs = false; | ||||
fSupportsDesiredCmpctVersion = false; | fSupportsDesiredCmpctVersion = false; | ||||
m_chain_sync = {0, nullptr, false, false}; | m_chain_sync = {0, nullptr, false, false}; | ||||
m_last_block_announcement = 0; | m_last_block_announcement = 0; | ||||
} | } | ||||
}; | }; | ||||
// Keeps track of the time (in microseconds) when transactions were requested | |||||
// last time | |||||
limitedmap<TxId, int64_t> g_already_asked_for GUARDED_BY(cs_main)(MAX_INV_SZ); | |||||
/** Map maintaining per-node state. */ | /** Map maintaining per-node state. */ | ||||
static std::map<NodeId, CNodeState> mapNodeState GUARDED_BY(cs_main); | static std::map<NodeId, CNodeState> mapNodeState GUARDED_BY(cs_main); | ||||
static CNodeState *State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { | static CNodeState *State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { | ||||
std::map<NodeId, CNodeState>::iterator it = mapNodeState.find(pnode); | std::map<NodeId, CNodeState>::iterator it = mapNodeState.find(pnode); | ||||
if (it == mapNodeState.end()) { | if (it == mapNodeState.end()) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 362 Lines • ▼ Show 20 Lines | while (pindexWalk->nHeight < nMaxHeight) { | ||||
} else if (waitingfor == -1) { | } else if (waitingfor == -1) { | ||||
// This is the first already-in-flight block. | // This is the first already-in-flight block. | ||||
waitingfor = mapBlocksInFlight[pindex->GetBlockHash()].first; | waitingfor = mapBlocksInFlight[pindex->GetBlockHash()].first; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
void EraseTxRequest(const TxId &txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { | |||||
g_already_asked_for.erase(txid); | |||||
} | |||||
int64_t GetTxRequestTime(const TxId &txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { | |||||
auto it = g_already_asked_for.find(txid); | |||||
if (it != g_already_asked_for.end()) { | |||||
return it->second; | |||||
} | |||||
return 0; | |||||
} | |||||
void UpdateTxRequestTime(const TxId &txid, int64_t request_time) | |||||
EXCLUSIVE_LOCKS_REQUIRED(cs_main) { | |||||
auto it = g_already_asked_for.find(txid); | |||||
if (it == g_already_asked_for.end()) { | |||||
g_already_asked_for.insert(std::make_pair(txid, request_time)); | |||||
} else { | |||||
g_already_asked_for.update(it, request_time); | |||||
} | |||||
} | |||||
void RequestTx(CNodeState *state, const TxId &txid, int64_t nNow) | |||||
EXCLUSIVE_LOCKS_REQUIRED(cs_main) { | |||||
CNodeState::TxDownloadState &peer_download_state = state->m_tx_download; | |||||
if (peer_download_state.m_tx_announced.size() >= | |||||
MAX_PEER_TX_ANNOUNCEMENTS || | |||||
peer_download_state.m_tx_announced.count(txid)) { | |||||
// Too many queued announcements from this peer, or we already have | |||||
// this announcement | |||||
return; | |||||
} | |||||
peer_download_state.m_tx_announced.insert(txid); | |||||
int64_t process_time; | |||||
int64_t last_request_time = GetTxRequestTime(txid); | |||||
// First time requesting this tx | |||||
if (last_request_time == 0) { | |||||
process_time = nNow; | |||||
} else { | |||||
// Randomize the delay to avoid biasing some peers over others (such as | |||||
// due to fixed ordering of peer processing in ThreadMessageHandler) | |||||
process_time = last_request_time + GETDATA_TX_INTERVAL + | |||||
GetRand(MAX_GETDATA_RANDOM_DELAY); | |||||
} | |||||
// We delay processing announcements from non-preferred (eg inbound) peers | |||||
if (!state->fPreferredDownload) { | |||||
process_time += INBOUND_PEER_TX_DELAY; | |||||
} | |||||
peer_download_state.m_tx_process_time.emplace(process_time, txid); | |||||
} | |||||
} // namespace | } // namespace | ||||
// This function is used for testing the stale tip eviction logic, see | // This function is used for testing the stale tip eviction logic, see | ||||
// denialofservice_tests.cpp | // denialofservice_tests.cpp | ||||
void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) { | void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
CNodeState *state = State(node); | CNodeState *state = State(node); | ||||
if (state) { | if (state) { | ||||
▲ Show 20 Lines • Show All 1,544 Lines • ▼ Show 20 Lines | else if (strCommand == NetMsgType::INV) { | ||||
// mode if whitelistrelay is true | // mode if whitelistrelay is true | ||||
if (pfrom->fWhitelisted && | if (pfrom->fWhitelisted && | ||||
gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) { | gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) { | ||||
fBlocksOnly = false; | fBlocksOnly = false; | ||||
} | } | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
for (auto &inv : vInv) { | int64_t nNow = GetTimeMicros(); | ||||
for (CInv &inv : vInv) { | |||||
if (interruptMsgProc) { | if (interruptMsgProc) { | ||||
return true; | return true; | ||||
} | } | ||||
bool fAlreadyHave = AlreadyHave(inv); | bool fAlreadyHave = AlreadyHave(inv); | ||||
LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), | LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), | ||||
fAlreadyHave ? "have" : "new", pfrom->GetId()); | fAlreadyHave ? "have" : "new", pfrom->GetId()); | ||||
Show All 21 Lines | else if (strCommand == NetMsgType::INV) { | ||||
pfrom->AddInventoryKnown(inv); | pfrom->AddInventoryKnown(inv); | ||||
if (fBlocksOnly) { | if (fBlocksOnly) { | ||||
LogPrint(BCLog::NET, | LogPrint(BCLog::NET, | ||||
"transaction (%s) inv sent in violation of " | "transaction (%s) inv sent in violation of " | ||||
"protocol peer=%d\n", | "protocol peer=%d\n", | ||||
inv.hash.ToString(), pfrom->GetId()); | inv.hash.ToString(), pfrom->GetId()); | ||||
} else if (!fAlreadyHave && !fImporting && !fReindex && | } else if (!fAlreadyHave && !fImporting && !fReindex && | ||||
!IsInitialBlockDownload()) { | !IsInitialBlockDownload()) { | ||||
pfrom->AskFor(inv); | RequestTx(State(pfrom->GetId()), TxId(inv.hash), nNow); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
else if (strCommand == NetMsgType::GETDATA) { | else if (strCommand == NetMsgType::GETDATA) { | ||||
std::vector<CInv> vInv; | std::vector<CInv> vInv; | ||||
vRecv >> vInv; | vRecv >> vInv; | ||||
▲ Show 20 Lines • Show All 252 Lines • ▼ Show 20 Lines | else if (strCommand == NetMsgType::TX) { | ||||
pfrom->AddInventoryKnown(inv); | pfrom->AddInventoryKnown(inv); | ||||
LOCK2(cs_main, g_cs_orphans); | LOCK2(cs_main, g_cs_orphans); | ||||
bool fMissingInputs = false; | bool fMissingInputs = false; | ||||
CValidationState state; | CValidationState state; | ||||
const TxId txid(inv.hash); | const TxId txid(inv.hash); | ||||
pfrom->setAskFor.erase(txid); | CNodeState *nodestate = State(pfrom->GetId()); | ||||
mapAlreadyAskedFor.erase(txid); | nodestate->m_tx_download.m_tx_announced.erase(txid); | ||||
nodestate->m_tx_download.m_tx_in_flight.erase(txid); | |||||
EraseTxRequest(txid); | |||||
std::list<CTransactionRef> lRemovedTxn; | |||||
if (!AlreadyHave(inv) && | if (!AlreadyHave(inv) && | ||||
AcceptToMemoryPool(config, g_mempool, state, ptx, true, | AcceptToMemoryPool(config, g_mempool, state, ptx, true, | ||||
&fMissingInputs)) { | &fMissingInputs)) { | ||||
g_mempool.check(pcoinsTip.get()); | g_mempool.check(pcoinsTip.get()); | ||||
RelayTransaction(tx, connman); | RelayTransaction(tx, connman); | ||||
for (size_t i = 0; i < tx.vout.size(); i++) { | for (size_t i = 0; i < tx.vout.size(); i++) { | ||||
vWorkQueue.emplace_back(txid, i); | vWorkQueue.emplace_back(txid, i); | ||||
▲ Show 20 Lines • Show All 86 Lines • ▼ Show 20 Lines | else if (strCommand == NetMsgType::TX) { | ||||
bool fRejectedParents = false; | bool fRejectedParents = false; | ||||
for (const CTxIn &txin : tx.vin) { | for (const CTxIn &txin : tx.vin) { | ||||
if (recentRejects->contains(txin.prevout.GetTxId())) { | if (recentRejects->contains(txin.prevout.GetTxId())) { | ||||
fRejectedParents = true; | fRejectedParents = true; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
if (!fRejectedParents) { | if (!fRejectedParents) { | ||||
int64_t nNow = GetTimeMicros(); | |||||
for (const CTxIn &txin : tx.vin) { | for (const CTxIn &txin : tx.vin) { | ||||
// FIXME: MSG_TX should use a TxHash, not a TxId. | // FIXME: MSG_TX should use a TxHash, not a TxId. | ||||
CInv _inv(MSG_TX, txin.prevout.GetTxId()); | const TxId _txid = txin.prevout.GetTxId(); | ||||
CInv _inv(MSG_TX, _txid); | |||||
pfrom->AddInventoryKnown(_inv); | pfrom->AddInventoryKnown(_inv); | ||||
if (!AlreadyHave(_inv)) { | if (!AlreadyHave(_inv)) { | ||||
pfrom->AskFor(_inv); | RequestTx(State(pfrom->GetId()), _txid, nNow); | ||||
} | } | ||||
} | } | ||||
AddOrphanTx(ptx, pfrom->GetId()); | AddOrphanTx(ptx, pfrom->GetId()); | ||||
// DoS prevention: do not allow mapOrphanTransactions to grow | // DoS prevention: do not allow mapOrphanTransactions to grow | ||||
// unbounded | // unbounded | ||||
unsigned int nMaxOrphanTx = (unsigned int)std::max( | unsigned int nMaxOrphanTx = (unsigned int)std::max( | ||||
int64_t(0), gArgs.GetArg("-maxorphantx", | int64_t(0), gArgs.GetArg("-maxorphantx", | ||||
▲ Show 20 Lines • Show All 1,689 Lines • ▼ Show 20 Lines | if (!pto->fClient && | ||||
LogPrint(BCLog::NET, "Stall started peer=%d\n", staller); | LogPrint(BCLog::NET, "Stall started peer=%d\n", staller); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// | // | ||||
// Message: getdata (non-blocks) | // Message: getdata (non-blocks) | ||||
// | // | ||||
while (!pto->mapAskFor.empty() && (*pto->mapAskFor.begin()).first <= nNow) { | auto &tx_process_time = state.m_tx_download.m_tx_process_time; | ||||
const CInv &inv = (*pto->mapAskFor.begin()).second; | while (!tx_process_time.empty() && tx_process_time.begin()->first <= nNow && | ||||
state.m_tx_download.m_tx_in_flight.size() < MAX_PEER_TX_IN_FLIGHT) { | |||||
const TxId &txid = tx_process_time.begin()->second; | |||||
CInv inv(MSG_TX, txid); | |||||
if (!AlreadyHave(inv)) { | if (!AlreadyHave(inv)) { | ||||
// If this transaction was last requested more than 1 minute ago, | |||||
// then request. | |||||
int64_t last_request_time = GetTxRequestTime(txid); | |||||
if (last_request_time <= nNow - GETDATA_TX_INTERVAL) { | |||||
LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), | LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), | ||||
pto->GetId()); | pto->GetId()); | ||||
vGetData.push_back(inv); | vGetData.push_back(inv); | ||||
if (vGetData.size() >= 1000) { | if (vGetData.size() >= MAX_GETDATA_SZ) { | ||||
connman->PushMessage( | connman->PushMessage( | ||||
pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); | pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); | ||||
vGetData.clear(); | vGetData.clear(); | ||||
} | } | ||||
UpdateTxRequestTime(txid, nNow); | |||||
state.m_tx_download.m_tx_in_flight.insert(inv.hash); | |||||
} else { | } else { | ||||
// If we're not going to ask, don't expect a response. | // This transaction is in flight from someone else; queue | ||||
pto->setAskFor.erase(inv.hash); | // up processing to happen after the download times out | ||||
// (with a slight delay for inbound peers, to prefer | |||||
// requests to outbound peers). | |||||
RequestTx(&state, txid, nNow); | |||||
} | } | ||||
pto->mapAskFor.erase(pto->mapAskFor.begin()); | } else { | ||||
// We have already seen this transaction, no need to download. | |||||
state.m_tx_download.m_tx_announced.erase(inv.hash); | |||||
state.m_tx_download.m_tx_in_flight.erase(inv.hash); | |||||
} | } | ||||
tx_process_time.erase(tx_process_time.begin()); | |||||
} | |||||
if (!vGetData.empty()) { | if (!vGetData.empty()) { | ||||
connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); | connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); | ||||
} | } | ||||
// | // | ||||
// Message: feefilter | // Message: feefilter | ||||
// | // | ||||
// We don't want white listed peers to filter txs to us if we have | // We don't want white listed peers to filter txs to us if we have | ||||
▲ Show 20 Lines • Show All 55 Lines • Show Last 20 Lines |