diff --git a/src/avalanche/peermanager.h b/src/avalanche/peermanager.h --- a/src/avalanche/peermanager.h +++ b/src/avalanche/peermanager.h @@ -87,6 +87,7 @@ uint32_t node_count = 0; ProofRef proof; + bool hasFinalized = false; // The network stack uses timestamp in seconds, so we oblige. std::chrono::seconds registration_time; @@ -277,6 +278,11 @@ bool updateNextPossibleConflictTime(PeerId peerid, const std::chrono::seconds &nextTime); + /** + * Latch on that this peer has a finalized proof. + */ + bool setFinalized(PeerId peerid); + /** * Registration mode * - DEFAULT: Default policy, register only if the proof is unknown and has diff --git a/src/avalanche/peermanager.cpp b/src/avalanche/peermanager.cpp --- a/src/avalanche/peermanager.cpp +++ b/src/avalanche/peermanager.cpp @@ -184,6 +184,18 @@ return it->nextPossibleConflictTime == nextTime; } +bool PeerManager::setFinalized(PeerId peerid) { + auto it = peers.find(peerid); + if (it == peers.end()) { + // No such peer + return false; + } + + peers.modify(it, [&](Peer &p) { p.hasFinalized = true; }); + + return true; +} + template void PeerManager::moveToConflictingPool(const ProofContainer &proofs) { auto &peersView = peers.get(); diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -5153,6 +5153,10 @@ proofid, [&](const avalanche::Peer &peer) { pm.updateNextPossibleConflictTime( peer.peerid, nextCooldownTimePoint); + if (u.getStatus() == + avalanche::VoteStatus::Finalized) { + pm.setFinalized(peer.peerid); + } // Only fail if the peer was not // created return true; diff --git a/src/rpc/avalanche.cpp b/src/rpc/avalanche.cpp --- a/src/rpc/avalanche.cpp +++ b/src/rpc/avalanche.cpp @@ -670,6 +670,9 @@ {RPCResult::Type::NUM, "connected_proof_count", "The number of avalanche proofs with at least one node " "we are connected to."}, + {RPCResult::Type::NUM, "finalized_proof_count", + "The number of known avalanche proofs that have been " + "finalized by avalanche."}, {RPCResult::Type::NUM, "conflicting_proof_count", "The number of known avalanche proofs that conflict with " "valid proofs."}, @@ -732,6 +735,7 @@ uint64_t proofCount{0}; uint64_t connectedProofCount{0}; + uint64_t finalizedProofCount{0}; Amount totalStakes = Amount::zero(); Amount connectedStakes = Amount::zero(); @@ -748,6 +752,10 @@ ++proofCount; totalStakes += proofStake; + if (peer.hasFinalized) { + ++finalizedProofCount; + } + if (peer.node_count > 0) { ++connectedProofCount; connectedStakes += proofStake; @@ -756,6 +764,7 @@ network.pushKV("proof_count", proofCount); network.pushKV("connected_proof_count", connectedProofCount); + network.pushKV("finalized_proof_count", finalizedProofCount); network.pushKV("conflicting_proof_count", uint64_t(pm.getConflictingProofCount())); network.pushKV("orphan_proof_count", diff --git a/test/functional/abc_rpc_getavalancheinfo.py b/test/functional/abc_rpc_getavalancheinfo.py --- a/test/functional/abc_rpc_getavalancheinfo.py +++ b/test/functional/abc_rpc_getavalancheinfo.py @@ -14,9 +14,9 @@ get_ava_p2p_interface, ) from test_framework.key import ECKey -from test_framework.messages import LegacyAvalancheProof +from test_framework.messages import LegacyAvalancheProof, AvalancheProofVoteResponse, AvalancheVote from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal +from test_framework.util import assert_equal, assert_greater_than from test_framework.wallet_util import bytes_to_wif @@ -29,8 +29,8 @@ '-enableavalancheproofreplacement=1', f'-avalancheconflictingproofcooldown={self.conflicting_proof_cooldown}', '-avaproofstakeutxoconfirmations=2', - '-avacooldown=', - '-avatimeout=100', + '-avacooldown=0', + '-avatimeout=5000', '-enableavalanchepeerdiscovery=1', '-avaminquorumstake=250000000', '-avaminquorumconnectedstakeratio=0.9', @@ -68,6 +68,7 @@ "network": { "proof_count": 0, "connected_proof_count": 0, + "finalized_proof_count": 0, "conflicting_proof_count": 0, "orphan_proof_count": 0, "total_stake_amount": Decimal('0.00'), @@ -97,6 +98,7 @@ "network": { "proof_count": 0, "connected_proof_count": 0, + "finalized_proof_count": 0, "conflicting_proof_count": 0, "orphan_proof_count": 0, "total_stake_amount": Decimal('0.00'), @@ -122,6 +124,7 @@ "network": { "proof_count": 0, "connected_proof_count": 0, + "finalized_proof_count": 0, "conflicting_proof_count": 0, "orphan_proof_count": 0, "total_stake_amount": Decimal('0.00'), @@ -138,9 +141,14 @@ mock_time = int(time.time()) node.setmocktime(mock_time) - N = 10 + privkeys = [] + proofs = [] + quorum = [] + N = 20 for _ in range(N): _privkey, _proof = gen_proof(node) + proofs.append(_proof) + privkeys.append(_privkey) # For each proof, also make a conflicting one stakes = create_coinbase_stakes( @@ -155,6 +163,7 @@ success = node.addavalanchenode( n.nodeid, _privkey.get_pubkey().get_bytes().hex(), _proof.serialize().hex()) assert success is True + quorum.append(n) mock_time += self.conflicting_proof_cooldown node.setmocktime(mock_time) @@ -177,6 +186,7 @@ "network": { "proof_count": N, "connected_proof_count": N, + "finalized_proof_count": 0, "conflicting_proof_count": N, "orphan_proof_count": 1, "total_stake_amount": coinbase_amount * N, @@ -195,6 +205,9 @@ n = node.p2ps.pop() n.peer_disconnect() n.wait_for_disconnect() + #proofs.pop() + #quorum.pop() + #privkeys.pop() self.wait_until( lambda: node.getavalancheinfo() == handle_legacy_format({ @@ -209,6 +222,7 @@ "network": { "proof_count": N, "connected_proof_count": N - D, + "finalized_proof_count": 0, "conflicting_proof_count": N, "orphan_proof_count": 1, "total_stake_amount": coinbase_amount * N, @@ -259,6 +273,7 @@ # Orphan became mature "proof_count": N + 1, "connected_proof_count": N - D, + "finalized_proof_count": 0, "conflicting_proof_count": N, "orphan_proof_count": 0, "total_stake_amount": coinbase_amount * (N + 1), @@ -269,6 +284,47 @@ } }) + self.log.info("Finalize the proofs for some peers") + + # Vote until proofs have finalized + def vote_for_all_proofs(): + responded = False + ni = -1 + for n in quorum: + ni += 1 + + if not n.is_connected: + self.log.info("peer {} is not connected".format(ni)) + continue + + from test_framework.p2p import p2p_lock + with p2p_lock: + self.log.info("polls for peer {}: {}".format(ni, n.avapolls)) + + poll = n.get_avapoll_if_available() + + # That node has not received a poll + if poll is None: + self.log.info("peer {} has no poll".format(ni)) + continue + + # Respond yes to all polls + votes = [] + for inv in poll.invs: + votes.append(AvalancheVote(AvalancheProofVoteResponse.ACTIVE, inv.hash)) + + n.send_avaresponse(poll.round, votes, privkeys[ni]) + responded = True + + return responded + + expected_logs = [] + for p in proofs: + expected_logs.append(f"Avalanche finalized proof {p.proofid:0{64}x}") + + with node.assert_debug_log(expected_logs): + self.wait_until(lambda: not vote_for_all_proofs(), timeout=20) + self.log.info("Disconnect all the nodes") for n in node.p2ps: @@ -287,6 +343,7 @@ "network": { "proof_count": N + 1, "connected_proof_count": 0, + "finalized_proof_count": 0, "conflicting_proof_count": N, "orphan_proof_count": 0, "total_stake_amount": coinbase_amount * (N + 1),