Changeset View
Changeset View
Standalone View
Standalone View
src/avalanche/test/processor_tests.cpp
| Show All 21 Lines | |||||
| #include <avalanche/test/util.h> | #include <avalanche/test/util.h> | ||||
| #include <test/util/setup_common.h> | #include <test/util/setup_common.h> | ||||
| #include <boost/mpl/list.hpp> | #include <boost/mpl/list.hpp> | ||||
| #include <boost/test/unit_test.hpp> | #include <boost/test/unit_test.hpp> | ||||
| #include <functional> | #include <functional> | ||||
| #include <type_traits> | |||||
| #include <vector> | #include <vector> | ||||
| using namespace avalanche; | using namespace avalanche; | ||||
| namespace avalanche { | namespace avalanche { | ||||
| namespace { | namespace { | ||||
| struct AvalancheTest { | struct AvalancheTest { | ||||
| static void runEventLoop(avalanche::Processor &p) { p.runEventLoop(); } | static void runEventLoop(avalanche::Processor &p) { p.runEventLoop(); } | ||||
| ▲ Show 20 Lines • Show All 215 Lines • ▼ Show 20 Lines | std::vector<Vote> buildVotesForItems(uint32_t error, | ||||
| // Votes are sorted by most work first | // Votes are sorted by most work first | ||||
| std::sort(items.begin(), items.end(), CBlockIndexWorkComparator()); | std::sort(items.begin(), items.end(), CBlockIndexWorkComparator()); | ||||
| for (auto &item : reverse_iterate(items)) { | for (auto &item : reverse_iterate(items)) { | ||||
| votes.emplace_back(error, item->GetBlockHash()); | votes.emplace_back(error, item->GetBlockHash()); | ||||
| } | } | ||||
| return votes; | return votes; | ||||
| } | } | ||||
| void invalidateItem(CBlockIndex *pindex) { | |||||
| pindex->nStatus = pindex->nStatus.withFailed(); | |||||
| } | |||||
| }; | }; | ||||
| struct ProofProvider { | struct ProofProvider { | ||||
| AvalancheTestingSetup *fixture; | AvalancheTestingSetup *fixture; | ||||
| std::vector<ProofUpdate> updates; | std::vector<ProofUpdate> updates; | ||||
| uint32_t invType; | uint32_t invType; | ||||
| Show All 20 Lines | bool registerVotes(NodeId nodeid, const avalanche::Response &response, | ||||
| nodeid, response, blockUpdates, updates, banscore, error); | nodeid, response, blockUpdates, updates, banscore, error); | ||||
| } | } | ||||
| bool registerVotes(NodeId nodeid, const avalanche::Response &response) { | bool registerVotes(NodeId nodeid, const avalanche::Response &response) { | ||||
| std::string error; | std::string error; | ||||
| return registerVotes(nodeid, response, error); | return registerVotes(nodeid, response, error); | ||||
| } | } | ||||
| bool addToReconcile(const ProofRef &proof) { | bool addToReconcile(const ProofRef &proof) { | ||||
| fixture->m_processor->addProofToReconcile(proof); | return fixture->m_processor->addProofToReconcile(proof); | ||||
| return true; | |||||
| } | } | ||||
| std::vector<Vote> buildVotesForItems(uint32_t error, | std::vector<Vote> buildVotesForItems(uint32_t error, | ||||
| std::vector<ProofRef> &&items) { | std::vector<ProofRef> &&items) { | ||||
| size_t numItems = items.size(); | size_t numItems = items.size(); | ||||
| std::vector<Vote> votes; | std::vector<Vote> votes; | ||||
| votes.reserve(numItems); | votes.reserve(numItems); | ||||
| // Votes are sorted by high score first | // Votes are sorted by high score first | ||||
| std::sort(items.begin(), items.end(), ProofComparatorByScore()); | std::sort(items.begin(), items.end(), ProofComparatorByScore()); | ||||
| for (auto &item : items) { | for (auto &item : items) { | ||||
| votes.emplace_back(error, item->getId()); | votes.emplace_back(error, item->getId()); | ||||
| } | } | ||||
| return votes; | return votes; | ||||
| } | } | ||||
| void invalidateItem(const ProofRef &proof) { | |||||
| fixture->m_processor->withPeerManager([&](avalanche::PeerManager &pm) { | |||||
| pm.rejectProof(proof->getId(), | |||||
| avalanche::PeerManager::RejectionMode::INVALIDATE); | |||||
| }); | |||||
| } | |||||
| }; | }; | ||||
| } // namespace | } // namespace | ||||
| BOOST_FIXTURE_TEST_SUITE(processor_tests, AvalancheTestingSetup) | BOOST_FIXTURE_TEST_SUITE(processor_tests, AvalancheTestingSetup) | ||||
| // FIXME A std::tuple can be used instead of boost::mpl::list after boost 1.67 | // FIXME A std::tuple can be used instead of boost::mpl::list after boost 1.67 | ||||
| using VoteItemProviders = boost::mpl::list<BlockProvider, ProofProvider>; | using VoteItemProviders = boost::mpl::list<BlockProvider, ProofProvider>; | ||||
| Show All 11 Lines | for (auto s : status) { | ||||
| BlockUpdate abu(pindex, s); | BlockUpdate abu(pindex, s); | ||||
| // The use of BOOST_CHECK instead of BOOST_CHECK_EQUAL prevents from | // The use of BOOST_CHECK instead of BOOST_CHECK_EQUAL prevents from | ||||
| // having to define operator<<() for each argument type. | // having to define operator<<() for each argument type. | ||||
| BOOST_CHECK(abu.getVoteItem() == pindex); | BOOST_CHECK(abu.getVoteItem() == pindex); | ||||
| BOOST_CHECK(abu.getStatus() == s); | BOOST_CHECK(abu.getStatus() == s); | ||||
| } | } | ||||
| } | } | ||||
| BOOST_AUTO_TEST_CASE(block_reconcile_twice) { | BOOST_AUTO_TEST_CASE_TEMPLATE(item_reconcile_twice, P, VoteItemProviders) { | ||||
| CBlock block = CreateAndProcessBlock({}, CScript()); | P provider(this); | ||||
| const BlockHash blockHash = block.GetHash(); | |||||
| CBlockIndex *pindex; | |||||
| { | |||||
| LOCK(cs_main); | |||||
| pindex = g_chainman.m_blockman.LookupBlockIndex(blockHash); | |||||
| } | |||||
| // Adding the block twice does nothing. | auto item = provider.buildVoteItem(); | ||||
| BOOST_CHECK(m_processor->addBlockToReconcile(pindex)); | |||||
| BOOST_CHECK(!m_processor->addBlockToReconcile(pindex)); | // Adding the item twice does nothing. | ||||
| BOOST_CHECK(m_processor->isAccepted(pindex)); | BOOST_CHECK(provider.addToReconcile(item)); | ||||
| BOOST_CHECK(!provider.addToReconcile(item)); | |||||
| BOOST_CHECK(m_processor->isAccepted(item)); | |||||
| } | } | ||||
| BOOST_AUTO_TEST_CASE(block_null) { | BOOST_AUTO_TEST_CASE_TEMPLATE(item_null, P, VoteItemProviders) { | ||||
| P provider(this); | |||||
| // Check that null case is handled on the public interface | // Check that null case is handled on the public interface | ||||
| BOOST_CHECK(!m_processor->isAccepted(nullptr)); | BOOST_CHECK(!m_processor->isAccepted(nullptr)); | ||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(nullptr), -1); | BOOST_CHECK_EQUAL(m_processor->getConfidence(nullptr), -1); | ||||
| BOOST_CHECK(!m_processor->addBlockToReconcile(nullptr)); | auto item = decltype(provider.buildVoteItem())(); | ||||
| BOOST_CHECK(item == nullptr); | |||||
| BOOST_CHECK(!provider.addToReconcile(item)); | |||||
| // Check that adding blocks to vote on doesn't change the outcome. A | // Check that adding item to vote on doesn't change the outcome. A | ||||
| // comparator is used under the hood, and this is skipped if there are no | // comparator is used under the hood, and this is skipped if there are no | ||||
| // vote records. | // vote records. | ||||
| CBlock block = CreateAndProcessBlock({}, CScript()); | item = provider.buildVoteItem(); | ||||
| const BlockHash blockHash = block.GetHash(); | BOOST_CHECK(provider.addToReconcile(item)); | ||||
| CBlockIndex *pindex; | |||||
| { | |||||
| LOCK(cs_main); | |||||
| pindex = g_chainman.m_blockman.LookupBlockIndex(blockHash); | |||||
| } | |||||
| BOOST_CHECK(m_processor->addBlockToReconcile(pindex)); | |||||
| BOOST_CHECK(!m_processor->isAccepted(nullptr)); | BOOST_CHECK(!m_processor->isAccepted(nullptr)); | ||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(nullptr), -1); | BOOST_CHECK_EQUAL(m_processor->getConfidence(nullptr), -1); | ||||
| } | } | ||||
| namespace { | namespace { | ||||
| Response next(Response &r) { | Response next(Response &r) { | ||||
| auto copy = r; | auto copy = r; | ||||
| ▲ Show 20 Lines • Show All 355 Lines • ▼ Show 20 Lines | BOOST_AUTO_TEST_CASE_TEMPLATE(poll_and_response, P, VoteItemProviders) { | ||||
| // But they are accepted in order. | // But they are accepted in order. | ||||
| resp = {getRound(), 0, votes}; | resp = {getRound(), 0, votes}; | ||||
| runEventLoop(); | runEventLoop(); | ||||
| BOOST_CHECK(provider.registerVotes(avanodeid, resp)); | BOOST_CHECK(provider.registerVotes(avanodeid, resp)); | ||||
| BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
| BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid); | ||||
| } | } | ||||
| BOOST_AUTO_TEST_CASE(dont_poll_invalid_block) { | BOOST_AUTO_TEST_CASE_TEMPLATE(dont_poll_invalid_item, P, VoteItemProviders) { | ||||
| std::vector<BlockUpdate> updates; | P provider(this); | ||||
| auto &updates = provider.updates; | |||||
| const uint32_t invType = provider.invType; | |||||
| CBlock blockA = CreateAndProcessBlock({}, CScript()); | auto itemA = provider.buildVoteItem(); | ||||
| CBlock blockB = CreateAndProcessBlock({}, CScript()); | auto itemB = provider.buildVoteItem(); | ||||
| const BlockHash blockHashA = blockA.GetHash(); | |||||
| const BlockHash blockHashB = blockB.GetHash(); | |||||
| const CBlockIndex *pindexA; | |||||
| CBlockIndex *pindexB; | |||||
| { | |||||
| LOCK(cs_main); | |||||
| pindexA = g_chainman.m_blockman.LookupBlockIndex(blockHashA); | |||||
| pindexB = g_chainman.m_blockman.LookupBlockIndex(blockHashB); | |||||
| } | |||||
| auto avanodes = ConnectNodes(); | auto avanodes = ConnectNodes(); | ||||
| // Register the blocks and check they are added to the list of elements to | // Build votes to get proper ordering | ||||
| std::vector<Vote> votes = provider.buildVotesForItems(0, {itemA, itemB}); | |||||
| // Register the items and check they are added to the list of elements to | |||||
| // poll. | // poll. | ||||
| BOOST_CHECK(m_processor->addBlockToReconcile(pindexA)); | BOOST_CHECK(provider.addToReconcile(itemA)); | ||||
| BOOST_CHECK(m_processor->addBlockToReconcile(pindexB)); | BOOST_CHECK(provider.addToReconcile(itemB)); | ||||
| auto invs = getInvsForNextPoll(); | auto invs = getInvsForNextPoll(); | ||||
| BOOST_CHECK_EQUAL(invs.size(), 2); | BOOST_CHECK_EQUAL(invs.size(), 2); | ||||
| BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | for (size_t i = 0; i < invs.size(); i++) { | ||||
| BOOST_CHECK(invs[0].hash == blockHashB); | BOOST_CHECK_EQUAL(invs[i].type, invType); | ||||
| BOOST_CHECK_EQUAL(invs[1].type, MSG_BLOCK); | BOOST_CHECK(invs[i].hash == votes[i].GetHash()); | ||||
| BOOST_CHECK(invs[1].hash == blockHashA); | } | ||||
| // When a block is marked invalid, stop polling. | // When an item is marked invalid, stop polling. | ||||
| pindexB->nStatus = pindexB->nStatus.withFailed(); | provider.invalidateItem(itemB); | ||||
| Response resp{getRound(), 0, {Vote(0, blockHashA)}}; | |||||
| Response goodResp{getRound(), 0, {Vote(0, provider.getVoteItemId(itemA))}}; | |||||
| runEventLoop(); | runEventLoop(); | ||||
| BOOST_CHECK(registerVotes(avanodes[0]->GetId(), resp, updates)); | BOOST_CHECK(provider.registerVotes(avanodes[0]->GetId(), goodResp)); | ||||
| BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
| // Votes including itemB are rejected | |||||
| Response badResp{getRound(), 0, votes}; | |||||
| runEventLoop(); | |||||
| std::string error; | |||||
| BOOST_CHECK(!provider.registerVotes(avanodes[1]->GetId(), badResp, error)); | |||||
| BOOST_CHECK_EQUAL(error, "invalid-ava-response-size"); | |||||
| } | } | ||||
| BOOST_TEST_DECORATOR(*boost::unit_test::timeout(60)) | BOOST_TEST_DECORATOR(*boost::unit_test::timeout(60)) | ||||
| BOOST_AUTO_TEST_CASE_TEMPLATE(poll_inflight_timeout, P, VoteItemProviders) { | BOOST_AUTO_TEST_CASE_TEMPLATE(poll_inflight_timeout, P, VoteItemProviders) { | ||||
| P provider(this); | P provider(this); | ||||
| const auto item = provider.buildVoteItem(); | const auto item = provider.buildVoteItem(); | ||||
| const auto itemid = provider.getVoteItemId(item); | const auto itemid = provider.getVoteItemId(item); | ||||
| ▲ Show 20 Lines • Show All 260 Lines • ▼ Show 20 Lines | BOOST_AUTO_TEST_CASE(destructor) { | ||||
| schedulerThread.join(); | schedulerThread.join(); | ||||
| } | } | ||||
| BOOST_AUTO_TEST_CASE(add_proof_to_reconcile) { | BOOST_AUTO_TEST_CASE(add_proof_to_reconcile) { | ||||
| uint32_t score = MIN_VALID_PROOF_SCORE; | uint32_t score = MIN_VALID_PROOF_SCORE; | ||||
| auto addProofToReconcile = [&](uint32_t proofScore) { | auto addProofToReconcile = [&](uint32_t proofScore) { | ||||
| auto proof = buildRandomProof(proofScore); | auto proof = buildRandomProof(proofScore); | ||||
| m_processor->addProofToReconcile(proof); | m_processor->withPeerManager([&](avalanche::PeerManager &pm) { | ||||
| BOOST_CHECK(pm.registerProof(proof)); | |||||
| }); | |||||
| BOOST_CHECK(m_processor->addProofToReconcile(proof)); | |||||
| return proof; | return proof; | ||||
| }; | }; | ||||
| for (size_t i = 0; i < AVALANCHE_MAX_ELEMENT_POLL; i++) { | for (size_t i = 0; i < AVALANCHE_MAX_ELEMENT_POLL; i++) { | ||||
| auto proof = addProofToReconcile(++score); | auto proof = addProofToReconcile(++score); | ||||
| auto invs = AvalancheTest::getInvsForNextPoll(*m_processor); | auto invs = AvalancheTest::getInvsForNextPoll(*m_processor); | ||||
| BOOST_CHECK_EQUAL(invs.size(), i + 1); | BOOST_CHECK_EQUAL(invs.size(), i + 1); | ||||
| Show All 28 Lines | BOOST_AUTO_TEST_CASE(add_proof_to_reconcile) { | ||||
| auto proof = addProofToReconcile(--score); | auto proof = addProofToReconcile(--score); | ||||
| auto invs = AvalancheTest::getInvsForNextPoll(*m_processor); | auto invs = AvalancheTest::getInvsForNextPoll(*m_processor); | ||||
| for (auto &inv : invs) { | for (auto &inv : invs) { | ||||
| BOOST_CHECK_NE(inv.hash, proof->getId()); | BOOST_CHECK_NE(inv.hash, proof->getId()); | ||||
| } | } | ||||
| } | } | ||||
| BOOST_AUTO_TEST_CASE(proof_record) { | BOOST_AUTO_TEST_CASE(proof_record) { | ||||
| gArgs.ForceSetArg("-enableavalancheproofreplacement", "1"); | |||||
| gArgs.ForceSetArg("-avalancheconflictingproofcooldown", "0"); | |||||
| BOOST_CHECK(!m_processor->isAccepted(nullptr)); | BOOST_CHECK(!m_processor->isAccepted(nullptr)); | ||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(nullptr), -1); | BOOST_CHECK_EQUAL(m_processor->getConfidence(nullptr), -1); | ||||
| auto proofA = GetProof(); | const CKey key = CKey::MakeCompressedKey(); | ||||
| auto proofB = GetProof(); | |||||
| const COutPoint conflictingOutpoint{TxId(GetRandHash()), 0}; | |||||
| { | |||||
| CScript script = GetScriptForDestination(PKHash(key.GetPubKey())); | |||||
| LOCK(cs_main); | |||||
| CCoinsViewCache &coins = ::ChainstateActive().CoinsTip(); | |||||
| coins.AddCoin(conflictingOutpoint, | |||||
| Coin(CTxOut(10 * COIN, script), 10, false), false); | |||||
| } | |||||
| const COutPoint missingOutpoint{TxId(GetRandHash()), 0}; | |||||
| auto buildProof = [&](const COutPoint &outpoint, uint64_t sequence) { | |||||
| ProofBuilder pb(sequence, 0, key); | |||||
| BOOST_CHECK(pb.addUTXO(outpoint, 10 * COIN, 10, false, key)); | |||||
| return pb.build(); | |||||
| }; | |||||
| BOOST_CHECK(!m_processor->isAccepted(proofA)); | auto conflictingProof = buildProof(conflictingOutpoint, 1); | ||||
| BOOST_CHECK(!m_processor->isAccepted(proofB)); | auto validProof = buildProof(conflictingOutpoint, 2); | ||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(proofA), -1); | auto orphanProof = buildProof(missingOutpoint, 3); | ||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(proofB), -1); | |||||
| BOOST_CHECK(!m_processor->isAccepted(conflictingProof)); | |||||
| m_processor->addProofToReconcile(proofA); | BOOST_CHECK(!m_processor->isAccepted(validProof)); | ||||
| BOOST_CHECK(!m_processor->isAccepted(proofA)); | BOOST_CHECK(!m_processor->isAccepted(orphanProof)); | ||||
| BOOST_CHECK(!m_processor->isAccepted(proofB)); | BOOST_CHECK_EQUAL(m_processor->getConfidence(conflictingProof), -1); | ||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(proofA), 0); | BOOST_CHECK_EQUAL(m_processor->getConfidence(validProof), -1); | ||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(proofB), -1); | BOOST_CHECK_EQUAL(m_processor->getConfidence(orphanProof), -1); | ||||
| // Reconciling proofs that don't exist will fail | |||||
| BOOST_CHECK(!m_processor->addProofToReconcile(conflictingProof)); | |||||
| BOOST_CHECK(!m_processor->addProofToReconcile(validProof)); | |||||
| BOOST_CHECK(!m_processor->addProofToReconcile(orphanProof)); | |||||
| m_processor->withPeerManager([&](avalanche::PeerManager &pm) { | m_processor->withPeerManager([&](avalanche::PeerManager &pm) { | ||||
| BOOST_CHECK(pm.registerProof(proofB)); | BOOST_CHECK(pm.registerProof(conflictingProof)); | ||||
| BOOST_CHECK(pm.registerProof(validProof)); | |||||
| BOOST_CHECK(!pm.registerProof(orphanProof)); | |||||
| BOOST_CHECK(pm.isBoundToPeer(validProof->getId())); | |||||
| BOOST_CHECK(pm.isInConflictingPool(conflictingProof->getId())); | |||||
| BOOST_CHECK(pm.isOrphan(orphanProof->getId())); | |||||
| }); | }); | ||||
| m_processor->addProofToReconcile(proofB); | |||||
| BOOST_CHECK(!m_processor->isAccepted(proofA)); | BOOST_CHECK(m_processor->addProofToReconcile(conflictingProof)); | ||||
| BOOST_CHECK(m_processor->isAccepted(proofB)); | BOOST_CHECK(!m_processor->isAccepted(conflictingProof)); | ||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(proofA), 0); | BOOST_CHECK(!m_processor->isAccepted(validProof)); | ||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(proofB), 0); | BOOST_CHECK(!m_processor->isAccepted(orphanProof)); | ||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(conflictingProof), 0); | |||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(validProof), -1); | |||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(orphanProof), -1); | |||||
| BOOST_CHECK(m_processor->addProofToReconcile(validProof)); | |||||
| BOOST_CHECK(!m_processor->isAccepted(conflictingProof)); | |||||
| BOOST_CHECK(m_processor->isAccepted(validProof)); | |||||
| BOOST_CHECK(!m_processor->isAccepted(orphanProof)); | |||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(conflictingProof), 0); | |||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(validProof), 0); | |||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(orphanProof), -1); | |||||
| BOOST_CHECK(!m_processor->addProofToReconcile(orphanProof)); | |||||
| BOOST_CHECK(!m_processor->isAccepted(conflictingProof)); | |||||
| BOOST_CHECK(m_processor->isAccepted(validProof)); | |||||
| BOOST_CHECK(!m_processor->isAccepted(orphanProof)); | |||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(conflictingProof), 0); | |||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(validProof), 0); | |||||
| BOOST_CHECK_EQUAL(m_processor->getConfidence(orphanProof), -1); | |||||
| gArgs.ClearForcedArg("-avalancheconflictingproofcooldown"); | |||||
| gArgs.ClearForcedArg("-enableavalancheproofreplacement"); | |||||
| } | } | ||||
| BOOST_AUTO_TEST_CASE(quorum_detection) { | BOOST_AUTO_TEST_CASE(quorum_detection) { | ||||
| // Set min quorum parameters for our test | // Set min quorum parameters for our test | ||||
| int minStake = 4'000'000; | int minStake = 4'000'000; | ||||
| gArgs.ForceSetArg("-avaminquorumstake", ToString(minStake)); | gArgs.ForceSetArg("-avaminquorumstake", ToString(minStake)); | ||||
| gArgs.ForceSetArg("-avaminquorumconnectedstakeratio", "0.5"); | gArgs.ForceSetArg("-avaminquorumconnectedstakeratio", "0.5"); | ||||
| ▲ Show 20 Lines • Show All 318 Lines • Show Last 20 Lines | |||||