diff --git a/src/avalanche/proof.h b/src/avalanche/proof.h --- a/src/avalanche/proof.h +++ b/src/avalanche/proof.h @@ -20,6 +20,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); @@ -57,7 +55,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(LOWEST_VALID_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 * LOWEST_VALID_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(LOWEST_VALID_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(100000000 * LOWEST_VALID_SCORE); Delegation dg = DelegationBuilder(proof).build(); BOOST_CHECK_EQUAL(pm.selectNode(), NO_NODE); @@ -347,7 +347,7 @@ // 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); + Proof altproof = buildRandomProof(LOWEST_VALID_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 @@ -22,7 +22,7 @@ BOOST_AUTO_TEST_CASE(proof_random) { for (int i = 0; i < 1000; i++) { - const uint32_t score = InsecureRand32(); + const uint32_t score = std::min(InsecureRand32(), LOWEST_VALID_SCORE); const Proof p = buildRandomProof(score); BOOST_CHECK_EQUAL(p.getScore(), score); @@ -340,7 +340,7 @@ key.MakeNewKey(true); const CPubKey pubkey = key.GetPubKey(); - const Amount value = 12345 * SATOSHI; + const Amount value = 123 * PROOF_DUST_THRESHOLD; const uint32_t height = 10; COutPoint pkh_outpoint(TxId(InsecureRand256()), InsecureRand32()); @@ -432,6 +432,17 @@ BOOST_CHECK(state.GetResult() == ProofValidationResult::DUST_THRESOLD); } + { + ProofBuilder pb(0, 0, pubkey); + pb.addUTXO(pkh_outpoint, PROOF_DUST_THRESHOLD - 1 * Amount::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,6 +12,9 @@ namespace avalanche { +// Minimum score for the proof to be valid +constexpr uint32_t LOWEST_VALID_SCORE = PROOF_DUST_THRESHOLD / COIN * 100; + Proof buildRandomProof(uint32_t score, const CPubKey &master = CPubKey()); } // namespace avalanche 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 @@ -16,6 +16,7 @@ namespace avalanche { Proof buildRandomProof(uint32_t score, const CPubKey &master) { + assert(score >= LOWEST_VALID_SCORE); CKey key; key.MakeNewKey(true); 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 @@ -17,6 +17,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 @@ -105,6 +108,11 @@ proof_sequence, proof_expiration, proof_master, get_stakes(node, [blockhashes[0]], addrkey0.key, amount="0")) + dust2 = node.buildavalancheproof( + proof_sequence, proof_expiration, proof_master, + get_stakes(node, [blockhashes[0]], addrkey0.key, + amount=f"{PROOF_DUST_THRESHOLD * 0.9999}")) + duplicate_stake = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, get_stakes(node, [blockhashes[0]] * 2, addrkey0.key)) @@ -132,6 +140,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,