diff --git a/src/avalanche/peermanager.h b/src/avalanche/peermanager.h --- a/src/avalanche/peermanager.h +++ b/src/avalanche/peermanager.h @@ -258,6 +258,7 @@ bool isBoundToPeer(const ProofId &proofid) const; bool isOrphan(const ProofId &proofid) const; bool hasConflictingPeer(const ProofId &proofid) const; + bool hasConflicts(const ProofId &proofid) const; private: bool createPeer(const ProofRef &proof); diff --git a/src/avalanche/peermanager.cpp b/src/avalanche/peermanager.cpp --- a/src/avalanche/peermanager.cpp +++ b/src/avalanche/peermanager.cpp @@ -276,6 +276,27 @@ return conflictingProofPool.getProof(proofid) != nullptr; } +bool PeerManager::hasConflicts(const ProofId &proofid) const { + if (hasConflictingPeer(proofid)) { + return true; + } + + // If the proof is not in the conflicting pool, check if it is in the peer + // set and any of its utxo has conflict. + auto proof = validProofPool.getProof(proofid); + if (!proof) { + return false; + } + + for (const SignedStake &ss : proof->getStakes()) { + if (conflictingProofPool.getProof(ss.getStake().getUTXO()) != nullptr) { + return true; + } + } + + return false; +} + bool PeerManager::createPeer(const ProofRef &proof) { assert(proof); 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 @@ -972,4 +972,62 @@ BOOST_CHECK(pm.isOrphan(orphan10->getId())); } +BOOST_AUTO_TEST_CASE(has_conflicts) { + avalanche::PeerManager pm; + + const CKey key = CKey::MakeCompressedKey(); + + const Amount amount = 10 * COIN; + const uint32_t height = 100; + const bool is_coinbase = false; + + CScript script = GetScriptForDestination(PKHash(key.GetPubKey())); + + auto addCoin = [&]() { + LOCK(cs_main); + COutPoint outpoint(TxId(GetRandHash()), 0); + CCoinsViewCache &coins = ::ChainstateActive().CoinsTip(); + coins.AddCoin(outpoint, + Coin(CTxOut(amount, script), height, is_coinbase), false); + + return outpoint; + }; + + auto buildProofWithSequenceAndOutpoints = + [&](uint64_t sequence, const std::vector &outpoints) { + ProofBuilder pb(sequence, 0, key); + + for (const COutPoint &outpoint : outpoints) { + BOOST_CHECK( + pb.addUTXO(outpoint, amount, height, is_coinbase, key)); + } + + return pb.build(); + }; + + for (size_t i = 0; i < 10; i++) { + BOOST_CHECK(!pm.hasConflicts(avalanche::ProofId(GetRandHash()))); + } + + const COutPoint conflictingOutpoint = addCoin(); + auto validProof = + buildProofWithSequenceAndOutpoints(20, {conflictingOutpoint}); + BOOST_CHECK(pm.registerProof(validProof)); + BOOST_CHECK(pm.isBoundToPeer(validProof->getId())); + BOOST_CHECK(!pm.hasConflicts(validProof->getId())); + + auto conflictingProof = + buildProofWithSequenceAndOutpoints(10, {conflictingOutpoint}); + BOOST_CHECK(!pm.registerProof(conflictingProof)); + BOOST_CHECK(pm.hasConflictingPeer(conflictingProof->getId())); + BOOST_CHECK(pm.hasConflicts(validProof->getId())); + BOOST_CHECK(pm.hasConflicts(conflictingProof->getId())); + + auto orphanProof = buildProofWithSequenceAndOutpoints( + 10, {COutPoint{TxId(GetRandHash()), 0}}); + BOOST_CHECK(!pm.registerProof(orphanProof)); + BOOST_CHECK(pm.isOrphan(orphanProof->getId())); + BOOST_CHECK(!pm.hasConflicts(orphanProof->getId())); +} + BOOST_AUTO_TEST_SUITE_END()