diff --git a/src/avalanche/peermanager.cpp b/src/avalanche/peermanager.cpp --- a/src/avalanche/peermanager.cpp +++ b/src/avalanche/peermanager.cpp @@ -165,7 +165,8 @@ static bool isOrphanState(const ProofValidationState &state) { return state.GetResult() == ProofValidationResult::MISSING_UTXO || - state.GetResult() == ProofValidationResult::HEIGHT_MISMATCH; + state.GetResult() == ProofValidationResult::HEIGHT_MISMATCH || + state.GetResult() == ProofValidationResult::IMMATURE_UTXO; } bool PeerManager::updateNextPossibleConflictTime( diff --git a/src/avalanche/proof.h b/src/avalanche/proof.h --- a/src/avalanche/proof.h +++ b/src/avalanche/proof.h @@ -32,6 +32,13 @@ */ static constexpr bool AVALANCHE_DEFAULT_LEGACY_PROOF = true; +/** + * Minimum number of confirmations before a stake utxo is mature enough to be + * included into a proof. + * FIXME: Set to sane default after the tests have been updated. + */ +static constexpr int AVALANCHE_DEFAULT_STAKE_UTXO_CONFIRMATIONS = 0; + namespace avalanche { /** Minimum amount per utxo */ diff --git a/src/avalanche/proof.cpp b/src/avalanche/proof.cpp --- a/src/avalanche/proof.cpp +++ b/src/avalanche/proof.cpp @@ -13,6 +13,7 @@ #include #include #include +#include // For g_chainman #include @@ -185,10 +186,24 @@ return false; } + const int64_t activeHeight = + WITH_LOCK(cs_main, return g_chainman.ActiveHeight()); + const int64_t stakeUtxoMinConfirmations = + gArgs.GetArg("-avaproofstakeutxoconfirmations", + AVALANCHE_DEFAULT_STAKE_UTXO_CONFIRMATIONS); + for (const SignedStake &ss : stakes) { const Stake &s = ss.getStake(); const COutPoint &utxo = s.getUTXO(); + if ((s.getHeight() + stakeUtxoMinConfirmations) > activeHeight) { + return state.Invalid( + ProofValidationResult::IMMATURE_UTXO, "immature-utxo", + strprintf("TxId: %s, block height: %d, chaintip height: %d", + s.getUTXO().GetTxId().ToString(), s.getHeight(), + activeHeight)); + } + Coin coin; if (!view.GetCoin(utxo, coin)) { // The coins are not in the UTXO set. diff --git a/src/avalanche/test/init_tests.cpp b/src/avalanche/test/init_tests.cpp --- a/src/avalanche/test/init_tests.cpp +++ b/src/avalanche/test/init_tests.cpp @@ -39,6 +39,8 @@ BOOST_CHECK_EQUAL(args.GetBoolArg("-enableavalanche", false), true); BOOST_CHECK_EQUAL(args.GetBoolArg("-legacyavaproof", true), false); + BOOST_CHECK_EQUAL(args.GetArg("-avaproofstakeutxoconfirmations", 42), + 1000); BOOST_CHECK_EQUAL( args.GetBoolArg("-enableavalanchepeerdiscovery", false), true); BOOST_CHECK_EQUAL( @@ -60,6 +62,8 @@ BOOST_CHECK_EQUAL(args.GetBoolArg("-enableavalanche", true), false); BOOST_CHECK_EQUAL(args.GetBoolArg("-legacyavaproof", false), AVALANCHE_DEFAULT_LEGACY_PROOF); + BOOST_CHECK_EQUAL(args.GetArg("-avaproofstakeutxoconfirmations", 42), + AVALANCHE_DEFAULT_STAKE_UTXO_CONFIRMATIONS); BOOST_CHECK_EQUAL( args.GetBoolArg("-enableavalanchepeerdiscovery", true), false); BOOST_CHECK_EQUAL( @@ -79,6 +83,7 @@ ArgsManager args; args.ForceSetArg("-avalanche", "1"); args.ForceSetArg("-legacyavaproof", "1"); + args.ForceSetArg("-avaproofstakeutxoconfirmations", "500"); args.ForceSetArg("-enableavalancheproofreplacement", "0"); args.ForceSetArg("-automaticunparking", "1"); args.ForceSetArg("-avaminquorumstake", FormatMoney(123 * COIN)); @@ -87,6 +92,8 @@ BOOST_CHECK_EQUAL(args.GetBoolArg("-enableavalanche", false), true); BOOST_CHECK_EQUAL(args.GetBoolArg("-legacyavaproof", false), true); + BOOST_CHECK_EQUAL(args.GetArg("-avaproofstakeutxoconfirmations", 42), + 500); BOOST_CHECK_EQUAL( args.GetBoolArg("-enableavalanchepeerdiscovery", false), true); BOOST_CHECK_EQUAL( 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 @@ -117,7 +117,7 @@ } // namespace avalanche namespace { -struct NoCoolDownFixture : public TestingSetup { +struct NoCoolDownFixture : public TestChain100Setup { NoCoolDownFixture() { gArgs.ForceSetArg("-avalancheconflictingproofcooldown", "0"); } @@ -127,7 +127,7 @@ }; } // namespace -BOOST_FIXTURE_TEST_SUITE(peermanager_tests, TestingSetup) +BOOST_FIXTURE_TEST_SUITE(peermanager_tests, TestChain100Setup) BOOST_AUTO_TEST_CASE(select_peer_linear) { // No peers. @@ -694,6 +694,7 @@ } BOOST_AUTO_TEST_CASE(orphan_proofs) { + gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "1"); avalanche::PeerManager pm; auto key = CKey::MakeCompressedKey(); @@ -701,10 +702,12 @@ COutPoint outpoint1 = COutPoint(TxId(GetRandHash()), 0); COutPoint outpoint2 = COutPoint(TxId(GetRandHash()), 0); COutPoint outpoint3 = COutPoint(TxId(GetRandHash()), 0); + COutPoint outpoint4 = COutPoint(TxId(GetRandHash()), 0); const Amount v = 5 * COIN; - const int height = 1234; - const int wrongHeight = 12345; + const int height = 98; + const int wrongHeight = 99; + const int immatureHeight = 100; const auto makeProof = [&](const COutPoint &outpoint, const int h) { return buildProofWithOutpoints(key, {outpoint}, v, key, 0, h); @@ -713,10 +716,12 @@ auto proof1 = makeProof(outpoint1, height); auto proof2 = makeProof(outpoint2, height); auto proof3 = makeProof(outpoint3, wrongHeight); + auto proof4 = makeProof(outpoint4, immatureHeight); - // Add outpoints 1 and 3, not 2 + // Add outpoints, except for proof 2 addCoin(outpoint1, key, v, height); addCoin(outpoint3, key, v, height); + addCoin(outpoint4, key, v, immatureHeight); // Add the proofs BOOST_CHECK(pm.registerProof(proof1)); @@ -729,6 +734,7 @@ registerOrphan(proof2); registerOrphan(proof3); + registerOrphan(proof4); auto checkOrphan = [&](const ProofRef &proof, bool expectedOrphan) { const ProofId &proofid = proof->getId(); @@ -752,6 +758,8 @@ checkOrphan(proof2, true); // HEIGHT_MISMATCH checkOrphan(proof3, true); + // IMMATURE_UTXO + checkOrphan(proof4, true); // Add outpoint2, proof2 is no longer considered orphan addCoin(outpoint2, key, v, height); @@ -763,6 +771,11 @@ checkOrphan(proof1, false); checkOrphan(proof3, true); + // Mine a block to increase the chain height for proof4 verification + mineBlocks(1); + pm.updatedBlockTip(); + checkOrphan(proof4, false); + // Spend outpoint1, proof1 becomes orphan { LOCK(cs_main); @@ -795,8 +808,6 @@ // Track expected orphans so we can test them later std::vector orphans; orphans.push_back(proof1); - orphans.push_back(proof2); - orphans.push_back(proof3); // Fill up orphan pool to test the size limit for (uint32_t i = 1; i < AVALANCHE_MAX_ORPHAN_PROOFS; i++) { @@ -834,6 +845,11 @@ CCoinsViewCache &coins = ::ChainstateActive().CoinsTip(); coins.SpendCoin(outpoint2); coins.SpendCoin(outpoint3); + coins.SpendCoin(outpoint4); + + orphans.push_back(proof2); + orphans.push_back(proof3); + orphans.push_back(proof4); } pm.updatedBlockTip(); @@ -865,6 +881,8 @@ } } BOOST_CHECK_EQUAL(numOrphans, AVALANCHE_MAX_ORPHAN_PROOFS); + + gArgs.ClearForcedArg("-avaproofstakeutxoconfirmations"); } BOOST_AUTO_TEST_CASE(dangling_node) { 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 @@ -11,6 +11,7 @@ #include