diff --git a/src/avalanche/peermanager.cpp b/src/avalanche/peermanager.cpp index 87d94e325..c61fce611 100644 --- a/src/avalanche/peermanager.cpp +++ b/src/avalanche/peermanager.cpp @@ -1,760 +1,752 @@ // 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 #include #include #include // For ChainstateActive() #include #include namespace avalanche { bool PeerManager::addNode(NodeId nodeid, const ProofId &proofid) { auto &pview = peers.get(); auto it = pview.find(proofid); if (it == pview.end()) { // If the node exists, it is actually updating its proof to an unknown // one. In this case we need to remove it so it is not both active and // pending at the same time. removeNode(nodeid); pendingNodes.emplace(proofid, nodeid); return false; } return addOrUpdateNode(peers.project<0>(it), nodeid); } bool PeerManager::addOrUpdateNode(const PeerSet::iterator &it, NodeId nodeid) { assert(it != peers.end()); const PeerId peerid = it->peerid; auto nit = nodes.find(nodeid); if (nit == nodes.end()) { if (!nodes.emplace(nodeid, peerid).second) { return false; } } else { const PeerId oldpeerid = nit->peerid; if (!nodes.modify(nit, [&](Node &n) { n.peerid = peerid; })) { return false; } // We actually have this node already, we need to update it. bool success = removeNodeFromPeer(peers.find(oldpeerid)); assert(success); } bool success = addNodeToPeer(it); assert(success); // If the added node was in the pending set, remove it pendingNodes.get().erase(nodeid); return true; } bool PeerManager::addNodeToPeer(const PeerSet::iterator &it) { assert(it != peers.end()); return peers.modify(it, [&](Peer &p) { if (p.node_count++ > 0) { // We are done. return; } // We need to allocate this peer. p.index = uint32_t(slots.size()); const uint32_t score = p.getScore(); const uint64_t start = slotCount; slots.emplace_back(start, score, it->peerid); slotCount = start + score; // Add to our allocated score when we allocate a new peer in the slots connectedPeersScore += score; }); } bool PeerManager::removeNode(NodeId nodeid) { if (pendingNodes.get().erase(nodeid) > 0) { // If this was a pending node, there is nothing else to do. return true; } auto it = nodes.find(nodeid); if (it == nodes.end()) { return false; } const PeerId peerid = it->peerid; nodes.erase(it); // Keep the track of the reference count. bool success = removeNodeFromPeer(peers.find(peerid)); assert(success); return true; } bool PeerManager::removeNodeFromPeer(const PeerSet::iterator &it, uint32_t count) { // It is possible for nodes to be dangling. If there was an inflight query // when the peer gets removed, the node was not erased. In this case there // is nothing to do. if (it == peers.end()) { return true; } assert(count <= it->node_count); if (count == 0) { // This is a NOOP. return false; } const uint32_t new_count = it->node_count - count; if (!peers.modify(it, [&](Peer &p) { p.node_count = new_count; })) { return false; } if (new_count > 0) { // We are done. return true; } // There are no more nodes left, we need to clean up. Subtract allocated // score and remove from slots. const size_t i = it->index; assert(i < slots.size()); assert(connectedPeersScore >= slots[i].getScore()); connectedPeersScore -= slots[i].getScore(); 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); } return true; } bool PeerManager::updateNextRequestTime(NodeId nodeid, TimePoint timeout) { auto it = nodes.find(nodeid); if (it == nodes.end()) { return false; } return nodes.modify(it, [&](Node &n) { n.nextRequestTime = timeout; }); } static bool isOrphanState(const ProofValidationState &state) { return state.GetResult() == ProofValidationResult::MISSING_UTXO || state.GetResult() == ProofValidationResult::HEIGHT_MISMATCH; } bool PeerManager::updateNextPossibleConflictTime( PeerId peerid, const std::chrono::seconds &nextTime) { auto it = peers.find(peerid); if (it == peers.end()) { // No such peer return false; } // Make sure we don't move the time in the past. peers.modify(it, [&](Peer &p) { p.nextPossibleConflictTime = std::max(p.nextPossibleConflictTime, nextTime); }); return it->nextPossibleConflictTime == nextTime; } -void PeerManager::moveToConflictingPool(const ProofRef &proof) { +template +void PeerManager::moveToConflictingPool(const ProofContainer &proofs) { auto &peersView = peers.get(); - auto it = peersView.find(proof->getId()); - if (it != peersView.end()) { - removePeer(it->peerid); - } + for (const ProofRef &proof : proofs) { + auto it = peersView.find(proof->getId()); + if (it != peersView.end()) { + removePeer(it->peerid); + } - conflictingProofPool.addProofIfPreferred(proof); + conflictingProofPool.addProofIfPreferred(proof); + } } bool PeerManager::registerProof(const ProofRef &proof, ProofRegistrationState ®istrationState, RegistrationMode mode) { assert(proof); const ProofId &proofid = proof->getId(); auto invalidate = [&](ProofRegistrationResult result, const std::string &message) { return registrationState.Invalid( result, message, strprintf("proofid: %s", proofid.ToString())); }; if ((mode != RegistrationMode::FORCE_ACCEPT || !isInConflictingPool(proofid)) && exists(proofid)) { // In default mode, we expect the proof to be unknown, i.e. in none of // the pools. // In forced accept mode, the proof can be in the conflicting pool. return invalidate(ProofRegistrationResult::ALREADY_REGISTERED, "proof-already-registered"); } // Check the proof's validity. ProofValidationState validationState; if (!WITH_LOCK(cs_main, return proof->verify(validationState, ::ChainstateActive().CoinsTip()))) { if (isOrphanState(validationState)) { orphanProofPool.addProofIfPreferred(proof); return invalidate(ProofRegistrationResult::ORPHAN, "orphan-proof"); } // Reject invalid proof. return invalidate(ProofRegistrationResult::INVALID, "invalid-proof"); } auto now = GetTime(); auto nextCooldownTimePoint = now + std::chrono::seconds( gArgs.GetArg("-avalancheconflictingproofcooldown", AVALANCHE_DEFAULT_CONFLICTING_PROOF_COOLDOWN)); ProofPool::ConflictingProofSet conflictingProofs; switch (validProofPool.addProofIfNoConflict(proof, conflictingProofs)) { case ProofPool::AddProofStatus::REJECTED: { if (mode != RegistrationMode::FORCE_ACCEPT) { auto bestPossibleConflictTime = std::chrono::seconds(); auto &pview = peers.get(); for (auto &conflictingProof : conflictingProofs) { auto it = pview.find(conflictingProof->getId()); assert(it != pview.end()); // Search the most recent time over the peers bestPossibleConflictTime = std::max( bestPossibleConflictTime, it->nextPossibleConflictTime); updateNextPossibleConflictTime(it->peerid, nextCooldownTimePoint); } if (bestPossibleConflictTime > now) { // Cooldown not elapsed, reject the proof. return invalidate( ProofRegistrationResult::COOLDOWN_NOT_ELAPSED, "cooldown-not-elapsed"); } // If proof replacement is enabled, give the proof a chance to // replace the conflicting ones. if (gArgs.GetBoolArg( "-enableavalancheproofreplacement", AVALANCHE_DEFAULT_PROOF_REPLACEMENT_ENABLED)) { if (validProofPool.addProofIfPreferred(proof)) { // If we have overridden other proofs due to conflict, // remove the peers and attempt to move them to the // conflicting pool. - for (const ProofRef &conflictingProof : - conflictingProofs) { - moveToConflictingPool(conflictingProof); - } + moveToConflictingPool(conflictingProofs); // Replacement is successful, continue to peer creation break; } } // Not the preferred proof, or replacement is not enabled return conflictingProofPool.addProofIfPreferred(proof) == ProofPool::AddProofStatus::REJECTED ? invalidate(ProofRegistrationResult::REJECTED, "rejected-proof") : invalidate(ProofRegistrationResult::CONFLICTING, "conflicting-utxos"); } conflictingProofPool.removeProof(proofid); // Move the conflicting proofs from the valid pool to the // conflicting pool - auto &peersView = peers.get(); - for (const ProofRef &conflictingProof : conflictingProofs) { - auto it = peersView.find(conflictingProof->getId()); - if (it != peersView.end()) { - removePeer(it->peerid); - } - - conflictingProofPool.addProofIfPreferred(conflictingProof); - } + moveToConflictingPool(conflictingProofs); auto status = validProofPool.addProofIfNoConflict(proof); assert(status == ProofPool::AddProofStatus::SUCCEED); break; } case ProofPool::AddProofStatus::DUPLICATED: // If the proof was already in the pool, don't duplicate the peer. return invalidate(ProofRegistrationResult::ALREADY_REGISTERED, "proof-already-registered"); case ProofPool::AddProofStatus::SUCCEED: break; // No default case, so the compiler can warn about missing cases } // At this stage we are going to create a peer so the proof should never // exist in the conflicting pool, but use belt and suspenders. conflictingProofPool.removeProof(proofid); // New peer means new peerid! const PeerId peerid = nextPeerId++; // We have no peer for this proof, time to create it. auto inserted = peers.emplace(peerid, proof, nextCooldownTimePoint); assert(inserted.second); // Add to our registered score when adding to the peer list totalPeersScore += proof->getScore(); // If there are nodes waiting for this proof, add them auto &pendingNodesView = pendingNodes.get(); auto range = pendingNodesView.equal_range(proofid); // We want to update the nodes then remove them from the pending set. That // will invalidate the range iterators, so we need to save the node ids // first before we can loop over them. std::vector nodeids; nodeids.reserve(std::distance(range.first, range.second)); std::transform(range.first, range.second, std::back_inserter(nodeids), [](const PendingNode &n) { return n.nodeid; }); for (const NodeId &nodeid : nodeids) { addOrUpdateNode(inserted.first, nodeid); } return true; } bool PeerManager::rejectProof(const ProofId &proofid, RejectionMode mode) { if (!exists(proofid)) { return false; } if (orphanProofPool.removeProof(proofid)) { return true; } if (mode == RejectionMode::DEFAULT && conflictingProofPool.getProof(proofid)) { // In default mode we keep the proof in the conflicting pool return true; } if (mode == RejectionMode::INVALIDATE && conflictingProofPool.removeProof(proofid)) { // In invalidate mode we remove the proof completely return true; } auto &pview = peers.get(); auto it = pview.find(proofid); assert(it != pview.end()); const ProofRef proof = it->proof; if (!removePeer(it->peerid)) { return false; } // If there was conflicting proofs, attempt to pull them back for (const SignedStake &ss : proof->getStakes()) { const ProofRef conflictingProof = conflictingProofPool.getProof(ss.getStake().getUTXO()); if (!conflictingProof) { continue; } conflictingProofPool.removeProof(conflictingProof->getId()); registerProof(conflictingProof); } if (mode == RejectionMode::DEFAULT) { conflictingProofPool.addProofIfPreferred(proof); } return true; } NodeId PeerManager::selectNode() { for (int retry = 0; retry < SELECT_NODE_MAX_RETRY; retry++) { const PeerId p = selectPeer(); // If we cannot find a peer, it may be due to the fact that it is // unlikely due to high fragmentation, so compact and retry. if (p == NO_PEER) { compact(); continue; } // See if that peer has an available node. auto &nview = nodes.get(); auto it = nview.lower_bound(boost::make_tuple(p, TimePoint())); if (it != nview.end() && it->peerid == p && it->nextRequestTime <= std::chrono::steady_clock::now()) { return it->nodeid; } } // We failed to find a node to query, flag this so we can request more needMoreNodes = true; return NO_NODE; } void PeerManager::updatedBlockTip() { std::vector invalidProofIds; std::vector newOrphans; { LOCK(cs_main); const CCoinsViewCache &coins = ::ChainstateActive().CoinsTip(); for (const auto &p : peers) { ProofValidationState state; if (!p.proof->verify(state, coins)) { if (isOrphanState(state)) { newOrphans.push_back(p.proof); } invalidProofIds.push_back(p.getProofId()); } } } // Remove the invalid proofs before the orphans rescan. This makes it // possible to pull back proofs with utxos that conflicted with these // invalid proofs. for (const ProofId &invalidProofId : invalidProofIds) { rejectProof(invalidProofId, RejectionMode::INVALIDATE); } orphanProofPool.rescan(*this); for (auto &p : newOrphans) { orphanProofPool.addProofIfPreferred(p); } } ProofRef PeerManager::getProof(const ProofId &proofid) const { ProofRef proof = nullptr; forPeer(proofid, [&](const Peer &p) { proof = p.proof; return true; }); if (!proof) { proof = conflictingProofPool.getProof(proofid); } if (!proof) { proof = orphanProofPool.getProof(proofid); } return proof; } bool PeerManager::isBoundToPeer(const ProofId &proofid) const { auto &pview = peers.get(); return pview.find(proofid) != pview.end(); } bool PeerManager::isOrphan(const ProofId &proofid) const { return orphanProofPool.getProof(proofid) != nullptr; } bool PeerManager::isInConflictingPool(const ProofId &proofid) const { return conflictingProofPool.getProof(proofid) != nullptr; } bool PeerManager::removePeer(const PeerId peerid) { auto it = peers.find(peerid); if (it == peers.end()) { return false; } // Remove all nodes from this peer. removeNodeFromPeer(it, it->node_count); auto &nview = nodes.get(); // Add the nodes to the pending set auto range = nview.equal_range(peerid); for (auto &nit = range.first; nit != range.second; ++nit) { pendingNodes.emplace(it->getProofId(), nit->nodeid); }; // Remove nodes associated with this peer, unless their timeout is still // active. This ensure that we don't overquery them in case they are // subsequently added to another peer. nview.erase(nview.lower_bound(boost::make_tuple(peerid, TimePoint())), nview.upper_bound(boost::make_tuple( peerid, std::chrono::steady_clock::now()))); // Release UTXOs attached to this proof. validProofPool.removeProof(it->getProofId()); m_unbroadcast_proofids.erase(it->getProofId()); // Remove the peer from the PeerSet and remove its score from the registered // score total. assert(totalPeersScore >= it->getScore()); totalPeersScore -= it->getScore(); peers.erase(it); 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() { // There is nothing to compact. if (fragmentation == 0) { return 0; } std::vector newslots; newslots.reserve(peers.size()); uint64_t prevStop = 0; uint32_t i = 0; for (auto it = peers.begin(); it != peers.end(); it++) { if (it->node_count == 0) { continue; } newslots.emplace_back(prevStop, it->getScore(), it->peerid); prevStop = slots[i].getStop(); if (!peers.modify(it, [&](Peer &p) { p.index = i++; })) { return 0; } } slots = std::move(newslots); const uint64_t saved = slotCount - prevStop; slotCount = prevStop; fragmentation = 0; return saved; } bool PeerManager::verify() const { uint64_t prevStop = 0; uint32_t scoreFromSlots = 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 = peers.find(s.getPeerId()); if (it == peers.end() || it->index != i) { return false; } // Accumulate score across slots scoreFromSlots += slots[i].getScore(); } // Score across slots must be the same as our allocated score if (scoreFromSlots != connectedPeersScore) { return false; } uint32_t scoreFromAllPeers = 0; uint32_t scoreFromPeersWithNodes = 0; std::unordered_set peersUtxos; for (const auto &p : peers) { // Accumulate the score across peers to compare with total known score scoreFromAllPeers += p.getScore(); // A peer should have a proof attached if (!p.proof) { return false; } // Check proof pool consistency for (const auto &ss : p.proof->getStakes()) { const COutPoint &outpoint = ss.getStake().getUTXO(); auto proof = validProofPool.getProof(outpoint); if (!proof) { // Missing utxo return false; } if (proof != p.proof) { // Wrong proof return false; } if (!peersUtxos.emplace(outpoint).second) { // Duplicated utxo return false; } } // Count node attached to this peer. const auto count_nodes = [&]() { size_t count = 0; auto &nview = nodes.get(); auto begin = nview.lower_bound(boost::make_tuple(p.peerid, TimePoint())); auto end = nview.upper_bound(boost::make_tuple(p.peerid + 1, TimePoint())); for (auto it = begin; it != end; ++it) { count++; } return count; }; if (p.node_count != count_nodes()) { return false; } // If there are no nodes attached to this peer, then we are done. if (p.node_count == 0) { continue; } scoreFromPeersWithNodes += p.getScore(); // The index must point to a slot refering to this peer. if (p.index >= slots.size() || slots[p.index].getPeerId() != p.peerid) { return false; } // If the score do not match, same thing. if (slots[p.index].getScore() != p.getScore()) { return false; } } // Check our accumulated scores against our registred and allocated scores if (scoreFromAllPeers != totalPeersScore) { return false; } if (scoreFromPeersWithNodes != connectedPeersScore) { return false; } // We checked the utxo consistency for all our peers utxos already, so if // the pool size differs from the expected one there are dangling utxos. if (validProofPool.size() != peersUtxos.size()) { 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; } void PeerManager::addUnbroadcastProof(const ProofId &proofid) { // The proof should be bound to a peer if (isBoundToPeer(proofid)) { m_unbroadcast_proofids.insert(proofid); } } void PeerManager::removeUnbroadcastProof(const ProofId &proofid) { m_unbroadcast_proofids.erase(proofid); } } // namespace avalanche diff --git a/src/avalanche/peermanager.h b/src/avalanche/peermanager.h index 4992ab319..c01c289ca 100644 --- a/src/avalanche/peermanager.h +++ b/src/avalanche/peermanager.h @@ -1,376 +1,378 @@ // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace avalanche { class Delegation; namespace { struct TestPeerManager; } 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; } PeerId 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; } }; struct Peer { PeerId peerid; uint32_t index = -1; uint32_t node_count = 0; ProofRef proof; // The network stack uses timestamp in seconds, so we oblige. std::chrono::seconds registration_time; std::chrono::seconds nextPossibleConflictTime; Peer(PeerId peerid_, ProofRef proof_, std::chrono::seconds nextPossibleConflictTime_) : peerid(peerid_), proof(std::move(proof_)), registration_time(GetTime()), nextPossibleConflictTime(std::move(nextPossibleConflictTime_)) {} const ProofId &getProofId() const { return proof->getId(); } uint32_t getScore() const { return proof->getScore(); } }; struct proof_index { using result_type = ProofId; result_type operator()(const Peer &p) const { return p.proof->getId(); } }; struct score_index { using result_type = uint32_t; result_type operator()(const Peer &p) const { return p.getScore(); } }; struct next_request_time {}; struct PendingNode { ProofId proofid; NodeId nodeid; PendingNode(ProofId proofid_, NodeId nodeid_) : proofid(proofid_), nodeid(nodeid_){}; }; struct by_proofid; struct by_nodeid; struct by_score; enum class ProofRegistrationResult { NONE = 0, ALREADY_REGISTERED, ORPHAN, INVALID, CONFLICTING, REJECTED, COOLDOWN_NOT_ELAPSED, }; class ProofRegistrationState : public ValidationState { }; namespace bmi = boost::multi_index; 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. */ using PeerSet = boost::multi_index_container< Peer, bmi::indexed_by< // index by peerid bmi::hashed_unique>, // index by proof bmi::hashed_unique, proof_index, SaltedProofIdHasher>, // ordered by score, decreasing order bmi::ordered_non_unique, score_index, std::greater>>>; PeerId nextPeerId = 0; PeerSet peers; ProofPool validProofPool; ProofPool conflictingProofPool; ProofPool orphanProofPool; using NodeSet = boost::multi_index_container< Node, bmi::indexed_by< // index by nodeid bmi::hashed_unique>, // sorted by peerid/nextRequestTime bmi::ordered_non_unique< bmi::tag, bmi::composite_key< Node, bmi::member, bmi::member>>>>; NodeSet nodes; /** * Flag indicating that we failed to select a node and need to expand our * node set. */ std::atomic needMoreNodes{false}; using PendingNodeSet = boost::multi_index_container< PendingNode, bmi::indexed_by< // index by proofid bmi::hashed_non_unique< bmi::tag, bmi::member, SaltedProofIdHasher>, // index by nodeid bmi::hashed_unique< bmi::tag, bmi::member>>>; PendingNodeSet pendingNodes; static constexpr int SELECT_PEER_MAX_RETRY = 3; static constexpr int SELECT_NODE_MAX_RETRY = 3; /** * Track proof ids to broadcast */ std::unordered_set m_unbroadcast_proofids; /** * Quorum management. */ uint32_t totalPeersScore = 0; uint32_t connectedPeersScore = 0; public: /** * Node API. */ bool addNode(NodeId nodeid, const ProofId &proofid); bool removeNode(NodeId nodeid); size_t getNodeCount() const { return nodes.size(); } size_t getPendingNodeCount() const { return pendingNodes.size(); } // Update when a node is to be polled next. bool updateNextRequestTime(NodeId nodeid, TimePoint timeout); // Randomly select a node to poll. NodeId selectNode(); /** * Returns true if we encountered a lack of node since the last call. */ bool shouldRequestMoreNodes() { return needMoreNodes.exchange(false); } template bool forNode(NodeId nodeid, Callable &&func) const { auto it = nodes.find(nodeid); return it != nodes.end() && func(*it); } template void forEachNode(const Peer &peer, Callable &&func) const { auto &nview = nodes.get(); auto range = nview.equal_range(peer.peerid); for (auto it = range.first; it != range.second; ++it) { func(*it); } } /** * Proof and Peer related API. */ /** * Update the time before which a proof is not allowed to have conflicting * UTXO with this peer's proof. */ bool updateNextPossibleConflictTime(PeerId peerid, const std::chrono::seconds &nextTime); /** * Registration mode * - DEFAULT: Default policy, register only if the proof is unknown and has * no conflict. * - FORCE_ACCEPT: Turn a valid proof into a peer even if it has conflicts * and is not the best candidate. */ enum class RegistrationMode { DEFAULT, FORCE_ACCEPT, }; bool registerProof(const ProofRef &proof, ProofRegistrationState ®istrationState, RegistrationMode mode = RegistrationMode::DEFAULT); bool registerProof(const ProofRef &proof, RegistrationMode mode = RegistrationMode::DEFAULT) { ProofRegistrationState dummy; return registerProof(proof, dummy, mode); } /** * Rejection mode * - DEFAULT: Default policy, reject a proof and attempt to keep it in the * conflicting pool if possible. * - INVALIDATE: Reject a proof by removing it from any of the pool. * * In any case if a peer is rejected, it attempts to pull the conflicting * proofs back. */ enum class RejectionMode { DEFAULT, INVALIDATE, }; bool rejectProof(const ProofId &proofid, RejectionMode mode = RejectionMode::DEFAULT); bool exists(const ProofId &proofid) const { return getProof(proofid) != nullptr; } template bool forPeer(const ProofId &proofid, Callable &&func) const { auto &pview = peers.get(); auto it = pview.find(proofid); return it != pview.end() && func(*it); } template void forEachPeer(Callable &&func) const { for (const auto &p : peers) { func(p); } } /** * Update the peer set when a new block is connected. */ void updatedBlockTip(); /** * Proof broadcast API. */ void addUnbroadcastProof(const ProofId &proofid); void removeUnbroadcastProof(const ProofId &proofid); auto getUnbroadcastProofs() const { return m_unbroadcast_proofids; } /* * Quorum management */ uint32_t getTotalPeersScore() const { return totalPeersScore; } uint32_t getConnectedPeersScore() const { return connectedPeersScore; } /**************************************************** * Functions which are public for testing purposes. * ****************************************************/ /** * Remove an existing peer. */ bool removePeer(const PeerId peerid); /** * Randomly select a peer to poll. */ PeerId selectPeer() const; /** * Trigger maintenance of internal data structures. * Returns how much slot space was saved after compaction. */ uint64_t compact(); /** * Perform consistency check on internal data structures. */ bool verify() const; // Accessors. uint64_t getSlotCount() const { return slotCount; } uint64_t getFragmentation() const { return fragmentation; } ProofRef getProof(const ProofId &proofid) const; bool isBoundToPeer(const ProofId &proofid) const; bool isOrphan(const ProofId &proofid) const; bool isInConflictingPool(const ProofId &proofid) const; private: - void moveToConflictingPool(const ProofRef &proof); + template + void moveToConflictingPool(const ProofContainer &proofs); + bool addOrUpdateNode(const PeerSet::iterator &it, NodeId nodeid); bool addNodeToPeer(const PeerSet::iterator &it); bool removeNodeFromPeer(const PeerSet::iterator &it, uint32_t count = 1); friend struct ::avalanche::TestPeerManager; }; /** * Internal methods that are exposed for testing purposes. */ PeerId selectPeerImpl(const std::vector &slots, const uint64_t slot, const uint64_t max); } // namespace avalanche #endif // BITCOIN_AVALANCHE_PEERMANAGER_H