diff --git a/src/avalanche/proof.h b/src/avalanche/proof.h --- a/src/avalanche/proof.h +++ b/src/avalanche/proof.h @@ -25,6 +25,9 @@ namespace avalanche { +/** Minimum amount per utxo */ +static constexpr Amount PROOF_DUST_THRESHOLD = 1 * COIN; + class ProofValidationState; class Stake { diff --git a/src/avalanche/proof.cpp b/src/avalanche/proof.cpp --- a/src/avalanche/proof.cpp +++ b/src/avalanche/proof.cpp @@ -47,8 +47,6 @@ return uint32_t((100 * total) / COIN); } -static constexpr Amount PROOF_DUST_THRESOLD = 1 * SATOSHI; - bool Proof::verify(ProofValidationState &state) const { if (stakes.empty()) { return state.Invalid(ProofValidationResult::NO_STAKE); @@ -61,7 +59,7 @@ std::unordered_set utxos; for (const SignedStake &ss : stakes) { const Stake &s = ss.getStake(); - if (s.getAmount() < PROOF_DUST_THRESOLD) { + if (s.getAmount() < PROOF_DUST_THRESHOLD) { return state.Invalid(ProofValidationResult::DUST_THRESOLD); } 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 @@ -162,13 +162,13 @@ const NodeId node0 = 42, node1 = 69, node2 = 37; // One peer, we always return it. - Proof proof0 = buildRandomProof(100); + Proof proof0 = buildRandomProof(MIN_VALID_PROOF_SCORE); Delegation dg0 = DelegationBuilder(proof0).build(); pm.addNode(node0, proof0, dg0); BOOST_CHECK_EQUAL(pm.selectNode(), node0); // Two peers, verify ratio. - Proof proof1 = buildRandomProof(200); + Proof proof1 = buildRandomProof(2 * MIN_VALID_PROOF_SCORE); Delegation dg1 = DelegationBuilder(proof1).build(); pm.addNode(node1, proof1, dg1); @@ -182,7 +182,7 @@ BOOST_CHECK(abs(2 * results[0] - results[1]) < 500); // Three peers, verify ratio. - Proof proof2 = buildRandomProof(100); + Proof proof2 = buildRandomProof(MIN_VALID_PROOF_SCORE); Delegation dg2 = DelegationBuilder(proof2).build(); pm.addNode(node2, proof2, dg2); @@ -308,7 +308,7 @@ avalanche::PeerManager pm; // Create one peer. - Proof proof = buildRandomProof(100000000); + Proof proof = buildRandomProof(10000000 * MIN_VALID_PROOF_SCORE); Delegation dg = DelegationBuilder(proof).build(); BOOST_CHECK_EQUAL(pm.selectNode(), NO_NODE); @@ -346,8 +346,8 @@ } // Move a node from a peer to another. This peer has a very low score such - // as chances of being picked are 1 in a billion. - Proof altproof = buildRandomProof(1); + // as chances of being picked are 1 in 10 million. + Proof altproof = buildRandomProof(MIN_VALID_PROOF_SCORE); Delegation altdg = DelegationBuilder(altproof).build(); BOOST_CHECK(pm.addNode(3, altproof, altdg)); 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 @@ -26,9 +26,14 @@ const Proof p = buildRandomProof(score); BOOST_CHECK_EQUAL(p.getScore(), score); + ProofValidationResult expected_state = + hasDustStake(p) ? ProofValidationResult::DUST_THRESOLD + : ProofValidationResult::NONE; + ProofValidationState state; - BOOST_CHECK(p.verify(state)); - BOOST_CHECK(state.GetResult() == ProofValidationResult::NONE); + BOOST_CHECK_EQUAL(p.verify(state), + state.GetResult() == ProofValidationResult::NONE); + BOOST_CHECK(state.GetResult() == expected_state); } } @@ -340,7 +345,7 @@ key.MakeNewKey(true); const CPubKey pubkey = key.GetPubKey(); - const Amount value = 12345 * SATOSHI; + const Amount value = 12345 * COIN; const uint32_t height = 10; COutPoint pkh_outpoint(TxId(InsecureRand256()), InsecureRand32()); @@ -432,6 +437,17 @@ BOOST_CHECK(state.GetResult() == ProofValidationResult::DUST_THRESOLD); } + { + ProofBuilder pb(0, 0, pubkey); + pb.addUTXO(pkh_outpoint, PROOF_DUST_THRESHOLD - 1 * SATOSHI, height, + false, key); + Proof p = pb.build(); + + ProofValidationState state; + BOOST_CHECK(!p.verify(state, coins)); + BOOST_CHECK(state.GetResult() == ProofValidationResult::DUST_THRESOLD); + } + // Duplicated input { ProofBuilder pb(0, 0, pubkey); 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 @@ -12,8 +12,12 @@ namespace avalanche { +constexpr uint32_t MIN_VALID_PROOF_SCORE = 100 * PROOF_DUST_THRESHOLD / COIN; + Proof buildRandomProof(uint32_t score, const CPubKey &master = CPubKey()); +bool hasDustStake(const Proof &proof); + } // namespace avalanche #endif // BITCOIN_AVALANCHE_TEST_UTIL_H 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 @@ -37,4 +37,13 @@ return pb.build(); } +bool hasDustStake(const Proof &proof) { + for (const SignedStake &s : proof.getStakes()) { + if (s.getStake().getAmount() < PROOF_DUST_THRESHOLD) { + return true; + } + } + return false; +} + } // namespace avalanche diff --git a/test/functional/abc_rpc_avalancheproof.py b/test/functional/abc_rpc_avalancheproof.py --- a/test/functional/abc_rpc_avalancheproof.py +++ b/test/functional/abc_rpc_avalancheproof.py @@ -3,6 +3,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test building avalanche proofs and using them to add avalanche peers.""" +from decimal import Decimal from test_framework.avatools import ( create_coinbase_stakes, @@ -21,6 +22,9 @@ AVALANCHE_MAX_PROOF_STAKES = 1000 +PROOF_DUST_THRESHOLD = 1.0 +"""Minimum amount per UTXO in a proof (in coins, not in satoshis)""" + def add_interface_node(test_node) -> str: """Create a mininode, connect it to test_node, return the nodeid @@ -180,6 +184,12 @@ proof_sequence, proof_expiration, proof_master, create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key, amount="0")) + dust_amount = Decimal(f"{PROOF_DUST_THRESHOLD * 0.9999:.4f}") + dust2 = node.buildavalancheproof( + proof_sequence, proof_expiration, proof_master, + create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key, + amount=str(dust_amount))) + duplicate_stake = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, create_coinbase_stakes(node, [blockhashes[0]] * 2, addrkey0.key)) @@ -229,6 +239,8 @@ "the avalanche proof has no stake") check_proof_init_error(dust, "the avalanche proof stake is too low") + check_proof_init_error(dust2, + "the avalanche proof stake is too low") check_proof_init_error(duplicate_stake, "the avalanche proof has duplicated stake") check_proof_init_error(bad_sig,