Bitcoin ABC 0.34.2
P2P Digital Currency
peermanager.cpp
Go to the documentation of this file.
1// Copyright (c) 2020 The Bitcoin developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
6
12#include <cashaddrenc.h>
13#include <common/args.h>
15#include <logging.h>
16#include <random.h>
17#include <scheduler.h>
18#include <threadsafety.h>
19#include <uint256.h>
20#include <util/fastrange.h>
21#include <util/fs_helpers.h>
22#include <util/strencodings.h>
23#include <util/time.h>
24#include <validation.h> // For ChainstateManager
25
26#include <algorithm>
27#include <cassert>
28#include <limits>
29
30namespace avalanche {
31static constexpr uint64_t PEERS_DUMP_VERSION{1};
32
33bool PeerManager::addNode(NodeId nodeid, const ProofId &proofid,
34 size_t max_elements) {
35 auto &pview = peers.get<by_proofid>();
36 auto it = pview.find(proofid);
37 if (it == pview.end()) {
38 // If the node exists, it is actually updating its proof to an unknown
39 // one. In this case we need to remove it so it is not both active and
40 // pending at the same time.
41 removeNode(nodeid);
42 pendingNodes.emplace(proofid, nodeid, max_elements);
43 return false;
44 }
45
46 return addOrUpdateNode(peers.project<0>(it), nodeid, max_elements);
47}
48
49bool PeerManager::addOrUpdateNode(const PeerSet::iterator &it, NodeId nodeid,
50 size_t max_elements) {
51 assert(it != peers.end());
52
53 const PeerId peerid = it->peerid;
54
55 auto nit = nodes.find(nodeid);
56 if (nit == nodes.end()) {
57 if (!nodes.emplace(nodeid, peerid, max_elements).second) {
58 return false;
59 }
60 } else {
61 const PeerId oldpeerid = nit->peerid;
62 if (!nodes.modify(nit, [&](Node &n) { n.peerid = peerid; })) {
63 return false;
64 }
65
66 // We actually have this node already, we need to update it.
67 bool success = removeNodeFromPeer(peers.find(oldpeerid));
68 assert(success);
69 }
70
71 // Then increase the node counter, and create the slot if needed
72 bool success = addNodeToPeer(it);
73 assert(success);
74
75 // If the added node was in the pending set, remove it
76 pendingNodes.get<by_nodeid>().erase(nodeid);
77
78 // If the proof was in the dangling pool, remove it
79 const ProofId &proofid = it->getProofId();
80 if (danglingProofPool.getProof(proofid)) {
82 }
83
84 // We know for sure there is at least 1 node. Note that this can fail if
85 // there is more than 1, in this case it's a no-op.
86 shareableProofs.insert(it->proof);
87
88 return true;
89}
90
91bool PeerManager::addNodeToPeer(const PeerSet::iterator &it) {
92 assert(it != peers.end());
93 return peers.modify(it, [&](Peer &p) {
94 if (p.node_count++ > 0) {
95 // We are done.
96 return;
97 }
98
99 // We need to allocate this peer.
100 p.index = uint32_t(slots.size());
101 const uint32_t score = p.getScore();
102 const uint64_t start = slotCount;
103 slots.emplace_back(start, score, it->peerid);
104 slotCount = start + score;
105
106 // Add to our allocated score when we allocate a new peer in the slots
107 connectedPeersScore += score;
108 });
109}
110
112 // Remove all the remote proofs from this node
113 clearRemoteProofs(nodeid);
114
115 if (pendingNodes.get<by_nodeid>().erase(nodeid) > 0) {
116 // If this was a pending node, there is nothing else to do.
117 return true;
118 }
119
120 auto it = nodes.find(nodeid);
121 if (it == nodes.end()) {
122 return false;
123 }
124
125 const PeerId peerid = it->peerid;
126 nodes.erase(it);
127
128 // Keep the track of the reference count.
129 bool success = removeNodeFromPeer(peers.find(peerid));
130 assert(success);
131
132 return true;
133}
134
135bool PeerManager::removeNodeFromPeer(const PeerSet::iterator &it,
136 uint32_t count) {
137 // It is possible for nodes to be dangling. If there was an inflight query
138 // when the peer gets removed, the node was not erased. In this case there
139 // is nothing to do.
140 if (it == peers.end()) {
141 return true;
142 }
143
144 assert(count <= it->node_count);
145 if (count == 0) {
146 // This is a NOOP.
147 return false;
148 }
149
150 const uint32_t new_count = it->node_count - count;
151 if (!peers.modify(it, [&](Peer &p) { p.node_count = new_count; })) {
152 return false;
153 }
154
155 if (new_count > 0) {
156 // We are done.
157 return true;
158 }
159
160 // There are no more nodes left, we need to clean up. Remove from the radix
161 // tree (unless it's our local proof), subtract allocated score and remove
162 // from slots.
163 if (!localProof || it->getProofId() != localProof->getId()) {
164 const auto removed = shareableProofs.remove(it->getProofId());
165 assert(removed);
166 }
167
168 const size_t i = it->index;
169 assert(i < slots.size());
170 assert(connectedPeersScore >= slots[i].getScore());
171 connectedPeersScore -= slots[i].getScore();
172
173 if (i + 1 == slots.size()) {
174 slots.pop_back();
175 slotCount = slots.empty() ? 0 : slots.back().getStop();
176 } else {
177 fragmentation += slots[i].getScore();
178 slots[i] = slots[i].withPeerId(NO_PEER);
179 }
180
181 return true;
182}
183
185 SteadyMilliseconds timeout,
186 uint64_t round) {
187 auto it = nodes.find(nodeid);
188 if (it == nodes.end()) {
189 return false;
190 }
191
192 return nodes.modify(it, [&](Node &n) {
193 n.nextRequestTime = timeout;
194 n.last_round = round;
195 });
196}
197
199 const Response &response) {
200 auto it = nodes.find(nodeid);
201 if (it == nodes.end()) {
202 return false;
203 }
204
205 if (it->last_round > response.getRound()) {
206 // This is a response for a previous round, ignore it.
207 return false;
208 }
209
210 auto timeout = Now<SteadyMilliseconds>() +
211 std::chrono::milliseconds(response.getCooldown());
212
213 return nodes.modify(it, [&](Node &n) {
214 n.nextRequestTime = timeout;
215 n.last_round = response.getRound();
216 });
217}
218
220 auto it = nodes.find(nodeid);
221 if (it == nodes.end()) {
222 return false;
223 }
224
225 return !it->avaproofsSent &&
226 nodes.modify(it, [&](Node &n) { n.avaproofsSent = true; });
227}
228
229static bool isImmatureState(const ProofValidationState &state) {
231}
232
234 PeerId peerid, const std::chrono::seconds &nextTime) {
235 auto it = peers.find(peerid);
236 if (it == peers.end()) {
237 // No such peer
238 return false;
239 }
240
241 // Make sure we don't move the time in the past.
242 peers.modify(it, [&](Peer &p) {
244 std::max(p.nextPossibleConflictTime, nextTime);
245 });
246
247 return it->nextPossibleConflictTime == nextTime;
248}
249
251 auto it = peers.find(peerid);
252 if (it == peers.end()) {
253 // No such peer
254 return false;
255 }
256
257 peers.modify(it, [&](Peer &p) { p.hasFinalized = true; });
258
259 return true;
260}
261
262template <typename ProofContainer>
263void PeerManager::moveToConflictingPool(const ProofContainer &proofs) {
264 auto &peersView = peers.get<by_proofid>();
265 for (const ProofRef &proof : proofs) {
266 auto it = peersView.find(proof->getId());
267 if (it != peersView.end()) {
268 removePeer(it->peerid);
269 }
270
272 }
273}
274
276 ProofRegistrationState &registrationState,
277 RegistrationMode mode) {
278 assert(proof);
279
280 const ProofId &proofid = proof->getId();
281
282 auto invalidate = [&](ProofRegistrationResult result,
283 const std::string &message) {
284 return registrationState.Invalid(
285 result, message, strprintf("proofid: %s", proofid.ToString()));
286 };
287
288 if ((mode != RegistrationMode::FORCE_ACCEPT ||
289 !isInConflictingPool(proofid)) &&
290 exists(proofid)) {
291 // In default mode, we expect the proof to be unknown, i.e. in none of
292 // the pools.
293 // In forced accept mode, the proof can be in the conflicting pool.
295 "proof-already-registered");
296 }
297
298 if (danglingProofPool.getProof(proofid) &&
299 pendingNodes.count(proofid) == 0) {
300 // Don't attempt to register a proof that we already evicted because it
301 // was dangling, but rather attempt to retrieve an associated node.
302 needMoreNodes = true;
303 return invalidate(ProofRegistrationResult::DANGLING, "dangling-proof");
304 }
305
306 // Check the proof's validity.
307 ProofValidationState validationState;
308 if (!WITH_LOCK(cs_main, return proof->verify(stakeUtxoDustThreshold,
309 chainman, validationState))) {
310 if (isImmatureState(validationState)) {
314 // Adding this proof exceeds the immature pool limit, so evict
315 // the lowest scoring proof.
318 }
319
320 return invalidate(ProofRegistrationResult::IMMATURE,
321 "immature-proof");
322 }
323
324 if (validationState.GetResult() ==
327 "utxo-missing-or-spent");
328 }
329
330 // Reject invalid proof.
331 return invalidate(ProofRegistrationResult::INVALID, "invalid-proof");
332 }
333
334 auto now = GetTime<std::chrono::seconds>();
335 auto nextCooldownTimePoint =
336 now + std::chrono::seconds(gArgs.GetIntArg(
337 "-avalancheconflictingproofcooldown",
339
340 ProofPool::ConflictingProofSet conflictingProofs;
341 switch (validProofPool.addProofIfNoConflict(proof, conflictingProofs)) {
342 case ProofPool::AddProofStatus::REJECTED: {
343 if (mode != RegistrationMode::FORCE_ACCEPT) {
344 auto bestPossibleConflictTime = std::chrono::seconds(0);
345 auto &pview = peers.get<by_proofid>();
346 for (auto &conflictingProof : conflictingProofs) {
347 auto it = pview.find(conflictingProof->getId());
348 assert(it != pview.end());
349
350 // Search the most recent time over the peers
351 bestPossibleConflictTime = std::max(
352 bestPossibleConflictTime, it->nextPossibleConflictTime);
353
355 nextCooldownTimePoint);
356 }
357
358 if (bestPossibleConflictTime > now) {
359 // Cooldown not elapsed, reject the proof.
360 return invalidate(
362 "cooldown-not-elapsed");
363 }
364
365 // Give the proof a chance to replace the conflicting ones.
367 // If we have overridden other proofs due to conflict,
368 // remove the peers and attempt to move them to the
369 // conflicting pool.
370 moveToConflictingPool(conflictingProofs);
371
372 // Replacement is successful, continue to peer creation
373 break;
374 }
375
376 // Not the preferred proof, or replacement is not enabled
378 ProofPool::AddProofStatus::REJECTED
380 "rejected-proof")
382 "conflicting-utxos");
383 }
384
386
387 // Move the conflicting proofs from the valid pool to the
388 // conflicting pool
389 moveToConflictingPool(conflictingProofs);
390
391 auto status = validProofPool.addProofIfNoConflict(proof);
392 assert(status == ProofPool::AddProofStatus::SUCCEED);
393
394 break;
395 }
396 case ProofPool::AddProofStatus::DUPLICATED:
397 // If the proof was already in the pool, don't duplicate the peer.
399 "proof-already-registered");
400 case ProofPool::AddProofStatus::SUCCEED:
401 break;
402
403 // No default case, so the compiler can warn about missing cases
404 }
405
406 // At this stage we are going to create a peer so the proof should never
407 // exist in the conflicting pool, but use belt and suspenders.
409
410 // A preferred peer owning these UTXOs supersedes any dangling proof that
411 // was parked earlier for the same stakes.
412 for (const ProofRef &conflicting : danglingProofPool.getConflicts(proof)) {
413 const ProofId &conflictingProofId = conflicting->getId();
414 danglingProofPool.removeProof(conflictingProofId);
416 "Evicted dangling proof %s due to conflict with peer proof "
417 "%s\n",
418 conflictingProofId.GetHex(), proofid.GetHex());
419 }
420
421 // New peer means new peerid!
422 const PeerId peerid = nextPeerId++;
423
424 // We have no peer for this proof, time to create it.
425 auto inserted = peers.emplace(peerid, proof, nextCooldownTimePoint);
426 assert(inserted.second);
427
428 if (localProof && proof->getId() == localProof->getId()) {
429 // Add it to the shareable proofs even if there is no node, we are the
430 // node. Otherwise it will be inserted after a node is attached to the
431 // proof.
432 shareableProofs.insert(proof);
433 }
434
435 // Add to our registered score when adding to the peer list
436 totalPeersScore += proof->getScore();
437
438 // If there are nodes waiting for this proof, add them
439 auto &pendingNodesView = pendingNodes.get<by_proofid>();
440 auto range = pendingNodesView.equal_range(proofid);
441
442 // We want to update the nodes then remove them from the pending set. That
443 // will invalidate the range iterators, so we need to save the node ids
444 // first before we can loop over them.
445 std::vector<std::pair<NodeId, size_t>> nodeids_and_max_elements;
446 nodeids_and_max_elements.reserve(std::distance(range.first, range.second));
447 std::transform(range.first, range.second,
448 std::back_inserter(nodeids_and_max_elements),
449 [](const PendingNode &n) {
450 return std::make_pair(n.nodeid, n.max_elements);
451 });
452
453 for (const auto &[nodeid, max_elements] : nodeids_and_max_elements) {
454 addOrUpdateNode(inserted.first, nodeid, max_elements);
455 }
456
458 addStakeContender(proof);
459 }
460
461 return true;
462}
463
465 if (isDangling(proofid) && mode == RejectionMode::INVALIDATE) {
467 return true;
468 }
469
470 if (!exists(proofid)) {
471 return false;
472 }
473
474 if (immatureProofPool.removeProof(proofid)) {
475 return true;
476 }
477
478 if (mode == RejectionMode::DEFAULT &&
480 // In default mode we keep the proof in the conflicting pool
481 return true;
482 }
483
484 if (mode == RejectionMode::INVALIDATE &&
486 // In invalidate mode we remove the proof completely
487 return true;
488 }
489
490 auto &pview = peers.get<by_proofid>();
491 auto it = pview.find(proofid);
492 assert(it != pview.end());
493
494 const ProofRef proof = it->proof;
495
496 if (!removePeer(it->peerid)) {
497 return false;
498 }
499
500 // If there was conflicting proofs, attempt to pull them back
501 for (const SignedStake &ss : proof->getStakes()) {
502 const ProofRef conflictingProof =
503 conflictingProofPool.getProof(ss.getStake().getUTXO());
504 if (!conflictingProof) {
505 continue;
506 }
507
508 conflictingProofPool.removeProof(conflictingProof->getId());
509 registerProof(conflictingProof);
510 }
511
512 if (mode == RejectionMode::DEFAULT) {
514 }
515
516 return true;
517}
518
520 std::unordered_set<ProofRef, SaltedProofHasher> &registeredProofs) {
521 registeredProofs.clear();
522 const auto now = GetTime<std::chrono::seconds>();
523
524 std::vector<ProofRef> newlyDanglingProofs;
525 for (const Peer &peer : peers) {
526 // If the peer is not our local proof, has been registered for some
527 // time and has no node attached, discard it.
528 if ((!localProof || peer.getProofId() != localProof->getId()) &&
529 peer.node_count == 0 &&
530 (peer.registration_time + Peer::DANGLING_TIMEOUT) <= now) {
531 // Check the remotes status to determine if we should set the proof
532 // as dangling. This prevents from dropping a proof on our own due
533 // to a network issue. If the remote presence status is inconclusive
534 // we assume our own position (missing = false).
535 if (!getRemotePresenceStatus(peer.getProofId()).value_or(false)) {
536 newlyDanglingProofs.push_back(peer.proof);
537 }
538 }
539 }
540
541 // Similarly, check if we have dangling proofs that could be pulled back
542 // because the network says so.
543 std::vector<ProofRef> previouslyDanglingProofs;
544 danglingProofPool.forEachProof([&](const ProofRef &proof) {
545 if (getRemotePresenceStatus(proof->getId()).value_or(false)) {
546 previouslyDanglingProofs.push_back(proof);
547 }
548 });
549 for (const ProofRef &proof : previouslyDanglingProofs) {
550 danglingProofPool.removeProof(proof->getId());
551 if (registerProof(proof)) {
552 registeredProofs.insert(proof);
553 }
554 }
555
556 for (const ProofRef &proof : newlyDanglingProofs) {
557 rejectProof(proof->getId(), RejectionMode::INVALIDATE);
558
559 // If a preferred peer already owns any of these UTXOs, do not park the
560 // proof as dangling — it can never be pulled back usefully.
561 // Note that since peer registration also checks for conflicts in the
562 // dangling pool, this condition should never be true. It is kept as a
563 // belt and suspenders check in the case this assumption is violated.
564 if (!validProofPool.getConflicts(proof).empty()) {
565 continue;
566 }
567
569 // If the proof is added, it means there is no better conflicting
570 // dangling proof and this is not a duplicated, so it's worth
571 // printing a message to the log.
573 "Proof dangling for too long (no connected node): %s\n",
574 proof->getId().GetHex());
575 }
576 }
577
578 // If we have dangling proof, this is a good indicator that we need to
579 // request more nodes from our peers.
580 needMoreNodes = !newlyDanglingProofs.empty();
581}
582
584 for (int retry = 0; retry < SELECT_NODE_MAX_RETRY; retry++) {
585 const PeerId p = selectPeer();
586
587 // If we cannot find a peer, it may be due to the fact that it is
588 // unlikely due to high fragmentation, so compact and retry.
589 if (p == NO_PEER) {
590 compact();
591 continue;
592 }
593
594 // See if that peer has an available node.
595 auto &nview = nodes.get<next_request_time>();
596 auto it = nview.lower_bound(boost::make_tuple(p, SteadyMilliseconds()));
597 if (it != nview.end() && it->peerid == p &&
598 it->nextRequestTime <= Now<SteadyMilliseconds>()) {
599 return it->nodeid;
600 }
601 }
602
603 // We failed to find a node to query, flag this so we can request more
604 needMoreNodes = true;
605
606 return NO_NODE;
607}
608
609std::unordered_set<ProofRef, SaltedProofHasher> PeerManager::updatedBlockTip() {
610 std::vector<ProofId> invalidProofIds;
611 std::vector<ProofRef> newImmatures;
612
613 {
614 LOCK(cs_main);
615
616 for (const auto &p : peers) {
618 if (!p.proof->verify(stakeUtxoDustThreshold, chainman, state)) {
619 if (isImmatureState(state)) {
620 newImmatures.push_back(p.proof);
621 }
622 invalidProofIds.push_back(p.getProofId());
623
625 "Invalidating proof %s: verification failed (%s)\n",
626 p.proof->getId().GetHex(), state.ToString());
627 }
628 }
629
630 // Disable thread safety analysis here because it does not play nicely
631 // with the lambda
633 [&](const ProofRef &proof) NO_THREAD_SAFETY_ANALYSIS {
636 if (!proof->verify(stakeUtxoDustThreshold, chainman, state)) {
637 invalidProofIds.push_back(proof->getId());
638
639 LogDebug(
641 "Invalidating dangling proof %s: verification failed "
642 "(%s)\n",
643 proof->getId().GetHex(), state.ToString());
644 }
645 });
646 }
647
648 // Remove the invalid proofs before the immature rescan. This makes it
649 // possible to pull back proofs with utxos that conflicted with these
650 // invalid proofs.
651 for (const ProofId &invalidProofId : invalidProofIds) {
652 rejectProof(invalidProofId, RejectionMode::INVALIDATE);
653 }
654
655 auto registeredProofs = immatureProofPool.rescan(*this);
656
657 for (auto &p : newImmatures) {
659 }
660
661 return registeredProofs;
662}
663
665 ProofRef proof;
666
667 forPeer(proofid, [&](const Peer &p) {
668 proof = p.proof;
669 return true;
670 });
671
672 if (!proof) {
673 proof = conflictingProofPool.getProof(proofid);
674 }
675
676 if (!proof) {
677 proof = immatureProofPool.getProof(proofid);
678 }
679
680 return proof;
681}
682
683bool PeerManager::isBoundToPeer(const ProofId &proofid) const {
684 auto &pview = peers.get<by_proofid>();
685 return pview.find(proofid) != pview.end();
686}
687
688bool PeerManager::isImmature(const ProofId &proofid) const {
689 return immatureProofPool.getProof(proofid) != nullptr;
690}
691
692bool PeerManager::isInConflictingPool(const ProofId &proofid) const {
693 return conflictingProofPool.getProof(proofid) != nullptr;
694}
695
696bool PeerManager::isDangling(const ProofId &proofid) const {
697 return danglingProofPool.getProof(proofid) != nullptr;
698}
699
700void PeerManager::setInvalid(const ProofId &proofid) {
701 invalidProofs.insert(proofid);
702}
703
704bool PeerManager::isInvalid(const ProofId &proofid) const {
705 return invalidProofs.contains(proofid);
706}
707
710}
711
712bool PeerManager::saveRemoteProof(const ProofId &proofid, const NodeId nodeid,
713 const bool present) {
714 if (present && isStakingPreconsensusActivated() && isBoundToPeer(proofid) &&
715 !isRemotelyPresentProof(proofid)) {
716 // If this is the first time this peer's proof becomes a remote proof of
717 // any node, ensure it is included in the contender cache. There is a
718 // special case where the contender cache can lose track of a proof if
719 // it is not saved as a remote proof before the next finalized block
720 // (triggering promotion, where non-remote cache entries are dropped).
721 // This does not happen in the hot path since receiving a proof
722 // immediately saves it as a remote, however it becomes more likely if
723 // the proof was loaded from a file (-persistavapeers) or added via RPC.
724 addStakeContender(getProof(proofid));
725 }
726
727 // Get how many proofs this node has announced
728 auto &remoteProofsByLastUpdate = remoteProofs.get<by_lastUpdate>();
729 auto [begin, end] = remoteProofsByLastUpdate.equal_range(nodeid);
730
731 // Limit the number of proofs a single node can save:
732 // - At least MAX_REMOTE_PROOFS
733 // - Up to 2x as much as we have
734 // The MAX_REMOTE_PROOFS minimum is there to ensure we don't overlimit at
735 // startup when we don't have proofs yet.
736 while (size_t(std::distance(begin, end)) >=
737 std::max(MAX_REMOTE_PROOFS, 2 * peers.size())) {
738 // Remove the proof with the oldest update time
739 begin = remoteProofsByLastUpdate.erase(begin);
740 }
741
742 auto it = remoteProofs.find(boost::make_tuple(proofid, nodeid));
743 if (it != remoteProofs.end()) {
744 remoteProofs.erase(it);
745 }
746
747 return remoteProofs
748 .emplace(RemoteProof{proofid, nodeid, GetTime<std::chrono::seconds>(),
749 present})
750 .second;
751}
752
753std::vector<RemoteProof>
755 std::vector<RemoteProof> nodeRemoteProofs;
756
757 auto &remoteProofsByLastUpdate = remoteProofs.get<by_lastUpdate>();
758 auto [begin, end] = remoteProofsByLastUpdate.equal_range(nodeid);
759
760 for (auto &it = begin; it != end; it++) {
761 nodeRemoteProofs.emplace_back(*it);
762 }
763
764 return nodeRemoteProofs;
765}
766
767bool PeerManager::hasRemoteProofStatus(const ProofId &proofid) const {
768 auto &view = remoteProofs.get<by_proofid>();
769 return view.count(proofid) > 0;
770}
771
773 auto &view = remoteProofs.get<by_proofid>();
774 auto [begin, end] = view.equal_range(proofid);
775 return std::any_of(begin, end, [](const auto &remoteProof) {
776 return remoteProof.present;
777 });
778}
779
781 auto &remoteProofsView = remoteProofs.get<by_nodeid>();
782 auto [begin, end] = remoteProofsView.equal_range(nodeid);
783 remoteProofsView.erase(begin, end);
784}
785
786bool PeerManager::removePeer(const PeerId peerid) {
787 auto it = peers.find(peerid);
788 if (it == peers.end()) {
789 return false;
790 }
791
792 // Remove all nodes from this peer.
793 removeNodeFromPeer(it, it->node_count);
794
795 auto &nview = nodes.get<next_request_time>();
796
797 // Add the nodes to the pending set
798 auto range = nview.equal_range(peerid);
799 for (auto &nit = range.first; nit != range.second; ++nit) {
800 pendingNodes.emplace(it->getProofId(), nit->nodeid, nit->maxElements);
801 };
802
803 // Remove nodes associated with this peer, unless their timeout is still
804 // active. This ensure that we don't overquery them in case they are
805 // subsequently added to another peer.
806 nview.erase(
807 nview.lower_bound(boost::make_tuple(peerid, SteadyMilliseconds())),
808 nview.upper_bound(
809 boost::make_tuple(peerid, Now<SteadyMilliseconds>())));
810
811 // Release UTXOs attached to this proof.
812 validProofPool.removeProof(it->getProofId());
813
814 // If there were nodes attached, remove from the radix tree as well
815 auto removed = shareableProofs.remove(Uint256RadixKey(it->getProofId()));
816
817 m_unbroadcast_proofids.erase(it->getProofId());
818
819 // Remove the peer from the PeerSet and remove its score from the registered
820 // score total.
821 assert(totalPeersScore >= it->getScore());
822 totalPeersScore -= it->getScore();
823 peers.erase(it);
824 return true;
825}
826
828 if (slots.empty() || slotCount == 0) {
829 return NO_PEER;
830 }
831
832 const uint64_t max = slotCount;
833 for (int retry = 0; retry < SELECT_PEER_MAX_RETRY; retry++) {
834 size_t i =
835 selectPeerImpl(slots, FastRandomContext().randrange(max), max);
836 if (i != NO_PEER) {
837 return i;
838 }
839 }
840
841 return NO_PEER;
842}
843
845 // There is nothing to compact.
846 if (fragmentation == 0) {
847 return 0;
848 }
849
850 std::vector<Slot> newslots;
851 newslots.reserve(peers.size());
852
853 uint64_t prevStop = 0;
854 uint32_t i = 0;
855 for (auto it = peers.begin(); it != peers.end(); it++) {
856 if (it->node_count == 0) {
857 continue;
858 }
859
860 newslots.emplace_back(prevStop, it->getScore(), it->peerid);
861 prevStop = newslots.back().getStop();
862 if (!peers.modify(it, [&](Peer &p) { p.index = i++; })) {
863 return 0;
864 }
865 }
866
867 slots = std::move(newslots);
868
869 const uint64_t saved = slotCount - prevStop;
870 slotCount = prevStop;
871 fragmentation = 0;
872
873 return saved;
874}
875
877 uint64_t prevStop = 0;
878 uint32_t scoreFromSlots = 0;
879 for (size_t i = 0; i < slots.size(); i++) {
880 const Slot &s = slots[i];
881
882 // Slots must be in correct order.
883 if (s.getStart() < prevStop) {
884 return false;
885 }
886
887 prevStop = s.getStop();
888
889 // If this is a dead slot, then nothing more needs to be checked.
890 if (s.getPeerId() == NO_PEER) {
891 continue;
892 }
893
894 // We have a live slot, verify index.
895 auto it = peers.find(s.getPeerId());
896 if (it == peers.end() || it->index != i) {
897 return false;
898 }
899
900 // Accumulate score across slots
901 scoreFromSlots += slots[i].getScore();
902 }
903
904 // Score across slots must be the same as our allocated score
905 if (scoreFromSlots != connectedPeersScore) {
906 return false;
907 }
908
909 uint32_t scoreFromAllPeers = 0;
910 uint32_t scoreFromPeersWithNodes = 0;
911
912 std::unordered_set<COutPoint, SaltedOutpointHasher> peersUtxos;
913 for (const auto &p : peers) {
914 // Accumulate the score across peers to compare with total known score
915 scoreFromAllPeers += p.getScore();
916
917 // A peer should have a proof attached
918 if (!p.proof) {
919 return false;
920 }
921
922 // Check proof pool consistency
923 for (const auto &ss : p.proof->getStakes()) {
924 const COutPoint &outpoint = ss.getStake().getUTXO();
925 auto proof = validProofPool.getProof(outpoint);
926
927 if (!proof) {
928 // Missing utxo
929 return false;
930 }
931 if (proof != p.proof) {
932 // Wrong proof
933 return false;
934 }
935
936 if (!peersUtxos.emplace(outpoint).second) {
937 // Duplicated utxo
938 return false;
939 }
940 }
941
942 // Count node attached to this peer.
943 const auto count_nodes = [&]() {
944 size_t count = 0;
945 auto &nview = nodes.get<next_request_time>();
946 auto begin = nview.lower_bound(
947 boost::make_tuple(p.peerid, SteadyMilliseconds()));
948 auto end = nview.upper_bound(
949 boost::make_tuple(p.peerid + 1, SteadyMilliseconds()));
950
951 for (auto it = begin; it != end; ++it) {
952 count++;
953 }
954
955 return count;
956 };
957
958 if (p.node_count != count_nodes()) {
959 return false;
960 }
961
962 // If there are no nodes attached to this peer, then we are done.
963 if (p.node_count == 0) {
964 continue;
965 }
966
967 scoreFromPeersWithNodes += p.getScore();
968 // The index must point to a slot refering to this peer.
969 if (p.index >= slots.size() || slots[p.index].getPeerId() != p.peerid) {
970 return false;
971 }
972
973 // If the score do not match, same thing.
974 if (slots[p.index].getScore() != p.getScore()) {
975 return false;
976 }
977
978 // Check the proof is in the radix tree only if there are nodes attached
979 if (((localProof && p.getProofId() == localProof->getId()) ||
980 p.node_count > 0) &&
981 shareableProofs.get(p.getProofId()) == nullptr) {
982 return false;
983 }
984 if (p.node_count == 0 &&
985 shareableProofs.get(p.getProofId()) != nullptr) {
986 return false;
987 }
988 }
989
990 // Check our accumulated scores against our registred and allocated scores
991 if (scoreFromAllPeers != totalPeersScore) {
992 return false;
993 }
994 if (scoreFromPeersWithNodes != connectedPeersScore) {
995 return false;
996 }
997
998 // We checked the utxo consistency for all our peers utxos already, so if
999 // the pool size differs from the expected one there are dangling utxos.
1000 if (validProofPool.size() != peersUtxos.size()) {
1001 return false;
1002 }
1003
1004 // Check there is no dangling proof in the radix tree
1006 return isBoundToPeer(pLeaf->getId());
1007 });
1008}
1009
1010PeerId selectPeerImpl(const std::vector<Slot> &slots, const uint64_t slot,
1011 const uint64_t max) {
1012 assert(slot <= max);
1013
1014 size_t begin = 0, end = slots.size();
1015 uint64_t bottom = 0, top = max;
1016
1017 // Try to find the slot using dichotomic search.
1018 while ((end - begin) > 8) {
1019 // The slot we picked in not allocated.
1020 if (slot < bottom || slot >= top) {
1021 return NO_PEER;
1022 }
1023
1024 // Guesstimate the position of the slot.
1025 size_t i = begin + ((slot - bottom) * (end - begin) / (top - bottom));
1026 assert(begin <= i && i < end);
1027
1028 // We have a match.
1029 if (slots[i].contains(slot)) {
1030 return slots[i].getPeerId();
1031 }
1032
1033 // We undershooted.
1034 if (slots[i].precedes(slot)) {
1035 begin = i + 1;
1036 if (begin >= end) {
1037 return NO_PEER;
1038 }
1039
1040 bottom = slots[begin].getStart();
1041 continue;
1042 }
1043
1044 // We overshooted.
1045 if (slots[i].follows(slot)) {
1046 end = i;
1047 top = slots[end].getStart();
1048 continue;
1049 }
1050
1051 // We have an unalocated slot.
1052 return NO_PEER;
1053 }
1054
1055 // Enough of that nonsense, let fallback to linear search.
1056 for (size_t i = begin; i < end; i++) {
1057 // We have a match.
1058 if (slots[i].contains(slot)) {
1059 return slots[i].getPeerId();
1060 }
1061 }
1062
1063 // We failed to find a slot, retry.
1064 return NO_PEER;
1065}
1066
1068 // The proof should be bound to a peer
1069 if (isBoundToPeer(proofid)) {
1070 m_unbroadcast_proofids.insert(proofid);
1071 }
1072}
1073
1075 m_unbroadcast_proofids.erase(proofid);
1076}
1077
1079 const CBlockIndex *pprev,
1080 std::vector<std::pair<ProofId, CScript>> &winners) {
1081 if (!pprev) {
1082 return false;
1083 }
1084
1085 const BlockHash prevblockhash = pprev->GetBlockHash();
1086
1087 // Rejected contenders only affect winner selection for their prevblock.
1089 rejectedContenderPrevBlockHash != prevblockhash) {
1091 }
1092
1093 // Don't select proofs that have not been known for long enough, i.e. at
1094 // least since twice the dangling proof cleanup timeout before the last
1095 // block time, so we're sure to not account for proofs more recent than the
1096 // previous block or lacking node connected.
1097 // The previous block time is capped to now for the unlikely event the
1098 // previous block time is in the future.
1099 auto registrationDelay = std::chrono::duration_cast<std::chrono::seconds>(
1101 auto maxRegistrationDelay =
1102 std::chrono::duration_cast<std::chrono::seconds>(
1104 auto minRegistrationDelay =
1105 std::chrono::duration_cast<std::chrono::seconds>(
1107
1108 const int64_t refTime = std::min(pprev->GetBlockTime(), GetTime());
1109
1110 const int64_t targetRegistrationTime = refTime - registrationDelay.count();
1111 const int64_t maxRegistrationTime = refTime - minRegistrationDelay.count();
1112 const int64_t minRegistrationTime = refTime - maxRegistrationDelay.count();
1113
1114 std::vector<ProofRef> selectedProofs;
1115 ProofRef firstCompliantProof = ProofRef();
1116 while (selectedProofs.size() < peers.size()) {
1117 double bestRewardRank = std::numeric_limits<double>::max();
1118 ProofRef selectedProof = ProofRef();
1119 int64_t selectedProofRegistrationTime{0};
1120 StakeContenderId bestRewardHash;
1121
1122 for (const Peer &peer : peers) {
1123 if (!peer.proof) {
1124 // Should never happen, continue
1125 continue;
1126 }
1127
1128 if (!peer.hasFinalized ||
1129 peer.registration_time.count() >= maxRegistrationTime) {
1130 continue;
1131 }
1132
1133 if (std::find_if(selectedProofs.begin(), selectedProofs.end(),
1134 [&peer](const ProofRef &proof) {
1135 return peer.getProofId() == proof->getId();
1136 }) != selectedProofs.end()) {
1137 continue;
1138 }
1139
1140 StakeContenderId proofRewardHash(prevblockhash, peer.getProofId());
1141 if (proofRewardHash == uint256::ZERO) {
1142 // This either the result of an incredibly unlikely lucky hash,
1143 // or a the hash is getting abused. In this case, skip the
1144 // proof.
1145 LogPrintf(
1146 "Staking reward hash has a suspicious value of zero for "
1147 "proof %s and blockhash %s, skipping\n",
1148 peer.getProofId().ToString(), prevblockhash.ToString());
1149 continue;
1150 }
1151
1152 double proofRewardRank =
1153 proofRewardHash.ComputeProofRewardRank(peer.getScore());
1154 // If selectedProof is nullptr, this means that bestRewardRank is
1155 // MAX_DOUBLE so the comparison will always select this proof as the
1156 // preferred one. As a consequence it is safe to use 0 as a proofid.
1158 proofRewardHash, proofRewardRank, peer.getProofId(),
1159 bestRewardHash, bestRewardRank,
1160 selectedProof ? selectedProof->getId()
1161 : ProofId(uint256::ZERO))) {
1162 bestRewardRank = proofRewardRank;
1163 selectedProof = peer.proof;
1164 selectedProofRegistrationTime = peer.registration_time.count();
1165 bestRewardHash = proofRewardHash;
1166 }
1167 }
1168
1169 if (!selectedProof) {
1170 // No winner
1171 break;
1172 }
1173
1174 if (!firstCompliantProof &&
1175 selectedProofRegistrationTime < targetRegistrationTime) {
1176 firstCompliantProof = selectedProof;
1177 }
1178
1179 selectedProofs.push_back(selectedProof);
1180
1181 if (selectedProofRegistrationTime < minRegistrationTime &&
1182 !isFlaky(selectedProof->getId()) &&
1183 rejectedContenderProofids.count(selectedProof->getId()) == 0) {
1184 break;
1185 }
1186 }
1187
1188 winners.clear();
1189
1190 if (!firstCompliantProof) {
1191 return false;
1192 }
1193
1194 winners.reserve(selectedProofs.size());
1195
1196 // Find the winner
1197 for (const ProofRef &proof : selectedProofs) {
1198 if (proof->getId() == firstCompliantProof->getId()) {
1199 winners.push_back({proof->getId(), proof->getPayoutScript()});
1200 }
1201 }
1202 // Add the others (if any) after the winner
1203 for (const ProofRef &proof : selectedProofs) {
1204 if (proof->getId() != firstCompliantProof->getId()) {
1205 winners.push_back({proof->getId(), proof->getPayoutScript()});
1206 }
1207 }
1208
1209 return true;
1210}
1211
1212bool PeerManager::setFlaky(const ProofId &proofid) {
1213 return manualFlakyProofids.insert(proofid).second;
1214}
1215
1216bool PeerManager::unsetFlaky(const ProofId &proofid) {
1217 return manualFlakyProofids.erase(proofid) > 0;
1218}
1219
1221 const BlockHash &prevblockhash) {
1222 if (rejectedContenderPrevBlockHash != prevblockhash) {
1224 rejectedContenderPrevBlockHash = prevblockhash;
1225 }
1226 return rejectedContenderProofids.insert(proofid).second;
1227}
1228
1232}
1233
1234bool PeerManager::isFlaky(const ProofId &proofid) const {
1235 if (localProof && proofid == localProof->getId()) {
1236 return false;
1237 }
1238
1239 if (manualFlakyProofids.count(proofid) > 0) {
1240 return true;
1241 }
1242
1243 // If we are missing connection to this proof, consider flaky
1244 if (forPeer(proofid,
1245 [](const Peer &peer) { return peer.node_count == 0; })) {
1246 return true;
1247 }
1248
1249 auto &remoteProofsByNodeId = remoteProofs.get<by_nodeid>();
1250 auto &nview = nodes.get<next_request_time>();
1251
1252 std::unordered_map<PeerId, std::unordered_set<ProofId, SaltedProofIdHasher>>
1253 missing_per_peer;
1254
1255 // Construct a set of missing proof ids per peer
1256 double total_score{0};
1257 for (const Peer &peer : peers) {
1258 const PeerId peerid = peer.peerid;
1259
1260 total_score += peer.getScore();
1261
1262 auto nodes_range = nview.equal_range(peerid);
1263 for (auto &nit = nodes_range.first; nit != nodes_range.second; ++nit) {
1264 auto proofs_range = remoteProofsByNodeId.equal_range(nit->nodeid);
1265 for (auto &proofit = proofs_range.first;
1266 proofit != proofs_range.second; ++proofit) {
1267 if (!proofit->present) {
1268 missing_per_peer[peerid].insert(proofit->proofid);
1269 }
1270 }
1271 };
1272 }
1273
1274 double missing_score{0};
1275
1276 // Now compute a score for the missing proof
1277 for (const auto &[peerid, missingProofs] : missing_per_peer) {
1278 if (missingProofs.size() > 3) {
1279 // Ignore peers with too many missing proofs
1280 continue;
1281 }
1282
1283 auto pit = peers.find(peerid);
1284 if (pit == peers.end()) {
1285 // Peer not found
1286 continue;
1287 }
1288
1289 if (missingProofs.count(proofid) > 0) {
1290 missing_score += pit->getScore();
1291 }
1292 }
1293
1294 return (missing_score / total_score) > 0.3;
1295}
1296
1297std::optional<bool>
1299 auto &remoteProofsView = remoteProofs.get<by_proofid>();
1300 auto [begin, end] = remoteProofsView.equal_range(proofid);
1301
1302 if (begin == end) {
1303 // No remote registered anything yet, we are on our own
1304 return std::nullopt;
1305 }
1306
1307 double total_score{0};
1308 double present_score{0};
1309 double missing_score{0};
1310
1311 for (auto it = begin; it != end; it++) {
1312 auto nit = nodes.find(it->nodeid);
1313 if (nit == nodes.end()) {
1314 // No such node
1315 continue;
1316 }
1317
1318 const PeerId peerid = nit->peerid;
1319
1320 auto pit = peers.find(peerid);
1321 if (pit == peers.end()) {
1322 // Peer not found
1323 continue;
1324 }
1325
1326 uint32_t node_count = pit->node_count;
1327 if (localProof && pit->getProofId() == localProof->getId()) {
1328 // If that's our local proof, account for ourself
1329 ++node_count;
1330 }
1331
1332 if (node_count == 0) {
1333 // should never happen
1334 continue;
1335 }
1336
1337 const double score = double(pit->getScore()) / node_count;
1338
1339 total_score += score;
1340 if (it->present) {
1341 present_score += score;
1342 } else {
1343 missing_score += score;
1344 }
1345 }
1346
1347 if (localProof) {
1348 auto &peersByProofid = peers.get<by_proofid>();
1349
1350 // Do we have a node connected for that proof ?
1351 bool present = false;
1352 auto pit = peersByProofid.find(proofid);
1353 if (pit != peersByProofid.end()) {
1354 present = pit->node_count > 0;
1355 }
1356
1357 pit = peersByProofid.find(localProof->getId());
1358 if (pit != peersByProofid.end()) {
1359 // Also divide by node_count, we can have several nodes even for our
1360 // local proof.
1361 const double score =
1362 double(pit->getScore()) / (1 + pit->node_count);
1363
1364 total_score += score;
1365 if (present) {
1366 present_score += score;
1367 } else {
1368 missing_score += score;
1369 }
1370 }
1371 }
1372
1373 if (present_score / total_score > 0.55) {
1374 return std::make_optional(true);
1375 }
1376
1377 if (missing_score / total_score > 0.55) {
1378 return std::make_optional(false);
1379 }
1380
1381 return std::nullopt;
1382}
1383
1384bool PeerManager::dumpPeersToFile(const fs::path &dumpPath) const {
1385 try {
1386 const fs::path dumpPathTmp = dumpPath + ".new";
1387 FILE *filestr = fsbridge::fopen(dumpPathTmp, "wb");
1388 if (!filestr) {
1389 return false;
1390 }
1391
1392 AutoFile file{filestr};
1393 file << PEERS_DUMP_VERSION;
1394 file << uint64_t(peers.size());
1395 for (const Peer &peer : peers) {
1396 file << peer.proof;
1397 file << peer.hasFinalized;
1398 file << int64_t(peer.registration_time.count());
1399 file << int64_t(peer.nextPossibleConflictTime.count());
1400 }
1401
1402 if (!FileCommit(file.Get())) {
1403 throw std::runtime_error(strprintf("Failed to commit to file %s",
1404 PathToString(dumpPathTmp)));
1405 }
1406 file.fclose();
1407
1408 if (!RenameOver(dumpPathTmp, dumpPath)) {
1409 throw std::runtime_error(strprintf("Rename failed from %s to %s",
1410 PathToString(dumpPathTmp),
1411 PathToString(dumpPath)));
1412 }
1413 } catch (const std::exception &e) {
1414 LogDebug(BCLog::AVALANCHE, "Failed to dump the avalanche peers: %s.\n",
1415 e.what());
1416 return false;
1417 }
1418
1419 LogDebug(BCLog::AVALANCHE, "Successfully dumped %d peers to %s.\n",
1420 peers.size(), PathToString(dumpPath));
1421
1422 return true;
1423}
1424
1426 const fs::path &dumpPath,
1427 std::unordered_set<ProofRef, SaltedProofHasher> &registeredProofs) {
1428 registeredProofs.clear();
1429
1430 FILE *filestr = fsbridge::fopen(dumpPath, "rb");
1431 AutoFile file{filestr};
1432 if (file.IsNull()) {
1434 "Failed to open avalanche peers file from disk.\n");
1435 return false;
1436 }
1437
1438 try {
1439 uint64_t version;
1440 file >> version;
1441
1442 if (version != PEERS_DUMP_VERSION) {
1444 "Unsupported avalanche peers file version.\n");
1445 return false;
1446 }
1447
1448 uint64_t numPeers;
1449 file >> numPeers;
1450
1451 auto &peersByProofId = peers.get<by_proofid>();
1452
1453 for (uint64_t i = 0; i < numPeers; i++) {
1454 ProofRef proof;
1455 bool hasFinalized;
1456 int64_t registrationTime;
1457 int64_t nextPossibleConflictTime;
1458
1459 file >> proof;
1460 file >> hasFinalized;
1461 file >> registrationTime;
1462 file >> nextPossibleConflictTime;
1463
1464 if (registerProof(proof)) {
1465 auto it = peersByProofId.find(proof->getId());
1466 if (it == peersByProofId.end()) {
1467 // Should never happen
1468 continue;
1469 }
1470
1471 // We don't modify any key so we don't need to rehash.
1472 // If the modify fails, it means we don't get the full benefit
1473 // from the file but we still added our peer to the set. The
1474 // non-overridden fields will be set the normal way.
1475 peersByProofId.modify(it, [&](Peer &p) {
1476 p.hasFinalized = hasFinalized;
1478 std::chrono::seconds{registrationTime};
1480 std::chrono::seconds{nextPossibleConflictTime};
1481 });
1482
1483 registeredProofs.insert(proof);
1484 }
1485 }
1486 } catch (const std::exception &e) {
1488 "Failed to read the avalanche peers file data on disk: %s.\n",
1489 e.what());
1490 return false;
1491 }
1492
1493 return true;
1494}
1495
1496void PeerManager::cleanupStakeContenders(const int requestedMinHeight) {
1497 stakeContenderCache.cleanup(requestedMinHeight);
1498}
1499
1501 const CBlockIndex *tip = WITH_LOCK(cs_main, return chainman.ActiveTip());
1502 stakeContenderCache.add(tip, proof);
1503
1504 const BlockHash blockhash = tip->GetBlockHash();
1505 const ProofId &proofid = proof->getId();
1507 "Cached stake contender with proofid %s, payout %s at block "
1508 "%s (height %d) with id %s\n",
1509 proofid.ToString(), HexStr(proof->getPayoutScript()),
1510 blockhash.ToString(), tip->nHeight,
1511 StakeContenderId(blockhash, proofid).ToString());
1512}
1513
1515 BlockHash &prevblockhashout) const {
1516 const auto contender = stakeContenderCache.getContenderInfo(contenderId);
1517 if (!contender) {
1518 return -1;
1519 }
1520
1521 prevblockhashout = contender->prevblockhash;
1522 if (contender->voteStatus != 0) {
1523 return contender->voteStatus;
1524 }
1525
1526 const ProofId &proofid = contender->proofid;
1527
1528 if (isInvalid(proofid)) {
1529 return 2;
1530 }
1531 if (isInConflictingPool(proofid)) {
1532 return 3;
1533 }
1534 if (isDangling(proofid) &&
1535 !getRemotePresenceStatus(proofid).value_or(false)) {
1536 return 4;
1537 }
1538
1539 // Bound peers need to be finalized and registered more than
1540 // 4 * DANGLING_TIMEOUT before the prev block.
1541 if (isBoundToPeer(proofid)) {
1542 auto &pview = peers.get<by_proofid>();
1543 auto it = pview.find(proofid);
1544 if (it == pview.end() || !it->hasFinalized) {
1545 return 5;
1546 }
1547
1548 const int64_t refTime = std::min(contender->prevblocktime, GetTime());
1549 const int64_t targetRegistrationTime =
1550 refTime - std::chrono::duration_cast<std::chrono::seconds>(
1552 .count();
1553 if (it->registration_time.count() >= targetRegistrationTime) {
1554 return 6;
1555 }
1556 }
1557
1558 // Cache-only contenders keep cache acceptance.
1559 return 0;
1560}
1561
1563 stakeContenderCache.accept(contenderId);
1564}
1565
1567 const StakeContenderId &contenderId, BlockHash &prevblockhash,
1568 std::vector<std::pair<ProofId, CScript>> &newWinners) {
1569 stakeContenderCache.finalize(contenderId);
1570
1571 // Get block hash related to this contender. We should not assume the
1572 // current chain tip is the block this contender is a winner for.
1573 stakeContenderCache.getVoteStatus(contenderId, prevblockhash);
1574
1575 // Calculate the new winners for this block
1576 stakeContenderCache.getWinners(prevblockhash, newWinners);
1577}
1578
1580 stakeContenderCache.reject(contenderId);
1581}
1582
1583std::optional<StakeContenderCacheInfo>
1585 return stakeContenderCache.getContenderInfo(contenderId);
1586}
1587
1588bool PeerManager::hasAcceptedStakeWinner(const BlockHash &prevblockhash) const {
1589 return stakeContenderCache.hasAcceptedWinner(prevblockhash);
1590}
1591
1593 const BlockHash &prevblockhash,
1594 std::vector<std::pair<ProofId, CScript>> &winners) const {
1595 return stakeContenderCache.getWinners(prevblockhash, winners);
1596}
1597
1599 stakeContenderCache.promoteToBlock(pindex, [&](const ProofId &proofid) {
1600 return isBoundToPeer(proofid) ||
1601 // isDangling check appears redundant, but remote proofs are not
1602 // guaranteed to be cleaned up when one of our peers is removed
1603 // for dangling too long. Whether or not a proof is dangling is
1604 // gated by remote presence status, so only proofs that are very
1605 // poorly connected to the network will stop being promoted.
1606 (isRemotelyPresentProof(proofid) && isDangling(proofid));
1607 });
1608}
1609
1611 const CBlockIndex *prevblock,
1612 const std::vector<std::pair<ProofId, CScript>> winners, size_t maxPollable,
1613 std::vector<StakeContenderId> &pollableContenders) {
1614 const BlockHash prevblockhash = prevblock->GetBlockHash();
1615 // Set status for local winners
1616 for (const auto &winner : winners) {
1617 const StakeContenderId contenderId(prevblockhash, winner.first);
1618 stakeContenderCache.finalize(contenderId);
1620 "Stake contender set as local winner: proofid %s, payout "
1621 "%s at block %s (height %d) with id %s\n",
1622 winner.first.ToString(), HexStr(winner.second),
1623 prevblockhash.ToString(), prevblock->nHeight,
1624 contenderId.ToString());
1625 }
1626
1627 // Treat the highest ranking contender similarly to local winners except
1628 // that it is not automatically included in the winner set (unless it
1629 // happens to be selected as a local winner). Vote-side eligibility is
1630 // enforced in getStakeContenderStatus.
1631 if (stakeContenderCache.getPollableContenders(prevblockhash, maxPollable,
1632 pollableContenders) > 0) {
1633 // Accept the highest ranking contender. This is a no-op if the highest
1634 // ranking contender is already the local winner.
1635 stakeContenderCache.accept(pollableContenders[0]);
1637 "Stake contender set as best contender: id %s at block "
1638 "%s (height %d)\n",
1639 pollableContenders[0].ToString(), prevblockhash.ToString(),
1640 prevblock->nHeight);
1641 return true;
1642 }
1643
1644 return false;
1645}
1646
1648 const CBlockIndex *pindex, const std::vector<CScript> &payoutScripts) {
1649 return stakeContenderCache.setWinners(pindex, payoutScripts);
1650}
1651
1652} // namespace avalanche
ArgsManager gArgs
Definition: args.cpp:39
static constexpr PeerId NO_PEER
Definition: node.h:15
uint32_t PeerId
Definition: node.h:14
static constexpr size_t AVALANCHE_DEFAULT_CONFLICTING_PROOF_COOLDOWN
Conflicting proofs cooldown time default value in seconds.
Definition: avalanche.h:21
int64_t GetIntArg(const std::string &strArg, int64_t nDefault) const
Return integer argument or default value.
Definition: args.cpp:494
Non-refcounted RAII wrapper for FILE*.
Definition: streams.h:433
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: blockindex.h:25
int64_t GetBlockTime() const
Definition: blockindex.h:159
BlockHash GetBlockHash() const
Definition: blockindex.h:129
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: blockindex.h:38
void insert(Span< const uint8_t > vKey)
Definition: bloom.cpp:215
bool contains(Span< const uint8_t > vKey) const
Definition: bloom.cpp:249
CBlockIndex * ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
Definition: validation.h:1453
Fast randomness source.
Definition: random.h:411
bool Invalid(Result result, const std::string &reject_reason="", const std::string &debug_message="")
Definition: validation.h:101
Result GetResult() const
Definition: validation.h:122
std::string ToString() const
Definition: validation.h:125
bool selectStakingRewardWinner(const CBlockIndex *pprev, std::vector< std::pair< ProofId, CScript > > &winners)
Deterministically select a list of payout scripts based on the proof set and the previous block hash.
uint32_t connectedPeersScore
Definition: peermanager.h:238
std::vector< RemoteProof > getRemoteProofs(const NodeId nodeid) const
bool removeNode(NodeId nodeid)
bool setFinalized(PeerId peerid)
Latch on that this peer has a finalized proof.
bool dumpPeersToFile(const fs::path &dumpPath) const
RemoteProofSet remoteProofs
Remember which node sent which proof so we have an image of the proof set of our peers.
Definition: peermanager.h:282
bool updateNextRequestTimeForResponse(NodeId nodeid, const Response &response)
bool isDangling(const ProofId &proofid) const
bool addNode(NodeId nodeid, const ProofId &proofid, size_t max_elements)
Node API.
Definition: peermanager.cpp:33
bool unsetFlaky(const ProofId &proofid)
std::optional< bool > getRemotePresenceStatus(const ProofId &proofid) const
Get the presence remote status of a proof.
bool addNodeToPeer(const PeerSet::iterator &it)
Definition: peermanager.cpp:91
bool exists(const ProofId &proofid) const
Return true if the (valid) proof exists, but only for non-dangling proofs.
Definition: peermanager.h:418
PendingNodeSet pendingNodes
Definition: peermanager.h:224
BlockHash rejectedContenderPrevBlockHash
Definition: peermanager.h:303
bool verify() const
Perform consistency check on internal data structures.
bool hasRemoteProofStatus(const ProofId &proofid) const
std::optional< StakeContenderCacheInfo > getStakeContenderInfo(const StakeContenderId &contenderId) const
bool forPeer(const ProofId &proofid, Callable &&func) const
Definition: peermanager.h:426
void clearRemoteProofs(NodeId nodeid)
void finalizeStakeContender(const StakeContenderId &contenderId, BlockHash &prevblockhash, std::vector< std::pair< ProofId, CScript > > &newWinners)
bool latchAvaproofsSent(NodeId nodeid)
Flag that a node did send its compact proofs.
void cleanupStakeContenders(const int requestedMinHeight)
Make some of the contender cache API available.
bool updateNextRequestTimeForPoll(NodeId nodeid, SteadyMilliseconds timeout, uint64_t round)
static constexpr int SELECT_PEER_MAX_RETRY
Definition: peermanager.h:226
ProofIdSet m_unbroadcast_proofids
Track proof ids to broadcast.
Definition: peermanager.h:232
bool loadPeersFromFile(const fs::path &dumpPath, std::unordered_set< ProofRef, SaltedProofHasher > &registeredProofs)
RejectionMode
Rejection mode.
Definition: peermanager.h:406
void addUnbroadcastProof(const ProofId &proofid)
Proof broadcast API.
std::unordered_set< ProofRef, SaltedProofHasher > updatedBlockTip()
Update the peer set when a new block is connected.
void removeUnbroadcastProof(const ProofId &proofid)
void promoteStakeContendersToBlock(const CBlockIndex *pindex)
bool isBoundToPeer(const ProofId &proofid) const
bool setContenderStatusForLocalWinners(const CBlockIndex *prevblock, const std::vector< std::pair< ProofId, CScript > > winners, size_t maxPollable, std::vector< StakeContenderId > &pollableContenders)
ProofRadixTree shareableProofs
Definition: peermanager.h:190
bool saveRemoteProof(const ProofId &proofid, const NodeId nodeid, const bool present)
bool getStakeContenderWinners(const BlockHash &prevblockhash, std::vector< std::pair< ProofId, CScript > > &winners) const
CRollingBloomFilter invalidProofs
Filter for proofs that are consensus-invalid or were recently invalidated by avalanche (finalized rej...
Definition: peermanager.h:296
bool addOrUpdateNode(const PeerSet::iterator &it, NodeId nodeid, size_t max_elements)
Definition: peermanager.cpp:49
uint64_t compact()
Trigger maintenance of internal data structures.
bool addRejectedContender(const ProofId &proofid, const BlockHash &prevblockhash)
Record a rejected stake contender for prevblockhash.
std::vector< Slot > slots
Definition: peermanager.h:162
uint32_t totalPeersScore
Quorum management.
Definition: peermanager.h:237
ProofPool danglingProofPool
Definition: peermanager.h:187
StakeContenderCache stakeContenderCache
Definition: peermanager.h:305
bool hasAcceptedStakeWinner(const BlockHash &prevblockhash) const
void setInvalid(const ProofId &proofid)
int getStakeContenderStatus(const StakeContenderId &contenderId, BlockHash &prevblockhashout) const
bool isFlaky(const ProofId &proofid) const
ChainstateManager & chainman
Definition: peermanager.h:242
bool isInvalid(const ProofId &proofid) const
std::unordered_set< ProofId, SaltedProofIdHasher > manualFlakyProofids
Definition: peermanager.h:298
bool removePeer(const PeerId peerid)
Remove an existing peer.
bool isImmature(const ProofId &proofid) const
bool rejectProof(const ProofId &proofid, RejectionMode mode=RejectionMode::DEFAULT)
ProofPool immatureProofPool
Definition: peermanager.h:186
RegistrationMode
Registration mode.
Definition: peermanager.h:383
ProofPool conflictingProofPool
Definition: peermanager.h:185
bool isStakingPreconsensusActivated() const
Definition: peermanager.h:591
static constexpr size_t MAX_REMOTE_PROOFS
Definition: peermanager.h:308
bool setFlaky(const ProofId &proofid)
void addStakeContender(const ProofRef &proof)
std::atomic< bool > needMoreNodes
Flag indicating that we failed to select a node and need to expand our node set.
Definition: peermanager.h:210
PeerId selectPeer() const
Randomly select a peer to poll.
std::unordered_set< ProofId, SaltedProofIdHasher > rejectedContenderProofids
Definition: peermanager.h:302
bool isInConflictingPool(const ProofId &proofid) const
bool isRemotelyPresentProof(const ProofId &proofid) const
static constexpr int SELECT_NODE_MAX_RETRY
Definition: peermanager.h:227
void cleanupDanglingProofs(std::unordered_set< ProofRef, SaltedProofHasher > &registeredProofs)
void acceptStakeContender(const StakeContenderId &contenderId)
ProofRef getProof(const ProofId &proofid) const
bool registerProof(const ProofRef &proof, ProofRegistrationState &registrationState, RegistrationMode mode=RegistrationMode::DEFAULT)
void rejectStakeContender(const StakeContenderId &contenderId)
bool removeNodeFromPeer(const PeerSet::iterator &it, uint32_t count=1)
bool updateNextPossibleConflictTime(PeerId peerid, const std::chrono::seconds &nextTime)
Proof and Peer related API.
void moveToConflictingPool(const ProofContainer &proofs)
bool setStakeContenderWinners(const CBlockIndex *pindex, const std::vector< CScript > &payoutScripts)
AddProofStatus addProofIfPreferred(const ProofRef &proof, ConflictingProofSet &conflictingProofs)
Attempt to add a proof to the pool.
Definition: proofpool.cpp:58
size_t size() const
Definition: proofpool.h:140
AddProofStatus addProofIfNoConflict(const ProofRef &proof, ConflictingProofSet &conflictingProofs)
Attempt to add a proof to the pool, and fail if there is a conflict on any UTXO.
Definition: proofpool.cpp:30
size_t countProofs() const
Definition: proofpool.cpp:133
bool removeProof(ProofId proofid)
Definition: proofpool.cpp:83
void forEachProof(Callable &&func) const
Definition: proofpool.h:123
ProofRef getProof(const ProofId &proofid) const
Definition: proofpool.cpp:116
std::set< ProofRef, ConflictingProofComparator > ConflictingProofSet
Definition: proofpool.h:87
ProofRef getLowestScoreProof() const
Definition: proofpool.cpp:127
std::unordered_set< ProofRef, SaltedProofHasher > rescan(PeerManager &peerManager)
Definition: proofpool.cpp:90
ConflictingProofSet getConflicts(const ProofRef &proof) const
Return proofs in this pool that share any UTXO with proof.
Definition: proofpool.cpp:14
bool getWinners(const BlockHash &prevblockhash, std::vector< std::pair< ProofId, CScript > > &winners) const
bool accept(const StakeContenderId &contenderId)
Helpers to set avalanche state of a contender.
void cleanup(const int requestedMinHeight)
size_t getPollableContenders(const BlockHash &prevblockhash, size_t maxPollable, std::vector< StakeContenderId > &pollableContenders) const
Get the best ranking contenders, accepted contenders ranking first.
bool reject(const StakeContenderId &contenderId)
bool hasAcceptedWinner(const BlockHash &prevblockhash) const
True if any accepted contender is in the winner set for prevblockhash.
bool setWinners(const CBlockIndex *pindex, const std::vector< CScript > &payoutScripts)
Set proof(s) that should be treated as winners (already finalized).
bool add(const CBlockIndex *pindex, const ProofRef &proof, uint8_t status=StakeContenderStatus::UNKNOWN)
Add a proof to consider in staking rewards pre-consensus.
std::optional< StakeContenderCacheInfo > getContenderInfo(const StakeContenderId &contenderId) const
void promoteToBlock(const CBlockIndex *activeTip, std::function< bool(const ProofId &proofid)> const &shouldPromote)
Promote cache entries to a the active chain tip.
int getVoteStatus(const StakeContenderId &contenderId, BlockHash &prevblockhashout) const
Get contender acceptance state for avalanche voting.
bool finalize(const StakeContenderId &contenderId)
std::string ToString() const
Definition: uint256.h:117
std::string GetHex() const
Definition: uint256.cpp:10
Path class wrapper to block calls to the fs::path(std::string) implicit constructor and the fs::path:...
Definition: fs.h:30
static const uint256 ZERO
Definition: uint256.h:210
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition: cs_main.cpp:7
int64_t NodeId
Definition: eviction.h:16
bool RenameOver(fs::path src, fs::path dest)
Rename src to dest.
Definition: fs_helpers.cpp:258
bool FileCommit(FILE *file)
Ensure file contents are fully committed to disk, using a platform-specific feature analogous to fsyn...
Definition: fs_helpers.cpp:111
std::string HexStr(const Span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
Definition: hex_base.cpp:30
#define LogTrace(category,...)
Definition: logging.h:448
#define LogDebug(category,...)
Definition: logging.h:446
#define LogPrintf(...)
Definition: logging.h:424
@ AVALANCHE
Definition: logging.h:91
ProofRegistrationResult
Definition: peermanager.h:144
static constexpr uint32_t AVALANCHE_MAX_IMMATURE_PROOFS
Maximum number of immature proofs the peer manager will accept from the network.
Definition: peermanager.h:44
static bool isImmatureState(const ProofValidationState &state)
static constexpr uint64_t PEERS_DUMP_VERSION
Definition: peermanager.cpp:31
PeerId selectPeerImpl(const std::vector< Slot > &slots, const uint64_t slot, const uint64_t max)
Internal methods that are exposed for testing purposes.
RCUPtr< const Proof > ProofRef
Definition: proof.h:183
static std::string PathToString(const path &path)
Convert path object to byte string.
Definition: fs.h:147
FILE * fopen(const fs::path &p, const char *mode)
Definition: fs.cpp:30
static constexpr NodeId NO_NODE
Special NodeId that represent no node.
Definition: nodeid.h:15
Response response
Definition: processor.cpp:537
static std::string ToString(const CService &ip)
Definition: db.h:36
A BlockHash is a unqiue identifier for a block.
Definition: blockhash.h:13
RCUPtr< T > remove(const KeyType &key)
Remove an element from the tree.
Definition: radix.h:180
RCUPtr< T > get(const KeyType &key)
Get the value corresponding to a key.
Definition: radix.h:117
bool forEachLeaf(Callable &&func) const
Definition: radix.h:143
bool insert(const RCUPtr< T > &value)
Insert a value into the tree.
Definition: radix.h:111
Facility for using an uint256 as a radix tree key.
uint64_t last_round
Definition: node.h:24
SteadyMilliseconds nextRequestTime
Definition: node.h:22
bool avaproofsSent
Definition: node.h:23
std::chrono::seconds registration_time
Definition: peermanager.h:93
std::chrono::seconds nextPossibleConflictTime
Definition: peermanager.h:94
uint32_t node_count
Definition: peermanager.h:87
static constexpr auto DANGLING_TIMEOUT
Consider dropping the peer if no node is attached after this timeout expired.
Definition: peermanager.h:100
uint32_t index
Definition: peermanager.h:86
uint32_t getScore() const
Definition: peermanager.h:109
ProofRef proof
Definition: peermanager.h:89
uint64_t getStop() const
Definition: peermanager.h:73
uint64_t getStart() const
Definition: peermanager.h:72
PeerId getPeerId() const
Definition: peermanager.h:75
StakeContenderIds are unique for each block to ensure that the peer polling for their acceptance has ...
double ComputeProofRewardRank(uint32_t proofScore) const
To make sure the selection is properly weighted according to the proof score, we normalize the conten...
#define LOCK(cs)
Definition: sync.h:295
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:343
static int count
#define NO_THREAD_SAFETY_ANALYSIS
Definition: threadsafety.h:58
int64_t GetTime()
DEPRECATED Use either ClockType::now() or Now<TimePointType>() if a cast is needed.
Definition: time.cpp:80
std::chrono::time_point< std::chrono::steady_clock, std::chrono::milliseconds > SteadyMilliseconds
Definition: time.h:33
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1203
AssertLockHeld(pool.cs)
assert(!tx.IsCoinBase())