diff --git a/src/avalanche/proofpool.h b/src/avalanche/proofpool.h --- a/src/avalanche/proofpool.h +++ b/src/avalanche/proofpool.h @@ -87,11 +87,13 @@ /** * 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. + * all the conflicting proofs according to ConflictingProofComparator and if + * the cooldown time has elapsed since the last registration for each UTXO. */ AddProofStatus addProofIfPreferred(const ProofRef &proof); - AddProofStatus addProofIfPreferred(const ProofRef &proof, - ConflictingProofSet &conflictingProofs); + AddProofStatus addProofIfPreferred( + const ProofRef &proof, ConflictingProofSet &conflictingProofs, + std::chrono::seconds cooldown = std::chrono::seconds{0}); bool removeProof(ProofRef proof); diff --git a/src/avalanche/proofpool.cpp b/src/avalanche/proofpool.cpp --- a/src/avalanche/proofpool.cpp +++ b/src/avalanche/proofpool.cpp @@ -8,6 +8,8 @@ #include #include +#include + namespace avalanche { ProofPool::AddProofStatus @@ -63,13 +65,27 @@ ProofPool::AddProofStatus ProofPool::addProofIfPreferred(const ProofRef &proof, - ConflictingProofSet &conflictingProofs) { + ConflictingProofSet &conflictingProofs, + std::chrono::seconds cooldown) { auto added = addProofIfNoConflict(proof, conflictingProofs); + // Find the registration time of the most recently registered conflicting + // proof. + std::chrono::seconds lastConflictingRegistrationTime = + std::chrono::seconds::min(); + for (auto &conflictingProof : conflictingProofs) { + lastConflictingRegistrationTime = + std::max(lastConflictingRegistrationTime, + getRegistrationTime(conflictingProof->getId())); + } + ConflictingProofComparator compare; - // In case the proof was rejected due to conflict and it is the best - // candidate, override the conflicting ones and add it again - if (!added && compare(proof, *conflictingProofs.begin())) { + // In case the proof was rejected due to conflict, if it is the best + // candidate and the cooldown has elapsed for each UTXO, then override the + // conflicting ones and add it again. + if (!added && compare(proof, *conflictingProofs.begin()) && + GetTime() >= + (lastConflictingRegistrationTime + cooldown)) { for (auto &conflictingProof : conflictingProofs) { removeProof(conflictingProof); } 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 @@ -169,6 +169,109 @@ BOOST_CHECK(!testPool.getProof(proof_seq30->getId())); } +BOOST_AUTO_TEST_CASE(proof_override_cooldown) { + 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(); + }; + + auto buildAndRegister = [&](uint64_t sequence, + const std::vector &outpoints, + std::chrono::seconds registrationTime) { + auto proof = buildProofWithSequenceAndOutpoints(sequence, outpoints); + + SetMockTime(registrationTime.count()); + BOOST_CHECK_EQUAL(testPool.addProofIfPreferred(proof), + ProofPool::AddProofStatus::SUCCEED); + BOOST_CHECK(testPool.getProof(proof->getId())); + BOOST_CHECK(testPool.getRegistrationTime(proof->getId()) == + registrationTime); + + return proof; + }; + + 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 each, and a different + // registration time. + auto proof1 = buildAndRegister(10, {outpoint1}, std::chrono::seconds{100}); + auto proof2 = buildAndRegister(20, {outpoint2}, std::chrono::seconds{200}); + auto proof3 = buildAndRegister(30, {outpoint3}, std::chrono::seconds{300}); + + // Build a proof that conflicts with the above 3, but has a higher sequence + auto proof4 = buildProofWithSequenceAndOutpoints( + 400, {outpoint1, outpoint2, outpoint3}); + ProofPool::ConflictingProofSet expectedConflictingProofs{proof1, proof2, + proof3}; + + SetMockTime(400); + + auto checkRejected = [&](std::chrono::seconds cooldown) { + ProofPool::ConflictingProofSet conflictingProofs; + + BOOST_CHECK_EQUAL( + testPool.addProofIfPreferred(proof4, conflictingProofs, cooldown), + ProofPool::AddProofStatus::REJECTED); + + BOOST_CHECK_EQUAL_COLLECTIONS( + conflictingProofs.begin(), conflictingProofs.end(), + expectedConflictingProofs.begin(), expectedConflictingProofs.end()); + + BOOST_CHECK(!testPool.getProof(proof4->getId())); + BOOST_CHECK(testPool.getProof(proof1->getId())); + BOOST_CHECK(testPool.getProof(proof2->getId())); + BOOST_CHECK(testPool.getProof(proof3->getId())); + }; + + // Cooldown is not enough for any conflicting proof + checkRejected(std::chrono::seconds{400}); + // Cooldown is not enough for proof2 and proof3 + checkRejected(std::chrono::seconds{300}); + // Cooldown is not enough for proof3 + checkRejected(std::chrono::seconds{200}); + // Cooldown is not enough for proof3 + checkRejected(std::chrono::seconds{101}); + + // Cooldown has elaped for each conflicting proof + ProofPool::ConflictingProofSet conflictingProofs; + BOOST_CHECK_EQUAL(testPool.addProofIfPreferred(proof4, conflictingProofs, + std::chrono::seconds{100}), + ProofPool::AddProofStatus::SUCCEED); + BOOST_CHECK(testPool.getRegistrationTime(proof4->getId()) == + std::chrono::seconds{400}); + + BOOST_CHECK_EQUAL_COLLECTIONS( + conflictingProofs.begin(), conflictingProofs.end(), + expectedConflictingProofs.begin(), expectedConflictingProofs.end()); + + BOOST_CHECK(testPool.getProof(proof4->getId())); + BOOST_CHECK(!testPool.getProof(proof1->getId())); + BOOST_CHECK(!testPool.getProof(proof2->getId())); + BOOST_CHECK(!testPool.getProof(proof3->getId())); + + // Cooldown is irrelevant if there is no conflict + conflictingProofs.clear(); + auto proofNoConflict = buildProofWithSequenceAndOutpoints( + 0, {COutPoint{TxId(GetRandHash()), 0}}); + BOOST_CHECK_EQUAL(testPool.addProofIfPreferred(proofNoConflict, + conflictingProofs, + std::chrono::seconds::max()), + ProofPool::AddProofStatus::SUCCEED); + BOOST_CHECK(conflictingProofs.empty()); + BOOST_CHECK(testPool.getProof(proofNoConflict->getId())); +} + BOOST_AUTO_TEST_CASE(get_proof) { ProofPool testPool;