diff --git a/src/main.cpp b/src/main.cpp index 220c0aa0b..8559372ca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6841,9 +6841,6 @@ void static ProcessGetData(CNode* pfrom) } } - // Track requests for our stuff. - GetMainSignals().Inventory(inv.hash); - if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK) break; } @@ -7000,6 +6997,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, { pfrom->PushMessage(NetMsgType::GETADDR); pfrom->fGetAddr = true; + // pfrom->m_getaddr_sent = true; + // When requesting a getaddr, accept an additional MAX_ADDR_TO_SEND addresses in response + // (bypassing the MAX_ADDR_PROCESSING_TOKEN_BUCKET limit). + pfrom->m_addr_token_bucket += MAX_ADDR_TO_SEND; } addrman.Good(pfrom->addr); } else { @@ -7103,15 +7104,37 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, vector vAddrOk; int64_t nNow = GetTime(); int64_t nSince = nNow - 10 * 60; + + // Update/increment addr rate limiting bucket. + const int64_t current_time = GetTimeMicros(); + if (pfrom->m_addr_token_bucket < MAX_ADDR_PROCESSING_TOKEN_BUCKET) { + // Don't increment bucket if it's already full + const auto time_diff = std::max(current_time - pfrom->m_addr_token_timestamp, (int64_t) 0); + const double increment = (time_diff / 1000000) * MAX_ADDR_RATE_PER_SECOND; + pfrom->m_addr_token_bucket = std::min(pfrom->m_addr_token_bucket + increment, MAX_ADDR_PROCESSING_TOKEN_BUCKET); + } + pfrom->m_addr_token_timestamp = current_time; + + uint64_t num_proc = 0; + uint64_t num_rate_limit = 0; + BOOST_FOREACH(CAddress& addr, vAddr) { boost::this_thread::interruption_point(); + // Apply rate limiting if the address is not allowlisted + if (!pfrom->IsAllowlistedRange(addr)) { + if (pfrom->m_addr_token_bucket < 1.0) break; + pfrom->m_addr_token_bucket -= 1.0; + } + if(p2pdebug) fprintf(stderr,"%s: %s.nTime=%d\n", __func__, addr.ToString().c_str(), addr.nTime); if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60) addr.nTime = nNow - 5 * 24 * 60 * 60; - pfrom->AddAddressKnown(addr); + pfrom->AddAddressIfNotAlreadyKnown(addr); + + ++num_proc; bool fReachable = IsReachable(addr); if (addr.nTime > nSince && !pfrom->fGetAddr && vAddr.size() <= 10 && addr.IsRoutable()) { @@ -7150,6 +7173,15 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, fprintf(stderr,"%s: %s with nTime=%d is not reachable\n",__func__,addr.ToString().c_str(), addr.nTime); } } + pfrom->m_addr_processed += num_proc; + pfrom->m_addr_rate_limited += num_rate_limit; + LogPrintf("ProcessMessage: Received addr: %u addresses (%u processed, %u rate-limited) from peer=%d%s\n", + vAddr.size(), + num_proc, + num_rate_limit, + pfrom->GetId(), + fLogIPs ? ", peeraddr=" + pfrom->addr.ToString() : ""); + addrman.Add(vAddrOk, pfrom->addr, 2 * 60 * 60); if (vAddr.size() < 1000) pfrom->fGetAddr = false; @@ -7320,9 +7352,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, } } - // Track requests for our stuff - GetMainSignals().Inventory(inv.hash); - if (pfrom->nSendSize > (SendBufferSize() * 2)) { Misbehaving(pfrom->GetId(), 50); return error("send buffer size() = %u", pfrom->nSendSize); @@ -7960,9 +7989,8 @@ bool SendMessages(CNode* pto, bool fSendTrickle) vAddr.reserve(pto->vAddrToSend.size()); BOOST_FOREACH(const CAddress& addr, pto->vAddrToSend) { - if (!pto->addrKnown.contains(addr.GetKey())) + if (pto->AddAddressIfNotAlreadyKnown(addr)) { - pto->addrKnown.insert(addr.GetKey()); vAddr.push_back(addr); if (vAddr.size() >= MAX_ADDR_TO_SEND) diff --git a/src/miner.cpp b/src/miner.cpp index f677d6b43..4e5e01216 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -924,13 +924,6 @@ static bool ProcessBlockFound(CBlock* pblock) reservekey.KeepKey(); } } - // Track how many getdata requests this block gets - //if ( 0 ) - { - //fprintf(stderr,"lock cs_wallet\n"); - LOCK(wallet.cs_wallet); - wallet.mapRequestCount[pblock->GetHash()] = 0; - } #endif //fprintf(stderr,"process new block\n"); diff --git a/src/net.cpp b/src/net.cpp index e8f693c11..b460f3c37 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -556,6 +556,10 @@ void CNode::CloseSocketDisconnect() CloseSocket(hSocket); } } + { + LOCK(cs_addrKnown); + addrKnown.reset(); + } // in case this fails, we'll empty the recv buffer when the CNode is deleted TRY_LOCK(cs_vRecvMsg, lockRecv); @@ -809,6 +813,9 @@ void CNode::copyStats(CNodeStats &stats, const std::vector &m_asmap) stats.dPingTime = (((double)nPingUsecTime) / 1e6); stats.dMinPing = (((double)nMinPingUsecTime) / 1e6); stats.dPingWait = (((double)nPingUsecWait) / 1e6); + stats.m_addr_processed = m_addr_processed.load(); + stats.m_addr_rate_limited = m_addr_rate_limited.load(); + // Leave string empty if addrLocal invalid (not filled in yet) stats.addrLocal = addrLocal.IsValid() ? addrLocal.ToString() : ""; @@ -1945,6 +1952,11 @@ void ThreadMessageHandler() bool fSleep = true; + // Randomize the order in which we process messages from/to our peers. + // This prevents attacks in which an attacker exploits having multiple + // consecutive connections in the vNodes list. + random_shuffle(vNodesCopy.begin(), vNodesCopy.end(), GetRandInt); + BOOST_FOREACH(CNode* pnode, vNodesCopy) { if (pnode->fDisconnect) diff --git a/src/net.h b/src/net.h index d89ecbc00..95e1c58f0 100644 --- a/src/net.h +++ b/src/net.h @@ -72,6 +72,13 @@ static const int TIMEOUT_INTERVAL = 20 * 60; static const unsigned int MAX_INV_SZ = 50000; /** The maximum number of new addresses to accumulate before announcing. */ static const unsigned int MAX_ADDR_TO_SEND = 1000; +/** The maximum rate of address records we're willing to process on average. Can be bypassed using + * the NetPermissionFlags::Addr permission. */ +static constexpr double MAX_ADDR_RATE_PER_SECOND{0.1}; +/** The soft limit of the address processing token bucket (the regular MAX_ADDR_RATE_PER_SECOND + * based increments won't go above this, but the MAX_ADDR_TO_SEND increment following GETADDR + * is exempt from this limit. */ +static constexpr size_t MAX_ADDR_PROCESSING_TOKEN_BUCKET{MAX_ADDR_TO_SEND}; /** Maximum length of incoming protocol messages (no message over 2 MiB is currently acceptable). */ static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = (_MAX_BLOCK_SIZE + 24); // 24 is msgheader size /** Maximum length of strSubVer in `version` message */ @@ -295,6 +302,9 @@ public: // CAddress addrBind; // https://github.com/bitcoin/bitcoin/commit/a7e3c2814c8e49197889a4679461be42254e5c51 uint32_t m_mapped_as; + uint64_t m_addr_processed{0}; + uint64_t m_addr_rate_limited{0}; + /** * Whether the peer has signaled support for receiving ADDRv2 (BIP155) * messages, implying a preference to receive ADDRv2 instead of ADDR ones. @@ -302,9 +312,6 @@ public: bool m_wants_addrv2; }; - - - class CNetMessage { public: bool in_data; // parsing header (false) or data (true) @@ -425,6 +432,8 @@ public: CCriticalSection cs_filter; CBloomFilter* pfilter; int nRefCount; + CRollingBloomFilter addrKnown; + mutable CCriticalSection cs_addrKnown; NodeId id; /** @@ -433,6 +442,15 @@ public: */ bool m_wants_addrv2{false}; + /** Number of addr messages that can be processed from this peer. Start at 1 to permit self-announcement. */ + double m_addr_token_bucket{1.0}; + /** When m_addr_token_bucket was last updated */ + int64_t m_addr_token_timestamp{GetTimeMicros()}; + /** Total number of addresses that were dropped due to rate limiting. */ + std::atomic m_addr_rate_limited{0}; + /** Total number of addresses that were processed (excludes rate limited ones). */ + std::atomic m_addr_processed{0}; + protected: // Denial-of-service detection/prevention @@ -462,7 +480,6 @@ public: // flood relay std::vector vAddrToSend; - CRollingBloomFilter addrKnown; bool fGetAddr; std::set setKnown; @@ -545,11 +562,25 @@ public: nRefCount--; } + bool AddAddressIfNotAlreadyKnown(const CAddress& addr) + { + LOCK(cs_addrKnown); + // Avoid adding to addrKnown after it has been reset in CloseSocketDisconnect. + if (fDisconnect) { + return false; + } + if (!addrKnown.contains(addr.GetKey())) { + addrKnown.insert(addr.GetKey()); + return true; + } else { + return false; + } + } - - void AddAddressKnown(const CAddress& _addr) + bool IsAddressKnown(const CAddress& addr) const { - addrKnown.insert(_addr.GetKey()); + LOCK(cs_addrKnown); + return addrKnown.contains(addr.GetKey()); } void PushAddress(const CAddress& _addr) @@ -562,7 +593,8 @@ public: // Known checking here is only to save space from duplicates. // SendMessages will filter it again for knowns that were added // after addresses were pushed. - if (_addr.IsValid() && !addrKnown.contains(_addr.GetKey()) && addr_format_supported) { + if (_addr.IsValid() && !IsAddressKnown(addr) && addr_format_supported) { + if (vAddrToSend.size() >= MAX_ADDR_TO_SEND) { vAddrToSend[insecure_rand() % vAddrToSend.size()] = _addr; } else { diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 8e6c539f2..67812ddb6 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -204,6 +204,8 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp, const CPubKey& mypk) obj.push_back(Pair("inflight", heights)); } obj.push_back(Pair("allowlisted", stats.fAllowlisted)); + obj.pushKV("addr_processed", stats.m_addr_processed); + obj.pushKV("addr_rate_limited", stats.m_addr_rate_limited); ret.push_back(obj); } diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index a5eb77e94..d16b235bf 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -31,19 +31,15 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) { g_signals.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1)); g_signals.ChainTip.connect(boost::bind(&CValidationInterface::ChainTip, pwalletIn, _1, _2, _3)); g_signals.SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1)); - g_signals.Inventory.connect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1)); g_signals.Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1)); g_signals.BlockChecked.connect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2)); //g_signals.ScriptForMining.connect(boost::bind(&CValidationInterface::GetScriptForMining, pwalletIn, _1)); - g_signals.BlockFound.connect(boost::bind(&CValidationInterface::ResetRequestCount, pwalletIn, _1)); } void UnregisterValidationInterface(CValidationInterface* pwalletIn) { - g_signals.BlockFound.disconnect(boost::bind(&CValidationInterface::ResetRequestCount, pwalletIn, _1)); //g_signals.ScriptForMining.disconnect(boost::bind(&CValidationInterface::GetScriptForMining, pwalletIn, _1)); g_signals.BlockChecked.disconnect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2)); g_signals.Broadcast.disconnect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1)); - g_signals.Inventory.disconnect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1)); g_signals.ChainTip.disconnect(boost::bind(&CValidationInterface::ChainTip, pwalletIn, _1, _2, _3)); g_signals.SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1)); g_signals.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1)); @@ -57,7 +53,6 @@ void UnregisterAllValidationInterfaces() { //g_signals.ScriptForMining.disconnect_all_slots(); g_signals.BlockChecked.disconnect_all_slots(); g_signals.Broadcast.disconnect_all_slots(); - g_signals.Inventory.disconnect_all_slots(); g_signals.ChainTip.disconnect_all_slots(); g_signals.SetBestChain.disconnect_all_slots(); g_signals.UpdatedTransaction.disconnect_all_slots(); diff --git a/src/validationinterface.h b/src/validationinterface.h index 122976f51..e815b6d57 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -40,10 +40,8 @@ protected: virtual void ChainTip(const CBlockIndex *pindex, const CBlock *pblock, boost::optional> added) {} virtual void SetBestChain(const CBlockLocator &locator) {} virtual void UpdatedTransaction(const uint256 &hash) {} - virtual void Inventory(const uint256 &hash) {} virtual void ResendWalletTransactions(int64_t nBestBlockTime) {} virtual void BlockChecked(const CBlock&, const CValidationState&) {} - virtual void ResetRequestCount(const uint256 &hash) {}; friend void ::RegisterValidationInterface(CValidationInterface*); friend void ::UnregisterValidationInterface(CValidationInterface*); friend void ::UnregisterAllValidationInterfaces(); @@ -64,8 +62,6 @@ struct CMainSignals { boost::signals2::signal>)> ChainTip; /** Notifies listeners of a new active block chain. */ boost::signals2::signal SetBestChain; - /** Notifies listeners about an inventory item being seen on the network. */ - boost::signals2::signal Inventory; /** Tells listeners to broadcast their data. */ boost::signals2::signal Broadcast; /** Notifies listeners of a block validation result */ diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 88ff8c73b..84b5d1b95 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2273,7 +2273,10 @@ int64_t CWalletTx::GetTxTime() const return n ? n : nTimeReceived; } +<<<<<<< HEAD +======= +>>>>>>> origin/dev // GetAmounts will determine the transparent debits and credits for a given wallet tx. void CWalletTx::GetAmounts(list& listReceived, list& listSent, CAmount& nFee, string& strSentAccount, const isminefilter& filter) const @@ -4044,9 +4047,6 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) delete pwalletdb; } - // Track how many getdata requests our transaction gets - mapRequestCount[wtxNew.GetHash()] = 0; - std::string strCmd = GetArg("-txsend", ""); if (fBroadcastTransactions) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index b2d29d3f9..c3c105b71 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1016,7 +1016,6 @@ public: std::map mapWallet; int64_t nOrderPosNext; - std::map mapRequestCount; std::map mapAddressBook; @@ -1235,22 +1234,7 @@ public: void UpdatedTransaction(const uint256 &hashTx); - void Inventory(const uint256 &hash) - { - { - LOCK(cs_wallet); - std::map::iterator mi = mapRequestCount.find(hash); - if (mi != mapRequestCount.end()) - (*mi).second++; - } - } - //void GetScriptForMining(boost::shared_ptr &script); - void ResetRequestCount(const uint256 &hash) - { - LOCK(cs_wallet); - mapRequestCount[hash] = 0; - }; unsigned int GetKeyPoolSize() {