diff --git a/src/avalanche/processor.cpp b/src/avalanche/processor.cpp --- a/src/avalanche/processor.cpp +++ b/src/avalanche/processor.cpp @@ -789,6 +789,14 @@ connectedPeersScore = peerManager->getConnectedPeersScore(); } + // If we have our own proof we need to consider its score in addition to our + // peers' scores. + ProofRef localProof = getLocalProof(); + if (localProof != nullptr) { + totalPeersScore += localProof->getScore(); + connectedPeersScore += localProof->getScore(); + } + // Ensure enough is being staked overall if (totalPeersScore < minQuorumScore) { return false; diff --git a/src/avalanche/test/processor_tests.cpp b/src/avalanche/test/processor_tests.cpp --- a/src/avalanche/test/processor_tests.cpp +++ b/src/avalanche/test/processor_tests.cpp @@ -8,8 +8,10 @@ #include #include #include +#include #include #include +#include #include // For ::PeerManager #include #include @@ -1286,8 +1288,27 @@ }); BOOST_CHECK(processor->isQuorumEstablished()); + // Reset the processor using a local proof that has enough stake for a + // quorum and ensure the available quorum is recognized. + auto proof = GetProof(); + gArgs.ForceSetArg("-avaminquorumstake", ToString(proof->getScore())); + gArgs.ForceSetArg("-avaproof", proof->ToHex()); + gArgs.ForceSetArg("-avamasterkey", EncodeSecret(masterpriv)); + processor = Processor::MakeProcessor(*m_node.args, *m_node.chain, + m_node.connman.get(), error); + + // We have an active quorum but don't actually have any peers. + BOOST_CHECK(processor != nullptr); + BOOST_CHECK(processor->isQuorumEstablished()); + processor->withPeerManager([&](avalanche::PeerManager &pm) { + BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), 0); + BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), 0); + }); + gArgs.ClearForcedArg("-avaminquorumstake"); gArgs.ClearForcedArg("-avaminquorumconnectedstakeratio"); + gArgs.ClearForcedArg("-avaproof"); + gArgs.ClearForcedArg("-avamasterkey"); } BOOST_AUTO_TEST_CASE(quorum_detection_parameter_validation) { diff --git a/test/functional/abc_p2p_avalanche_quorum.py b/test/functional/abc_p2p_avalanche_quorum.py --- a/test/functional/abc_p2p_avalanche_quorum.py +++ b/test/functional/abc_p2p_avalanche_quorum.py @@ -20,14 +20,22 @@ class AvalancheQuorumTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 1 - self.extra_args = [ - ['-enableavalanche=1', - '-avacooldown=0', - '-avatimeout=0', - '-avaminquorumstake=100000000', - '-avaminquorumconnectedstakeratio=0.8'] - ] + self.num_nodes = 3 + + self.extra_args = [['-enableavalanche=1', + '-avacooldown=0', + '-avatimeout=0', + '-avaminquorumstake=100000000', + '-avaminquorumconnectedstakeratio=0.8']] + + # Nodes 2 and 3 are a single peer + self.extra_args.extend(2 * [['-enableavalanche=1', + '-avacooldown=0', + '-avatimeout=0', + '-avaminquorumstake=100000000', + '-avaminquorumconnectedstakeratio=0.8', + '-avamasterkey=cPaKTtT3Nxk5E8uERSqnDiDSSnCSZPP6qa4AiipQb2ETgYukADtR', + '-avaproof=0000000000000000000000000000000021025582a9ca9e57a560306c07b2ed2124fa97a14969f449106747e91490d30012c501b123754aaeb592ae6282bc169b3a68a92d9fca6afa1ad6aaaa5e323f4684304c000000000000c16ff2862300030000002103e6aefd9321197f88f23d0f4ffe18b84d4997998ca0da9b7097399aef8433b1e018473378520efbd5f64ea867675665cc8fbdbe953bc7e77bcbe47c4505d85c1037026b42d00d9e439aef0112d6fb3fa66cc55031daeddd722ba485cd91206d71']]) def mock_forward(self, delta): self.mock_time += delta @@ -44,7 +52,8 @@ poll_node_pubkey = ECPubKey() poll_node_pubkey.set(bytes.fromhex(node.getavalanchekey())) - def poll_and_assert_response(expected): + def poll_and_assert_response( + expected, poll_node=poll_node, pubkey=poll_node_pubkey): # Send poll for best block block = int(node.getbestblockhash(), 16) poll_node.send_poll([block]) @@ -52,7 +61,7 @@ # Get response and check that the vote is what we expect response = poll_node.wait_for_avaresponse() r = response.response - assert poll_node_pubkey.verify_schnorr(response.sig, r.get_hash()) + assert pubkey.verify_schnorr(response.sig, r.get_hash()) assert_equal(len(r.votes), 1) actual = repr(r.votes[0]) @@ -106,6 +115,19 @@ addavalanchenode(peers[1]) poll_and_assert_response(AvalancheVoteError.ACCEPTED) + # Nodes 2 and 3 represent the same peer, and their own stake is required + # to form a quorum. Even though they don't connect to each other they + # are active due to their own local stakes. + for node in [self.nodes[1], self.nodes[2]]: + info = node.getavalancheinfo() + assert info['active'] + assert_equal(info['network']['node_count'], 0) + poll_node = get_ava_p2p_interface(node) + pubkey = ECPubKey() + pubkey.set(bytes.fromhex(node.getavalanchekey())) + poll_and_assert_response( + AvalancheVoteError.ACCEPTED, poll_node, pubkey) + if __name__ == '__main__': AvalancheQuorumTest().main()