diff --git a/src/avalanche/peermanager.cpp b/src/avalanche/peermanager.cpp index 0f68e60b8..5389138d0 100644 --- a/src/avalanche/peermanager.cpp +++ b/src/avalanche/peermanager.cpp @@ -1,226 +1,224 @@ // Copyright (c) 2020 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include PeerId PeerManager::addPeer(PeerId p, uint32_t score) { - auto inserted = peerIndices.emplace(p, slots.size()); + auto inserted = peers.emplace(p, Peer(score, uint32_t(slots.size()))); assert(inserted.second); const uint64_t start = slotCount; slots.emplace_back(start, score, p); slotCount = start + score; return p; } bool PeerManager::removePeer(PeerId p) { - auto it = peerIndices.find(p); - if (it == peerIndices.end()) { + auto it = peers.find(p); + if (it == peers.end()) { return false; } - size_t i = it->second; + size_t i = it->second.index; assert(i < slots.size()); if (i + 1 == slots.size()) { slots.pop_back(); slotCount = slots.empty() ? 0 : slots.back().getStop(); } else { fragmentation += slots[i].getScore(); slots[i] = slots[i].withPeerId(NO_PEER); } - peerIndices.erase(it); + peers.erase(it); return true; } bool PeerManager::rescorePeer(PeerId p, uint32_t score) { - auto it = peerIndices.find(p); - if (it == peerIndices.end()) { + auto it = peers.find(p); + if (it == peers.end()) { return false; } - size_t i = it->second; + size_t i = it->second.index; assert(i < slots.size()); + // Update the peer's score. + it->second.score = score; + + // Update the slot allocation to reflect the new score. const uint64_t start = slots[i].getStart(); // If this is the last element, we can extend/shrink easily. if (i + 1 == slots.size()) { slots[i] = slots[i].withScore(score); slotCount = slots[i].getStop(); return true; } const uint64_t stop = start + score; const uint64_t nextStart = slots[i + 1].getStart(); // We can extend in place. if (stop <= nextStart) { fragmentation += (slots[i].getStop() - stop); slots[i] = slots[i].withScore(score); return true; } // So we need to insert a new entry. fragmentation += slots[i].getScore(); slots[i] = slots[i].withPeerId(NO_PEER); - it->second = slots.size(); + it->second.index = uint32_t(slots.size()); const uint64_t newStart = slotCount; slots.emplace_back(newStart, score, p); slotCount = newStart + score; return true; } PeerId PeerManager::selectPeer() const { if (slots.empty() || slotCount == 0) { return NO_PEER; } const uint64_t max = slotCount; for (int retry = 0; retry < SELECT_PEER_MAX_RETRY; retry++) { size_t i = selectPeerImpl(slots, GetRand(max), max); if (i != NO_PEER) { return i; } } return NO_PEER; } uint64_t PeerManager::compact() { - auto clearLastElements = [this] { - while (!slots.empty() && slots.back().getPeerId() == NO_PEER) { - slots.pop_back(); - } - }; - - // Always make sure that the last element is not dead. - clearLastElements(); + // Shrink the vector to the expected size. + while (slots.size() > peers.size()) { + slots.pop_back(); + } uint64_t prevStop = 0; - for (size_t i = 0; i < slots.size(); i++) { - if (slots[i].getPeerId() != NO_PEER) { - // This element is good, just slide it to the right position. - slots[i] = slots[i].withStart(prevStop); - prevStop = slots[i].getStop(); - continue; - } + uint32_t i = 0; + for (auto &pair : peers) { + PeerId pid = pair.first; + Peer &p = pair.second; - // This element is dead, put the last one it its place. - slots[i] = slots.back().withStart(prevStop); + slots[i] = Slot(prevStop, p.score, pid); prevStop = slots[i].getStop(); - assert(slots[i].getPeerId() != NO_PEER); - peerIndices[slots[i].getPeerId()] = i; - - slots.pop_back(); - clearLastElements(); + p.index = i++; } const uint64_t saved = slotCount - prevStop; slotCount = prevStop; fragmentation = 0; return saved; } bool PeerManager::verify() const { uint64_t prevStop = 0; for (size_t i = 0; i < slots.size(); i++) { const Slot &s = slots[i]; // Slots must be in correct order. if (s.getStart() < prevStop) { return false; } prevStop = s.getStop(); // If this is a dead slot, then nothing more needs to be checked. if (s.getPeerId() == NO_PEER) { continue; } // We have a live slot, verify index. - auto it = peerIndices.find(s.getPeerId()); - if (it == peerIndices.end() || it->second != i) { + auto it = peers.find(s.getPeerId()); + if (it == peers.end() || it->second.index != i) { return false; } } - for (const auto &pair : peerIndices) { + for (const auto &pair : peers) { auto p = pair.first; - auto i = pair.second; + auto i = pair.second.index; + auto s = pair.second.score; // The index must point to a slot refering to this peer. if (i >= slots.size() || slots[i].getPeerId() != p) { return false; } + + // If the score do not match, same thing. + if (slots[i].getScore() != s) { + return false; + } } return true; } PeerId selectPeerImpl(const std::vector &slots, const uint64_t slot, const uint64_t max) { assert(slot <= max); size_t begin = 0, end = slots.size(); uint64_t bottom = 0, top = max; // Try to find the slot using dichotomic search. while ((end - begin) > 8) { // The slot we picked in not allocated. if (slot < bottom || slot >= top) { return NO_PEER; } // Guesstimate the position of the slot. size_t i = begin + ((slot - bottom) * (end - begin) / (top - bottom)); assert(begin <= i && i < end); // We have a match. if (slots[i].contains(slot)) { return slots[i].getPeerId(); } // We undershooted. if (slots[i].precedes(slot)) { begin = i + 1; if (begin >= end) { return NO_PEER; } bottom = slots[begin].getStart(); continue; } // We overshooted. if (slots[i].follows(slot)) { end = i; top = slots[end].getStart(); continue; } // We have an unalocated slot. return NO_PEER; } // Enough of that nonsense, let fallback to linear search. for (size_t i = begin; i < end; i++) { // We have a match. if (slots[i].contains(slot)) { return slots[i].getPeerId(); } } // We failed to find a slot, retry. return NO_PEER; } diff --git a/src/avalanche/peermanager.h b/src/avalanche/peermanager.h index 0a54c537c..bd0ce20c6 100644 --- a/src/avalanche/peermanager.h +++ b/src/avalanche/peermanager.h @@ -1,90 +1,101 @@ // Copyright (c) 2020 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_AVALANCHE_PEERMANAGER_H #define BITCOIN_AVALANCHE_PEERMANAGER_H #include #include #include using PeerId = uint32_t; static constexpr PeerId NO_PEER = ~uint32_t(0); struct Slot { private: uint64_t start; uint32_t score; PeerId peerid; public: Slot(uint64_t startIn, uint32_t scoreIn, PeerId peeridIn) : start(startIn), score(scoreIn), peerid(peeridIn) {} Slot withStart(uint64_t startIn) const { return Slot(startIn, score, peerid); } Slot withScore(uint64_t scoreIn) const { return Slot(start, scoreIn, peerid); } Slot withPeerId(PeerId peeridIn) const { return Slot(start, score, peeridIn); } uint64_t getStart() const { return start; } uint64_t getStop() const { return start + score; } uint32_t getScore() const { return score; } uint32_t getPeerId() const { return peerid; } bool contains(uint64_t slot) const { return getStart() <= slot && slot < getStop(); } bool precedes(uint64_t slot) const { return slot >= getStop(); } bool follows(uint64_t slot) const { return getStart() > slot; } }; class PeerManager { std::vector slots; uint64_t slotCount = 0; uint64_t fragmentation = 0; + /** + * Several nodes can make an avalanche peer. In this case, all nodes are + * considered interchangeable parts of the same peer. + */ + struct Peer { + uint32_t score; + uint32_t index; + + Peer(uint32_t score_, uint32_t index_) : score(score_), index(index_) {} + }; + PeerId nextPeerId = 0; - std::unordered_map peerIndices; + std::unordered_map peers; static constexpr int SELECT_PEER_MAX_RETRY = 3; public: PeerId addPeer(uint32_t score) { return addPeer(nextPeerId++, score); } bool removePeer(PeerId p); bool rescorePeer(PeerId p, uint32_t score); PeerId selectPeer() const; /** * Trigger maintenance of internal datastructures. * Returns how much slot space was saved after compaction. */ uint64_t compact(); /** * Perform consistency check on internal data structures. * Mostly useful for tests. */ bool verify() const; // Accssors. uint64_t getSlotCount() const { return slotCount; } uint64_t getFragmentation() const { return fragmentation; } private: PeerId addPeer(PeerId peerid, uint32_t score); }; /** * This is an internal method that is exposed for testing purposes. */ PeerId selectPeerImpl(const std::vector &slots, const uint64_t slot, const uint64_t max); #endif // BITCOIN_AVALANCHE_PEERMANAGER_H