diff --git a/src/avalanche/proofpool.h b/src/avalanche/proofpool.h --- a/src/avalanche/proofpool.h +++ b/src/avalanche/proofpool.h @@ -18,6 +18,7 @@ #include #include +#include namespace avalanche { @@ -38,6 +39,7 @@ struct by_utxo; struct by_proofid; struct by_proof_score; +struct by_registration_time; struct ProofPoolEntryProofIdKeyExtractor { using result_type = ProofId; @@ -75,6 +77,31 @@ bool cacheClean = true; size_t cacheProofCount = 0; + struct ProofRegistrationTime { + ProofId proofid; + // std::chrono::duration is not hashable by default, so use an integer + int64_t registrationTime; + + ProofRegistrationTime(const ProofId &proofidIn, + int64_t registrationTimeIn) + : proofid(proofidIn), registrationTime(registrationTimeIn) {} + }; + + using ProofRegistrationTimeSet = boost::multi_index_container< + ProofRegistrationTime, + bmi::indexed_by< + // index by proofid + bmi::hashed_unique, + bmi::member, + SaltedProofIdHasher>, + // ordered by registrationTime + bmi::ordered_non_unique< + bmi::tag, + bmi::member>>>; + ProofRegistrationTimeSet registrationTimes; + public: enum AddProofStatus { REJECTED = 0, //!< Rejected due to conflicts @@ -115,6 +142,9 @@ ProofRef getProof(const COutPoint &outpoint) const; ProofRef getLowestScoreProof() const; + std::optional + getRegistrationTime(const ProofId &proofid) const; + size_t size() const { return pool.size(); } size_t countProofs(); }; diff --git a/src/avalanche/proofpool.cpp b/src/avalanche/proofpool.cpp --- a/src/avalanche/proofpool.cpp +++ b/src/avalanche/proofpool.cpp @@ -46,6 +46,8 @@ return AddProofStatus::REJECTED; } + registrationTimes.emplace(proofid, GetTime()); + cacheClean = false; return AddProofStatus::SUCCEED; } @@ -78,17 +80,42 @@ // loop so we pass it by value. bool ProofPool::removeProof(ProofId proofid) { cacheClean = false; + + if (!registrationTimes.erase(proofid)) { + return false; + } + auto &poolView = pool.get(); return poolView.erase(proofid); } void ProofPool::rescan(PeerManager &peerManager) { auto previousPool = std::move(pool); + auto previousRegistrationTimes = std::move(registrationTimes); pool.clear(); + registrationTimes.clear(); cacheClean = false; for (auto &entry : previousPool) { + const ProofId &proofid = entry.proof->getId(); peerManager.registerProof(entry.proof); + + // If the proof was added back to this pool, retain the registration + // time. + auto newIt = registrationTimes.find(proofid); + if (newIt == registrationTimes.end()) { + // Was not added back into this pool + continue; + } + + auto oldIt = previousRegistrationTimes.find(proofid); + assert(oldIt != registrationTimes.end()); + + registrationTimes.modify( + newIt, [&](ProofRegistrationTime &proofRegistrationTime) { + proofRegistrationTime.registrationTime = + oldIt->registrationTime; + }); } } @@ -109,6 +136,16 @@ : poolView.rbegin()->proof; } +std::optional +ProofPool::getRegistrationTime(const ProofId &proofid) const { + auto it = registrationTimes.find(proofid); + if (it == registrationTimes.end()) { + return {}; + } + + return std::make_optional(std::chrono::seconds{it->registrationTime}); +} + size_t ProofPool::countProofs() { if (cacheClean) { return cacheProofCount; diff --git a/src/avalanche/test/proofpool_tests.cpp b/src/avalanche/test/proofpool_tests.cpp --- a/src/avalanche/test/proofpool_tests.cpp +++ b/src/avalanche/test/proofpool_tests.cpp @@ -18,6 +18,16 @@ using namespace avalanche; +namespace avalanche { +namespace { + struct TestPeerManager { + static ProofPool &getOrphanPool(PeerManager &pm) { + return pm.orphanProofPool; + } + }; +} // namespace +} // namespace avalanche + BOOST_FIXTURE_TEST_SUITE(proofpool_tests, TestingSetup) BOOST_AUTO_TEST_CASE(add_remove_proof_no_conflict) { @@ -356,4 +366,104 @@ BOOST_CHECK_EQUAL(testPool.countProofs(), 0); } +BOOST_AUTO_TEST_CASE(registration_time) { + ProofPool testPool; + + // Proofs that are not in the pool have no registration time + for (size_t i = 0; i < 10; i++) { + BOOST_CHECK(!testPool.getRegistrationTime(ProofId(GetRandHash()))); + } + + auto now = GetTime(); + auto mocktime = now; + + auto elapseTime = [&](std::chrono::seconds seconds) { + mocktime += seconds; + SetMockTime(mocktime.count()); + }; + elapseTime(0s); + + // Add some proofs and check the registration time + std::vector proofs; + for (size_t i = 0; i < 10; i++) { + auto proof = buildRandomProof(MIN_VALID_PROOF_SCORE); + BOOST_CHECK(testPool.addProofIfNoConflict(proof)); + + auto registrationTime = testPool.getRegistrationTime(proof->getId()); + BOOST_CHECK(registrationTime.value_or(0s) == mocktime); + + proofs.push_back(std::move(proof)); + + elapseTime(1s); + } + + // Removing a proof clears its registration time + for (size_t i = 0; i < 5; i++) { + auto &proof = proofs[i]; + BOOST_CHECK(testPool.removeProof(proof->getId())); + + BOOST_CHECK(!testPool.getRegistrationTime(proof->getId())); + } + // Other proofs registration time are unaffected + for (size_t i = 5; i < 10; i++) { + auto registrationTime = + testPool.getRegistrationTime(proofs[i]->getId()); + BOOST_CHECK(registrationTime.value_or(0s) == now + i * 1s); + } + + { + // Proofs removed during a rescan have their registration time cleared. + avalanche::PeerManager pm; + testPool.rescan(pm); + BOOST_CHECK_EQUAL(testPool.countProofs(), 0); + + for (size_t i = 5; i < 10; i++) { + BOOST_CHECK(!testPool.getRegistrationTime(proofs[i]->getId())); + } + } + + { + // Proofs added back during a rescan retain their registration time. + avalanche::PeerManager pm; + auto &pool = TestPeerManager::getOrphanPool(pm); + + now = GetTime(); + mocktime = now; + elapseTime(0s); + + const CKey key = CKey::MakeCompressedKey(); + + // Add some orphan proofs + std::vector orphans; + for (size_t i = 0; i < 10; i++) { + ProofBuilder pb(0, 0, key); + BOOST_CHECK(pb.addUTXO({TxId(GetRandHash()), 0}, 10 * COIN, 10, + false, key)); + auto proof = pb.build(); + BOOST_CHECK(proof != nullptr); + + ProofRegistrationState state; + BOOST_CHECK(!pm.registerProof(proof, state)); + BOOST_CHECK(state.GetResult() == ProofRegistrationResult::ORPHAN); + BOOST_CHECK(pm.isOrphan(proof->getId())); + + auto registrationTime = pool.getRegistrationTime(proof->getId()); + BOOST_CHECK(registrationTime.value_or(0s) == now + i * 1s); + + orphans.push_back(std::move(proof)); + + elapseTime(1s); + } + + pool.rescan(pm); + BOOST_CHECK_EQUAL(pool.countProofs(), 10); + + for (size_t i = 0; i < 10; i++) { + auto registrationTime = + pool.getRegistrationTime(orphans[i]->getId()); + BOOST_CHECK(registrationTime.value_or(0s) == now + i * 1s); + } + } +} + BOOST_AUTO_TEST_SUITE_END()