diff --git a/src/avalanche/avalanche.h b/src/avalanche/avalanche.h --- a/src/avalanche/avalanche.h +++ b/src/avalanche/avalanche.h @@ -30,6 +30,11 @@ */ static constexpr size_t AVALANCHE_DEFAULT_CONFLICTING_PROOF_COOLDOWN = 60; +/** + * Is proof replacement enabled by default. + */ +static constexpr bool AVALANCHE_DEFAULT_PROOF_REPLACEMENT_ENABLED = false; + /** * Avalanche default cooldown in milliseconds. */ diff --git a/src/avalanche/peermanager.h b/src/avalanche/peermanager.h --- a/src/avalanche/peermanager.h +++ b/src/avalanche/peermanager.h @@ -305,6 +305,7 @@ bool isInConflictingPool(const ProofId &proofid) const; private: + void moveToConflictingPool(const ProofRef &proof); bool addOrUpdateNode(const PeerSet::iterator &it, NodeId nodeid); bool addNodeToPeer(const PeerSet::iterator &it); bool removeNodeFromPeer(const PeerSet::iterator &it, uint32_t count = 1); diff --git a/src/avalanche/peermanager.cpp b/src/avalanche/peermanager.cpp --- a/src/avalanche/peermanager.cpp +++ b/src/avalanche/peermanager.cpp @@ -165,6 +165,16 @@ return it->nextPossibleConflictTime == nextTime; } +void PeerManager::moveToConflictingPool(const ProofRef &proof) { + auto &peersView = peers.get(); + auto it = peersView.find(proof->getId()); + if (it != peersView.end()) { + removePeer(it->peerid); + } + + conflictingProofPool.addProofIfPreferred(proof); +} + bool PeerManager::registerProof(const ProofRef &proof, ProofRegistrationState ®istrationState, RegistrationMode mode) { @@ -236,9 +246,26 @@ "cooldown-not-elapsed"); } - // The proof has conflicts, move it to the conflicting proof - // pool so it can be pulled back if the conflicting ones are - // invalidated. + // If proof replacement is enabled, give the proof a chance to + // replace the conflicting ones. + if (gArgs.GetBoolArg( + "-enableavalancheproofreplacement", + AVALANCHE_DEFAULT_PROOF_REPLACEMENT_ENABLED)) { + if (validProofPool.addProofIfPreferred(proof)) { + // If we have overridden other proofs due to conflict, + // remove the peers and attempt to move them to the + // conflicting pool. + for (const ProofRef &conflictingProof : + conflictingProofs) { + moveToConflictingPool(conflictingProof); + } + + // Replacement is successful, continue to peer creation + break; + } + } + + // Not the preferred proof, or replacement is not enabled return conflictingProofPool.addProofIfPreferred(proof) == ProofPool::AddProofStatus::REJECTED ? invalidate(ProofRegistrationResult::REJECTED, diff --git a/src/avalanche/test/peermanager_tests.cpp b/src/avalanche/test/peermanager_tests.cpp --- a/src/avalanche/test/peermanager_tests.cpp +++ b/src/avalanche/test/peermanager_tests.cpp @@ -887,9 +887,7 @@ BOOST_CHECK(pm.isBoundToPeer(conflictingProof->getId())); } -BOOST_AUTO_TEST_CASE(conflicting_proof_selection) { - avalanche::PeerManager pm; - +BOOST_FIXTURE_TEST_CASE(conflicting_proof_selection, NoCoolDownFixture) { const CKey key = CKey::MakeCompressedKey(); const Amount amount(10 * COIN); @@ -921,11 +919,33 @@ auto proof_base = buildProofWithSequence(10); + gArgs.ForceSetArg("-enableavalancheproofreplacement", "1"); + ConflictingProofComparator comparator; auto checkPreferred = [&](const ProofRef &candidate, const ProofRef &reference, bool expectAccepted) { BOOST_CHECK_EQUAL(comparator(candidate, reference), expectAccepted); BOOST_CHECK_EQUAL(comparator(reference, candidate), !expectAccepted); + + avalanche::PeerManager pm; + BOOST_CHECK(pm.registerProof(reference)); + BOOST_CHECK(pm.isBoundToPeer(reference->getId())); + + ProofRegistrationState state; + BOOST_CHECK_EQUAL(pm.registerProof(candidate, state), expectAccepted); + BOOST_CHECK_EQUAL(state.IsValid(), expectAccepted); + BOOST_CHECK_EQUAL(state.GetResult() == + ProofRegistrationResult::CONFLICTING, + !expectAccepted); + + BOOST_CHECK_EQUAL(pm.isBoundToPeer(candidate->getId()), expectAccepted); + BOOST_CHECK_EQUAL(pm.isInConflictingPool(candidate->getId()), + !expectAccepted); + + BOOST_CHECK_EQUAL(pm.isBoundToPeer(reference->getId()), + !expectAccepted); + BOOST_CHECK_EQUAL(pm.isInConflictingPool(reference->getId()), + expectAccepted); }; // Same master key, lower sequence number @@ -969,6 +989,8 @@ checkPreferred(proofSimilar, proof_multiUtxo, proofSimilar->getId() < proof_multiUtxo->getId()); } + + gArgs.ClearForcedArg("-enableavalancheproofreplacement"); } BOOST_AUTO_TEST_CASE(conflicting_orphans) { diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -1284,6 +1284,10 @@ "in seconds (default: %u)", AVALANCHE_DEFAULT_CONFLICTING_PROOF_COOLDOWN), ArgsManager::ALLOW_INT, OptionsCategory::AVALANCHE); + argsman.AddArg("-enableavalancheproofreplacement", + strprintf("Enable avalanche proof replacement (default: %u)", + AVALANCHE_DEFAULT_PROOF_REPLACEMENT_ENABLED), + ArgsManager::ALLOW_BOOL, OptionsCategory::AVALANCHE); argsman.AddArg( "-avacooldown", strprintf("Mandatory cooldown between two avapoll (default: %u)",