diff --git a/src/avalanche/proofpool.cpp b/src/avalanche/proofpool.cpp index 793fa8cdb..af7415837 100644 --- a/src/avalanche/proofpool.cpp +++ b/src/avalanche/proofpool.cpp @@ -1,102 +1,127 @@ // Copyright (c) 2021 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include namespace avalanche { ProofPool::AddProofStatus ProofPool::addProofIfNoConflict(const ProofRef &proof, ConflictingProofSet &conflictingProofs) { const ProofId &proofid = proof->getId(); // Make sure the set is empty before we add items conflictingProofs.clear(); auto &poolView = pool.get(); if (poolView.find(proofid) != poolView.end()) { return AddProofStatus::DUPLICATED; } // Attach UTXOs to this proof. for (size_t i = 0; i < proof->getStakes().size(); i++) { auto p = pool.emplace(i, proof); if (!p.second) { // We have a collision with an existing proof. conflictingProofs.insert(p.first->proof); } } // If there is a conflict, just cleanup the mess. if (conflictingProofs.size() > 0) { for (const auto &s : proof->getStakes()) { auto it = pool.find(s.getStake().getUTXO()); assert(it != pool.end()); // We need to delete that one. if (it->proof->getId() == proofid) { pool.erase(it); } } return AddProofStatus::REJECTED; } + cacheClean = false; return AddProofStatus::SUCCEED; } ProofPool::AddProofStatus ProofPool::addProofIfPreferred(const ProofRef &proof, ConflictingProofSet &conflictingProofs) { auto status = addProofIfNoConflict(proof, conflictingProofs); // In case the proof was rejected due to conflict and it is the best // candidate, override the conflicting ones and add it again if (status != AddProofStatus::REJECTED || ConflictingProofComparator()(*conflictingProofs.begin(), proof)) { return status; } for (auto &conflictingProof : conflictingProofs) { removeProof(conflictingProof->getId()); } status = addProofIfNoConflict(proof); assert(status == AddProofStatus::SUCCEED); + cacheClean = false; return AddProofStatus::SUCCEED; } // Having the ProofId passed by reference is risky because it is usually a // reference to a proof member. This proof will be deleted during the erasure // loop so we pass it by value. bool ProofPool::removeProof(ProofId proofid) { + cacheClean = false; auto &poolView = pool.get(); return poolView.erase(proofid); } void ProofPool::rescan(PeerManager &peerManager) { auto previousPool = std::move(pool); pool.clear(); + cacheClean = false; for (auto &entry : previousPool) { peerManager.registerProof(entry.proof); } } ProofRef ProofPool::getProof(const ProofId &proofid) const { auto &poolView = pool.get(); auto it = poolView.find(proofid); return it == poolView.end() ? ProofRef() : it->proof; } ProofRef ProofPool::getProof(const COutPoint &outpoint) const { auto it = pool.find(outpoint); return it == pool.end() ? ProofRef() : it->proof; } +size_t ProofPool::countProofs() { + if (cacheClean) { + return cacheProofCount; + } + + size_t count = 0; + ProofId lastProofId; + auto &poolView = pool.get(); + for (auto it = poolView.begin(); it != poolView.end(); it++) { + const ProofId &proofId = it->proof->getId(); + if (lastProofId != proofId) { + lastProofId = proofId; + count++; + } + } + + cacheProofCount = count; + cacheClean = true; + return cacheProofCount; +} + } // namespace avalanche diff --git a/src/avalanche/proofpool.h b/src/avalanche/proofpool.h index cd8e1b843..1869fd557 100644 --- a/src/avalanche/proofpool.h +++ b/src/avalanche/proofpool.h @@ -1,111 +1,115 @@ // Copyright (c) 2021 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_AVALANCHE_PROOFPOOL_H #define BITCOIN_AVALANCHE_PROOFPOOL_H #include #include #include #include #include #include #include #include #include namespace avalanche { class PeerManager; struct ProofPoolEntry { size_t utxoIndex; ProofRef proof; const COutPoint &getUTXO() const { return proof->getStakes().at(utxoIndex).getStake().getUTXO(); } ProofPoolEntry(size_t _utxoIndex, ProofRef _proof) : utxoIndex(_utxoIndex), proof(std::move(_proof)) {} }; struct by_utxo; struct by_proofid; struct ProofPoolEntryProofIdKeyExtractor { using result_type = ProofId; result_type operator()(const ProofPoolEntry &entry) const { return entry.proof->getId(); } }; namespace bmi = boost::multi_index; /** * Map a proof to each utxo. A proof can be mapped with several utxos. */ class ProofPool { boost::multi_index_container< ProofPoolEntry, bmi::indexed_by< // index by utxo bmi::hashed_unique< bmi::tag, bmi::const_mem_fun, SaltedOutpointHasher>, // index by proofid bmi::hashed_non_unique, ProofPoolEntryProofIdKeyExtractor, SaltedProofIdHasher>>> pool; + bool cacheClean = true; + size_t cacheProofCount = 0; + public: enum AddProofStatus { REJECTED = 0, //!< Rejected due to conflicts SUCCEED = 1, //!< Added successfully DUPLICATED = 2, //!< Already in pool }; using ConflictingProofSet = std::set; /** * Attempt to add a proof to the pool, and fail if there is a conflict on * any UTXO. */ AddProofStatus addProofIfNoConflict(const ProofRef &proof, ConflictingProofSet &conflictingProofs); AddProofStatus addProofIfNoConflict(const ProofRef &proof) { ConflictingProofSet dummy; return addProofIfNoConflict(proof, dummy); } /** * Attempt to add a proof to the pool. In case there is a conflict with one * or more UTXO, the proof is only added if it is the best candidate over * all the conflicting proofs according to ConflictingProofComparator. */ AddProofStatus addProofIfPreferred(const ProofRef &proof, ConflictingProofSet &conflictingProofs); AddProofStatus addProofIfPreferred(const ProofRef &proof) { ConflictingProofSet dummy; return addProofIfPreferred(proof, dummy); } bool removeProof(ProofId proofid); void rescan(PeerManager &peerManager); ProofRef getProof(const ProofId &proofid) const; ProofRef getProof(const COutPoint &outpoint) const; size_t size() const { return pool.size(); } + size_t countProofs(); }; } // namespace avalanche #endif // BITCOIN_AVALANCHE_PROOFPOOL_H diff --git a/src/avalanche/test/proofpool_tests.cpp b/src/avalanche/test/proofpool_tests.cpp index 9ba56df7e..66bf072a7 100644 --- a/src/avalanche/test/proofpool_tests.cpp +++ b/src/avalanche/test/proofpool_tests.cpp @@ -1,281 +1,295 @@ // Copyright (c) 2021 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include using namespace avalanche; BOOST_FIXTURE_TEST_SUITE(proofpool_tests, TestingSetup) BOOST_AUTO_TEST_CASE(add_remove_proof_no_conflict) { ProofPool testPool; std::vector proofs; for (size_t i = 0; i < 10; i++) { // Add a bunch of random proofs auto proof = buildRandomProof(MIN_VALID_PROOF_SCORE); BOOST_CHECK_EQUAL(testPool.addProofIfNoConflict(proof), ProofPool::AddProofStatus::SUCCEED); + BOOST_CHECK_EQUAL(testPool.countProofs(), i + 1); // Trying to add them again will return a duplicated status for (size_t j = 0; j < 10; j++) { BOOST_CHECK_EQUAL(testPool.addProofIfNoConflict(proof), ProofPool::AddProofStatus::DUPLICATED); + BOOST_CHECK_EQUAL(testPool.countProofs(), i + 1); } proofs.push_back(std::move(proof)); } const CKey key = CKey::MakeCompressedKey(); const COutPoint conflictingOutpoint{TxId(GetRandHash()), 0}; auto buildProofWithSequence = [&](uint64_t sequence) { ProofBuilder pb(sequence, 0, key); BOOST_CHECK( pb.addUTXO(conflictingOutpoint, 10 * COIN, 123456, false, key)); return pb.build(); }; auto proof_seq10 = buildProofWithSequence(10); BOOST_CHECK_EQUAL(testPool.addProofIfNoConflict(proof_seq10), ProofPool::AddProofStatus::SUCCEED); + BOOST_CHECK_EQUAL(testPool.countProofs(), 11); proofs.push_back(std::move(proof_seq10)); auto proof_seq20 = buildProofWithSequence(20); BOOST_CHECK_EQUAL(testPool.addProofIfNoConflict(proof_seq20), ProofPool::AddProofStatus::REJECTED); + BOOST_CHECK_EQUAL(testPool.countProofs(), 11); // Removing proofs which are not in the pool will fail for (size_t i = 0; i < 10; i++) { BOOST_CHECK(!testPool.removeProof(ProofId(GetRandHash()))); } + BOOST_CHECK_EQUAL(testPool.countProofs(), 11); for (auto proof : proofs) { BOOST_CHECK(testPool.removeProof(proof->getId())); } BOOST_CHECK_EQUAL(testPool.size(), 0); + BOOST_CHECK_EQUAL(testPool.countProofs(), 0); } BOOST_AUTO_TEST_CASE(rescan) { ProofPool testPool; avalanche::PeerManager pm; testPool.rescan(pm); BOOST_CHECK_EQUAL(testPool.size(), 0); + BOOST_CHECK_EQUAL(testPool.countProofs(), 0); // No peer should be created bool hasPeer = false; pm.forEachPeer([&](const Peer &p) { hasPeer = true; }); BOOST_CHECK(!hasPeer); std::set poolProofs; for (size_t i = 0; i < 10; i++) { auto proof = buildRandomProof(MIN_VALID_PROOF_SCORE); BOOST_CHECK_EQUAL(testPool.addProofIfNoConflict(proof), ProofPool::AddProofStatus::SUCCEED); poolProofs.insert(std::move(proof)); + BOOST_CHECK_EQUAL(testPool.countProofs(), i + 1); } testPool.rescan(pm); // All the proofs should be registered as peer std::set pmProofs; pm.forEachPeer([&](const Peer &p) { pmProofs.insert(p.proof); }); BOOST_CHECK_EQUAL_COLLECTIONS(poolProofs.begin(), poolProofs.end(), pmProofs.begin(), pmProofs.end()); BOOST_CHECK_EQUAL(testPool.size(), 0); + BOOST_CHECK_EQUAL(testPool.countProofs(), 0); } BOOST_AUTO_TEST_CASE(proof_override) { ProofPool testPool; const CKey key = CKey::MakeCompressedKey(); auto buildProofWithSequenceAndOutpoints = [&](uint64_t sequence, const std::vector &outpoints) { ProofBuilder pb(sequence, 0, key); for (const COutPoint &outpoint : outpoints) { BOOST_CHECK( pb.addUTXO(outpoint, 10 * COIN, 123456, false, key)); } return pb.build(); }; const COutPoint outpoint1{TxId(GetRandHash()), 0}; const COutPoint outpoint2{TxId(GetRandHash()), 0}; const COutPoint outpoint3{TxId(GetRandHash()), 0}; // Build and register 3 proofs with a single utxo auto proof_seq10 = buildProofWithSequenceAndOutpoints(10, {outpoint1}); auto proof_seq20 = buildProofWithSequenceAndOutpoints(20, {outpoint2}); auto proof_seq30 = buildProofWithSequenceAndOutpoints(30, {outpoint3}); BOOST_CHECK_EQUAL(testPool.addProofIfPreferred(proof_seq10), ProofPool::AddProofStatus::SUCCEED); BOOST_CHECK(testPool.getProof(proof_seq10->getId())); + BOOST_CHECK_EQUAL(testPool.countProofs(), 1); BOOST_CHECK_EQUAL(testPool.addProofIfPreferred(proof_seq20), ProofPool::AddProofStatus::SUCCEED); BOOST_CHECK(testPool.getProof(proof_seq20->getId())); + BOOST_CHECK_EQUAL(testPool.countProofs(), 2); BOOST_CHECK_EQUAL(testPool.addProofIfPreferred(proof_seq30), ProofPool::AddProofStatus::SUCCEED); BOOST_CHECK(testPool.getProof(proof_seq30->getId())); + BOOST_CHECK_EQUAL(testPool.countProofs(), 3); // Build a proof that conflicts with the above 3, but has a higher sequence auto proof_seq123 = buildProofWithSequenceAndOutpoints( 123, {outpoint1, outpoint2, outpoint3}); ProofPool::ConflictingProofSet expectedConflictingProofs = { proof_seq10, proof_seq20, proof_seq30}; // The no conflict call should reject our candidate and not alter the 3 // conflicting proofs ProofPool::ConflictingProofSet conflictingProofs; BOOST_CHECK_EQUAL( testPool.addProofIfNoConflict(proof_seq123, conflictingProofs), ProofPool::AddProofStatus::REJECTED); BOOST_CHECK_EQUAL_COLLECTIONS( conflictingProofs.begin(), conflictingProofs.end(), expectedConflictingProofs.begin(), expectedConflictingProofs.end()); + BOOST_CHECK_EQUAL(testPool.countProofs(), 3); BOOST_CHECK(!testPool.getProof(proof_seq123->getId())); BOOST_CHECK(testPool.getProof(proof_seq10->getId())); BOOST_CHECK(testPool.getProof(proof_seq20->getId())); BOOST_CHECK(testPool.getProof(proof_seq30->getId())); // The conflict handling call will override the 3 conflicting proofs conflictingProofs.clear(); BOOST_CHECK_EQUAL( testPool.addProofIfPreferred(proof_seq123, conflictingProofs), ProofPool::AddProofStatus::SUCCEED); BOOST_CHECK_EQUAL_COLLECTIONS( conflictingProofs.begin(), conflictingProofs.end(), expectedConflictingProofs.begin(), expectedConflictingProofs.end()); + BOOST_CHECK_EQUAL(testPool.countProofs(), 1); BOOST_CHECK(testPool.getProof(proof_seq123->getId())); BOOST_CHECK(!testPool.getProof(proof_seq10->getId())); BOOST_CHECK(!testPool.getProof(proof_seq20->getId())); BOOST_CHECK(!testPool.getProof(proof_seq30->getId())); } BOOST_AUTO_TEST_CASE(conflicting_proofs_set) { ProofPool testPool; const CKey key = CKey::MakeCompressedKey(); const COutPoint conflictingOutpoint{TxId(GetRandHash()), 0}; auto buildProofWithSequence = [&](uint64_t sequence) { ProofBuilder pb(sequence, 0, key); BOOST_CHECK( pb.addUTXO(conflictingOutpoint, 10 * COIN, 123456, false, key)); return pb.build(); }; auto proofSeq10 = buildProofWithSequence(10); auto proofSeq20 = buildProofWithSequence(20); auto proofSeq30 = buildProofWithSequence(30); BOOST_CHECK_EQUAL(testPool.addProofIfNoConflict(proofSeq20), ProofPool::AddProofStatus::SUCCEED); auto getRandomConflictingProofSet = []() { return ProofPool::ConflictingProofSet{ buildRandomProof(MIN_VALID_PROOF_SCORE), buildRandomProof(MIN_VALID_PROOF_SCORE), buildRandomProof(MIN_VALID_PROOF_SCORE), }; }; auto checkConflictingProofs = [&](const ProofPool::ConflictingProofSet &conflictingProofs, const ProofPool::ConflictingProofSet &expectedConflictingProofs) { BOOST_CHECK_EQUAL_COLLECTIONS(conflictingProofs.begin(), conflictingProofs.end(), expectedConflictingProofs.begin(), expectedConflictingProofs.end()); }; { // Without override, duplicated proof auto conflictingProofs = getRandomConflictingProofSet(); BOOST_CHECK_EQUAL( testPool.addProofIfNoConflict(proofSeq20, conflictingProofs), ProofPool::AddProofStatus::DUPLICATED); checkConflictingProofs(conflictingProofs, {}); } { // With override, duplicated proof auto conflictingProofs = getRandomConflictingProofSet(); BOOST_CHECK_EQUAL( testPool.addProofIfPreferred(proofSeq20, conflictingProofs), ProofPool::AddProofStatus::DUPLICATED); checkConflictingProofs(conflictingProofs, {}); } { // Without override, worst proof auto conflictingProofs = getRandomConflictingProofSet(); BOOST_CHECK_EQUAL( testPool.addProofIfNoConflict(proofSeq10, conflictingProofs), ProofPool::AddProofStatus::REJECTED); checkConflictingProofs(conflictingProofs, {proofSeq20}); } { // Without override, better proof auto conflictingProofs = getRandomConflictingProofSet(); BOOST_CHECK_EQUAL( testPool.addProofIfNoConflict(proofSeq30, conflictingProofs), ProofPool::AddProofStatus::REJECTED); checkConflictingProofs(conflictingProofs, {proofSeq20}); } { // With override, worst proof auto conflictingProofs = getRandomConflictingProofSet(); BOOST_CHECK_EQUAL( testPool.addProofIfPreferred(proofSeq10, conflictingProofs), ProofPool::AddProofStatus::REJECTED); checkConflictingProofs(conflictingProofs, {proofSeq20}); } { // With override, better proof auto conflictingProofs = getRandomConflictingProofSet(); BOOST_CHECK_EQUAL( testPool.addProofIfPreferred(proofSeq30, conflictingProofs), ProofPool::AddProofStatus::SUCCEED); checkConflictingProofs(conflictingProofs, {proofSeq20}); } } BOOST_AUTO_TEST_CASE(get_proof) { ProofPool testPool; for (size_t i = 0; i < 10; i++) { BOOST_CHECK(!testPool.getProof(ProofId(GetRandHash()))); } for (size_t i = 0; i < 10; i++) { auto proof = buildRandomProof(MIN_VALID_PROOF_SCORE); BOOST_CHECK_EQUAL(testPool.addProofIfNoConflict(proof), ProofPool::AddProofStatus::SUCCEED); auto retrievedProof = testPool.getProof(proof->getId()); BOOST_CHECK_NE(retrievedProof, nullptr); BOOST_CHECK_EQUAL(retrievedProof->getId(), proof->getId()); } } BOOST_AUTO_TEST_SUITE_END()