diff --git a/src/avalanche/config.h b/src/avalanche/config.h --- a/src/avalanche/config.h +++ b/src/avalanche/config.h @@ -5,15 +5,20 @@ #ifndef BITCOIN_AVALANCHE_CONFIG_H #define BITCOIN_AVALANCHE_CONFIG_H +#include <amount.h> + #include <chrono> namespace avalanche { struct Config { const std::chrono::milliseconds queryTimeoutDuration; + const Amount stakeUtxoDustThreshold; - Config(std::chrono::milliseconds queryTimeoutDurationIn) - : queryTimeoutDuration(queryTimeoutDurationIn) {} + Config(std::chrono::milliseconds queryTimeoutDurationIn, + Amount stakeUtxoDustThresholdIn) + : queryTimeoutDuration(queryTimeoutDurationIn), + stakeUtxoDustThreshold(stakeUtxoDustThresholdIn) {} }; } // namespace avalanche diff --git a/src/avalanche/peermanager.h b/src/avalanche/peermanager.h --- a/src/avalanche/peermanager.h +++ b/src/avalanche/peermanager.h @@ -238,10 +238,12 @@ uint32_t totalPeersScore = 0; uint32_t connectedPeersScore = 0; + Config &avaconfig; ChainstateManager &chainman; public: - PeerManager(ChainstateManager &chainmanIn) : chainman(chainmanIn){}; + PeerManager(Config &avaconfigIn, ChainstateManager &chainmanIn) + : avaconfig(avaconfigIn), chainman(chainmanIn){}; /** * Node API. diff --git a/src/avalanche/peermanager.cpp b/src/avalanche/peermanager.cpp --- a/src/avalanche/peermanager.cpp +++ b/src/avalanche/peermanager.cpp @@ -242,7 +242,8 @@ // Check the proof's validity. ProofValidationState validationState; - if (!WITH_LOCK(cs_main, return proof->verify(validationState, chainman))) { + if (!WITH_LOCK(cs_main, return proof->verify(avaconfig, chainman, + validationState))) { if (isOrphanState(validationState)) { orphanProofPool.addProofIfPreferred(proof); if (orphanProofPool.countProofs() > AVALANCHE_MAX_ORPHAN_PROOFS) { @@ -481,7 +482,7 @@ for (const auto &p : peers) { ProofValidationState state; - if (!p.proof->verify(state, chainman)) { + if (!p.proof->verify(avaconfig, chainman, state)) { if (isOrphanState(state)) { newOrphans.push_back(p.proof); } diff --git a/src/avalanche/processor.h b/src/avalanche/processor.h --- a/src/avalanche/processor.h +++ b/src/avalanche/processor.h @@ -186,6 +186,8 @@ public: ~Processor(); + const Config &GetAvaConfig() { return avaconfig; } + static std::unique_ptr<Processor> MakeProcessor(const ArgsManager &argsman, interfaces::Chain &chain, CConnman *connman, ChainstateManager &chainman, diff --git a/src/avalanche/processor.cpp b/src/avalanche/processor.cpp --- a/src/avalanche/processor.cpp +++ b/src/avalanche/processor.cpp @@ -35,10 +35,11 @@ std::unique_ptr<avalanche::Processor> g_avalanche; namespace avalanche { -static bool VerifyProof(const Proof &proof, bilingual_str &error) { +static bool VerifyProof(const Config &avaconfig, const Proof &proof, + bilingual_str &error) { ProofValidationState proof_state; - if (!proof.verify(proof_state)) { + if (!proof.verify(avaconfig, proof_state)) { switch (proof_state.GetResult()) { case ProofValidationResult::NO_STAKE: error = _("The avalanche proof has no stake."); @@ -137,7 +138,7 @@ uint32_t staleVoteThresholdIn, uint32_t staleVoteFactorIn) : avaconfig(std::move(avaconfigIn)), connman(connmanIn), chainman(chainmanIn), round(0), - peerManager(std::make_unique<PeerManager>(chainman)), + peerManager(std::make_unique<PeerManager>(avaconfig, chainman)), peerData(std::move(peerDataIn)), sessionKey(std::move(sessionKeyIn)), minQuorumScore(minQuorumTotalScoreIn), minQuorumConnectedScoreRatio(minQuorumConnectedScoreRatioIn), @@ -171,6 +172,19 @@ CKey masterKey; CKey sessionKey; + const auto queryTimeoutDuration = std::chrono::milliseconds( + argsman.GetArg("-avatimeout", AVALANCHE_DEFAULT_QUERY_TIMEOUT.count())); + + Amount stakeUtxoDustThreshold = PROOF_DUST_THRESHOLD; + if (gArgs.IsArgSet("-avaproofstakeutxodustthreshold") && + !ParseMoney(gArgs.GetArg("-avaproofstakeutxodustthreshold", ""), + stakeUtxoDustThreshold)) { + error = _("The avalanche stake utxo dust threshold amount is invalid."); + return {}; + } + + Config avaconfig(queryTimeoutDuration, stakeUtxoDustThreshold); + if (argsman.IsArgSet("-avasessionkey")) { sessionKey = DecodeSecret(argsman.GetArg("-avasessionkey", "")); if (!sessionKey.IsValid()) { @@ -203,7 +217,7 @@ peerData = std::make_unique<PeerData>(); peerData->proof = std::move(proof); - if (!VerifyProof(*peerData->proof, error)) { + if (!VerifyProof(avaconfig, *peerData->proof, error)) { // error is set by VerifyProof return nullptr; } @@ -257,9 +271,6 @@ } } - const auto queryTimeoutDuration = std::chrono::milliseconds( - argsman.GetArg("-avatimeout", AVALANCHE_DEFAULT_QUERY_TIMEOUT.count())); - // Determine quorum parameters Amount minQuorumStake = AVALANCHE_DEFAULT_MIN_QUORUM_STAKE; if (argsman.IsArgSet("-avaminquorumstake") && @@ -326,8 +337,6 @@ return nullptr; } - Config avaconfig(queryTimeoutDuration); - // We can't use std::make_unique with a private constructor return std::unique_ptr<Processor>(new Processor( std::move(avaconfig), chain, connman, chainman, scheduler, diff --git a/src/avalanche/proof.h b/src/avalanche/proof.h --- a/src/avalanche/proof.h +++ b/src/avalanche/proof.h @@ -6,6 +6,7 @@ #define BITCOIN_AVALANCHE_PROOF_H #include <amount.h> +#include <avalanche/config.h> #include <avalanche/proofid.h> #include <key.h> #include <primitives/transaction.h> @@ -188,9 +189,9 @@ uint32_t getScore() const { return score; } Amount getStakedAmount() const; - bool verify(ProofValidationState &state) const; - bool verify(ProofValidationState &state, - const ChainstateManager &chainman) const + bool verify(const Config &avaconfig, ProofValidationState &state) const; + bool verify(const Config &avaconfig, const ChainstateManager &chainman, + ProofValidationState &state) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); }; diff --git a/src/avalanche/proof.cpp b/src/avalanche/proof.cpp --- a/src/avalanche/proof.cpp +++ b/src/avalanche/proof.cpp @@ -10,6 +10,7 @@ #include <policy/policy.h> #include <script/standard.h> #include <streams.h> +#include <util/moneystr.h> #include <util/strencodings.h> #include <util/system.h> #include <util/translation.h> @@ -122,7 +123,7 @@ }); } -bool Proof::verify(ProofValidationState &state) const { +bool Proof::verify(const Config &avaconfig, ProofValidationState &state) const { if (stakes.empty()) { return state.Invalid(ProofValidationResult::NO_STAKE, "no-stake"); } @@ -150,11 +151,12 @@ std::unordered_set<COutPoint, SaltedOutpointHasher> utxos; for (const SignedStake &ss : stakes) { const Stake &s = ss.getStake(); - if (s.getAmount() < PROOF_DUST_THRESHOLD) { - return state.Invalid(ProofValidationResult::DUST_THRESHOLD, - "amount-below-dust-threshold", - strprintf("%s < %s", s.getAmount().ToString(), - PROOF_DUST_THRESHOLD.ToString())); + if (s.getAmount() < avaconfig.stakeUtxoDustThreshold) { + return state.Invalid( + ProofValidationResult::DUST_THRESHOLD, + "amount-below-dust-threshold", + strprintf("%s < %s", s.getAmount().ToString(), + avaconfig.stakeUtxoDustThreshold.ToString())); } if (s.getId() < prevId) { @@ -179,10 +181,10 @@ return true; } -bool Proof::verify(ProofValidationState &state, - const ChainstateManager &chainman) const { +bool Proof::verify(const Config &avaconfig, const ChainstateManager &chainman, + ProofValidationState &state) const { AssertLockHeld(cs_main); - if (!verify(state)) { + if (!verify(avaconfig, state)) { // state is set by verify. return false; } 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 @@ -294,9 +294,10 @@ }; BOOST_AUTO_TEST_CASE(peer_probabilities) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); // No peers. - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); BOOST_CHECK_EQUAL(pm.selectNode(), NO_NODE); const NodeId node0 = 42, node1 = 69, node2 = 37; @@ -332,9 +333,10 @@ } BOOST_AUTO_TEST_CASE(remove_peer) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); // No peers. - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); BOOST_CHECK_EQUAL(pm.selectPeer(), NO_PEER); CChainState &active_chainstate = chainman.ActiveChainstate(); @@ -410,8 +412,9 @@ } BOOST_AUTO_TEST_CASE(compact_slots) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); // Add 4 peers. std::array<PeerId, 4> peerids; @@ -440,8 +443,9 @@ } BOOST_AUTO_TEST_CASE(node_crud) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); CChainState &active_chainstate = chainman.ActiveChainstate(); @@ -504,8 +508,9 @@ } BOOST_AUTO_TEST_CASE(node_binding) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); CChainState &active_chainstate = chainman.ActiveChainstate(); @@ -608,9 +613,10 @@ BOOST_AUTO_TEST_CASE(node_binding_reorg) { gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2"); + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); auto proof = buildRandomProof(chainman.ActiveChainstate(), MIN_VALID_PROOF_SCORE, 99); @@ -680,13 +686,14 @@ const Amount v = PROOF_DUST_THRESHOLD; const int height = 100; + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); for (uint32_t i = 0; i < 10; i++) { addCoin(chainman.ActiveChainstate(), {txid1, i}, key); addCoin(chainman.ActiveChainstate(), {txid2, i}, key); } - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); CKey masterKey = CKey::MakeCompressedKey(); const auto getPeerId = [&](const std::vector<COutPoint> &outpoints) { return TestPeerManager::registerAndGetPeerId( @@ -739,9 +746,10 @@ } BOOST_AUTO_TEST_CASE(orphan_proofs) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2"); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); auto key = CKey::MakeCompressedKey(); int immatureHeight = 100; @@ -821,8 +829,9 @@ } BOOST_AUTO_TEST_CASE(dangling_node) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); CChainState &active_chainstate = chainman.ActiveChainstate(); @@ -869,8 +878,9 @@ } BOOST_AUTO_TEST_CASE(proof_accessors) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); constexpr int numProofs = 10; @@ -915,8 +925,9 @@ } BOOST_FIXTURE_TEST_CASE(conflicting_proof_rescan, NoCoolDownFixture) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); const CKey key = CKey::MakeCompressedKey(); @@ -960,6 +971,7 @@ const uint32_t height = 100; const bool is_coinbase = false; + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); CChainState &active_chainstate = chainman.ActiveChainstate(); @@ -976,7 +988,7 @@ BOOST_CHECK_EQUAL(comparator(candidate, reference), expectAccepted); BOOST_CHECK_EQUAL(comparator(reference, candidate), !expectAccepted); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); BOOST_CHECK(pm.registerProof(reference)); BOOST_CHECK(pm.isBoundToPeer(reference->getId())); @@ -1052,9 +1064,10 @@ } BOOST_AUTO_TEST_CASE(conflicting_orphans) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2"); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); const CKey key = CKey::MakeCompressedKey(); @@ -1099,8 +1112,9 @@ } BOOST_FIXTURE_TEST_CASE(preferred_conflicting_proof, NoCoolDownFixture) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); const CKey key = CKey::MakeCompressedKey(); const COutPoint conflictingOutpoint = @@ -1132,8 +1146,9 @@ } BOOST_FIXTURE_TEST_CASE(update_next_conflict_time, NoCoolDownFixture) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); auto now = GetTime<std::chrono::seconds>(); SetMockTime(now.count()); @@ -1167,8 +1182,9 @@ } BOOST_FIXTURE_TEST_CASE(register_force_accept, NoCoolDownFixture) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); const CKey key = CKey::MakeCompressedKey(); @@ -1233,8 +1249,9 @@ } BOOST_FIXTURE_TEST_CASE(evicted_proof, NoCoolDownFixture) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); const CKey key = CKey::MakeCompressedKey(); @@ -1265,8 +1282,9 @@ } BOOST_AUTO_TEST_CASE(conflicting_proof_cooldown) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); const CKey key = CKey::MakeCompressedKey(); @@ -1335,9 +1353,10 @@ } BOOST_FIXTURE_TEST_CASE(reject_proof, NoCoolDownFixture) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2"); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); const CKey key = CKey::MakeCompressedKey(); @@ -1418,8 +1437,9 @@ } BOOST_AUTO_TEST_CASE(should_request_more_nodes) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); // Set mock time so that proof registration time is predictable and // testable. @@ -1514,8 +1534,9 @@ } BOOST_AUTO_TEST_CASE(score_ordering) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); std::vector<uint32_t> expectedScores(10); // Expect the peers to be ordered by descending score @@ -1540,9 +1561,10 @@ } BOOST_FIXTURE_TEST_CASE(known_score_tracking, NoCoolDownFixture) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2"); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); const CKey key = CKey::MakeCompressedKey(); @@ -1657,8 +1679,9 @@ } BOOST_AUTO_TEST_CASE(connected_score_tracking) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); const auto checkScores = [&pm](uint32_t known, uint32_t connected) { BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), known); @@ -1748,8 +1771,9 @@ } BOOST_FIXTURE_TEST_CASE(proof_radix_tree, NoCoolDownFixture) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); gArgs.ForceSetArg("-enableavalancheproofreplacement", "1"); @@ -1867,8 +1891,9 @@ } BOOST_AUTO_TEST_CASE(received_avaproofs) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); auto addNode = [&](NodeId nodeid) { auto proof = buildRandomProof(chainman.ActiveChainstate(), @@ -1890,10 +1915,11 @@ } BOOST_FIXTURE_TEST_CASE(cleanup_dangling_proof, NoCoolDownFixture) { + auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); gArgs.ForceSetArg("-enableavalancheproofreplacement", "1"); - avalanche::PeerManager pm(chainman); + avalanche::PeerManager pm(avaconfig, chainman); const auto now = GetTime<std::chrono::seconds>(); auto mocktime = now; diff --git a/src/avalanche/test/proof_tests.cpp b/src/avalanche/test/proof_tests.cpp --- a/src/avalanche/test/proof_tests.cpp +++ b/src/avalanche/test/proof_tests.cpp @@ -4,11 +4,13 @@ #include <avalanche/proof.h> +#include <avalanche/processor.h> #include <avalanche/proofbuilder.h> #include <avalanche/test/util.h> #include <avalanche/validation.h> #include <coins.h> #include <script/standard.h> +#include <util/moneystr.h> #include <util/strencodings.h> #include <util/translation.h> #include <validation.h> @@ -22,6 +24,7 @@ BOOST_FIXTURE_TEST_SUITE(proof_tests, TestChain100Setup) BOOST_AUTO_TEST_CASE(proof_random) { + const auto avaconfig = buildDefaultConfig(); CChainState &active_chainstate = Assert(m_node.chainman)->ActiveChainstate(); @@ -35,14 +38,14 @@ : ProofValidationResult::NONE; ProofValidationState state; - BOOST_CHECK_EQUAL(p->verify(state), + BOOST_CHECK_EQUAL(p->verify(avaconfig, state), state.GetResult() == ProofValidationResult::NONE); BOOST_CHECK(state.GetResult() == expected_state); } } BOOST_AUTO_TEST_CASE(proofbuilder) { - // Master key. + const auto avaconfig = buildDefaultConfig(); auto key = CKey::MakeCompressedKey(); const CPubKey master = key.GetPubKey(); @@ -61,7 +64,7 @@ ProofRef p = pb.build(); ProofValidationState state; - BOOST_CHECK(p->verify(state)); + BOOST_CHECK(p->verify(avaconfig, state)); BOOST_CHECK_EQUAL(p->getSequence(), sequence); BOOST_CHECK_EQUAL(p->getExpirationTime(), expiration); @@ -863,6 +866,7 @@ 2 * 444638638, ProofValidationResult::NONE}, }; + const auto avaconfig = buildDefaultConfig(); auto checkCases = [&](const std::vector<TestVector> &testcases) { for (auto &c : testcases) { Proof p; @@ -872,7 +876,7 @@ BOOST_CHECK_EQUAL(p.getScore(), c.score); ProofValidationState state; - BOOST_CHECK_EQUAL(p.verify(state), + BOOST_CHECK_EQUAL(p.verify(avaconfig, state), c.result == ProofValidationResult::NONE); BOOST_CHECK(state.GetResult() == c.result); BOOST_TEST_MESSAGE(c.proofid); @@ -896,9 +900,10 @@ auto key = CKey::MakeCompressedKey(); const CPubKey pubkey = key.GetPubKey(); - const Amount value = 12345 * COIN; + const Amount value = 2 * PROOF_DUST_THRESHOLD; const uint32_t height = 10; + const auto avaconfig = buildDefaultConfig(); ChainstateManager &chainman = *Assert(m_node.chainman); LOCK(cs_main); CCoinsViewCache &coins = chainman.ActiveChainstate().CoinsTip(); @@ -926,9 +931,9 @@ ProofRef p = pb.build(); ProofValidationState state; - BOOST_CHECK(p->verify(state)); + BOOST_CHECK(p->verify(avaconfig, state)); LOCK(cs_main); - BOOST_CHECK(p->verify(state, chainman) == + BOOST_CHECK(p->verify(avaconfig, chainman, state) == (result == ProofValidationResult::NONE)); BOOST_CHECK(state.GetResult() == result); }; @@ -976,31 +981,42 @@ ProofRef p = ProofBuilder(0, 0, key).build(); ProofValidationState state; - BOOST_CHECK(!p->verify(state, chainman)); + BOOST_CHECK(!p->verify(avaconfig, chainman, state)); BOOST_CHECK(state.GetResult() == ProofValidationResult::NO_STAKE); } // Dust thresold { - ProofBuilder pb(0, 0, key); - BOOST_CHECK( - pb.addUTXO(pkh_outpoint, Amount::zero(), height, false, key)); - ProofRef p = pb.build(); + // tuple<utxo value, dust threshold, result> + std::vector<std::tuple<Amount, Amount, ProofValidationResult>> + testCases = { + // Defaults + {Amount::zero(), PROOF_DUST_THRESHOLD, + ProofValidationResult::DUST_THRESHOLD}, + {PROOF_DUST_THRESHOLD - 1 * SATOSHI, PROOF_DUST_THRESHOLD, + ProofValidationResult::DUST_THRESHOLD}, + {value, PROOF_DUST_THRESHOLD, ProofValidationResult::NONE}, + // Modified threshold + {Amount::zero(), value, ProofValidationResult::DUST_THRESHOLD}, + {value - 1 * SATOSHI, value, + ProofValidationResult::DUST_THRESHOLD}, + {value, value, ProofValidationResult::NONE}, + }; - ProofValidationState state; - BOOST_CHECK(!p->verify(state, chainman)); - BOOST_CHECK(state.GetResult() == ProofValidationResult::DUST_THRESHOLD); - } + for (auto it = testCases.begin(); it != testCases.end(); ++it) { + const auto dustThresholdConfig = avalanche::Config( + AVALANCHE_DEFAULT_QUERY_TIMEOUT, std::get<1>(*it)); - { - ProofBuilder pb(0, 0, key); - BOOST_CHECK(pb.addUTXO(pkh_outpoint, PROOF_DUST_THRESHOLD - 1 * SATOSHI, - height, false, key)); - ProofRef p = pb.build(); + ProofBuilder pb(0, 0, key); + BOOST_CHECK( + pb.addUTXO(pkh_outpoint, std::get<0>(*it), height, false, key)); + ProofRef p = pb.build(); - ProofValidationState state; - BOOST_CHECK(!p->verify(state, chainman)); - BOOST_CHECK(state.GetResult() == ProofValidationResult::DUST_THRESHOLD); + ProofValidationState state; + BOOST_CHECK(p->verify(dustThresholdConfig, chainman, state) == + (std::get<2>(*it) == ProofValidationResult::NONE)); + BOOST_CHECK(state.GetResult() == std::get<2>(*it)); + } } // Duplicated input @@ -1010,7 +1026,7 @@ ProofRef p = TestProofBuilder::buildDuplicatedStakes(pb); ProofValidationState state; - BOOST_CHECK(!p->verify(state, chainman)); + BOOST_CHECK(!p->verify(avaconfig, chainman, state)); BOOST_CHECK(state.GetResult() == ProofValidationResult::DUPLICATE_STAKE); } @@ -1028,7 +1044,7 @@ ProofRef p = TestProofBuilder::buildWithReversedOrderStakes(pb); ProofValidationState state; - BOOST_CHECK(!p->verify(state, chainman)); + BOOST_CHECK(!p->verify(avaconfig, chainman, state)); BOOST_CHECK(state.GetResult() == ProofValidationResult::WRONG_STAKE_ORDERING); } 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 @@ -78,9 +78,10 @@ } BOOST_AUTO_TEST_CASE(rescan) { + auto avaconfig = buildDefaultConfig(); gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "1"); ProofPool testPool; - avalanche::PeerManager pm(*Assert(m_node.chainman)); + avalanche::PeerManager pm(avaconfig, *Assert(m_node.chainman)); testPool.rescan(pm); BOOST_CHECK_EQUAL(testPool.size(), 0); diff --git a/src/avalanche/test/util.h b/src/avalanche/test/util.h --- a/src/avalanche/test/util.h +++ b/src/avalanche/test/util.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_AVALANCHE_TEST_UTIL_H #define BITCOIN_AVALANCHE_TEST_UTIL_H +#include <avalanche/config.h> #include <avalanche/proof.h> #include <avalanche/proofbuilder.h> #include <key.h> @@ -15,6 +16,8 @@ constexpr uint32_t MIN_VALID_PROOF_SCORE = 100 * PROOF_DUST_THRESHOLD / COIN; +Config buildDefaultConfig(); + ProofRef buildRandomProof(CChainState &active_chainstate, uint32_t score, int height = 100, const CKey &masterKey = CKey::MakeCompressedKey()); diff --git a/src/avalanche/test/util.cpp b/src/avalanche/test/util.cpp --- a/src/avalanche/test/util.cpp +++ b/src/avalanche/test/util.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <amount.h> +#include <avalanche/processor.h> #include <avalanche/proofbuilder.h> #include <avalanche/test/util.h> #include <key.h> @@ -17,6 +18,10 @@ namespace avalanche { +Config buildDefaultConfig() { + return Config(AVALANCHE_DEFAULT_QUERY_TIMEOUT, PROOF_DUST_THRESHOLD); +} + ProofRef buildRandomProof(CChainState &active_chainstate, uint32_t score, int height, const CKey &masterKey) { auto key = CKey::MakeCompressedKey(); diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -1400,6 +1400,11 @@ "accepted (i.e this value must be greater than 0) (default: %s)", AVALANCHE_DEFAULT_STAKE_UTXO_CONFIRMATIONS), ArgsManager::ALLOW_INT, OptionsCategory::HIDDEN); + argsman.AddArg("-avaproofstakeutxodustthreshold", + strprintf("Minimum value each stake utxo must have to be " + "considered valid (default: %s)", + avalanche::PROOF_DUST_THRESHOLD), + ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN); argsman.AddArg("-avamasterkey", "Master key associated with the proof. If a proof is " "required, this is mandatory.", @@ -2165,6 +2170,23 @@ "a positive integer.")); } + if (args.IsArgSet("-avaproofstakeutxodustthreshold")) { + Amount amount = Amount::zero(); + auto parsed = ParseMoney( + args.GetArg("-avaproofstakeutxodustthreshold", ""), amount); + if (!parsed || Amount::zero() == amount) { + return InitError(AmountErrMsg( + "avaproofstakeutxodustthreshold", + args.GetArg("-avaproofstakeutxodustthreshold", ""))); + } + + if (!chainparams.IsTestChain() && + amount != avalanche::PROOF_DUST_THRESHOLD) { + return InitError(_("Avalanche stake UTXO dust threshold can " + "only be set on test chains.")); + } + } + return true; } diff --git a/src/rpc/avalanche.cpp b/src/rpc/avalanche.cpp --- a/src/rpc/avalanche.cpp +++ b/src/rpc/avalanche.cpp @@ -85,7 +85,8 @@ avalanche::ProofValidationState state; { LOCK(cs_main); - if (!proof.verify(state, *Assert(node.chainman))) { + if (!proof.verify(g_avalanche->GetAvaConfig(), *Assert(node.chainman), + state)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "The proof is invalid: " + state.ToString()); } @@ -1026,6 +1027,11 @@ const JSONRPCRequest &request) -> UniValue { RPCTypeCheck(request.params, {UniValue::VSTR}); + if (!g_avalanche) { + throw JSONRPCError(RPC_INTERNAL_ERROR, + "Avalanche is not initialized"); + } + avalanche::Proof proof; verifyProofOrThrow(EnsureAnyNodeContext(request.context), proof, request.params[0].get_str());