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,12 @@ */ 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. + */ +static constexpr int AVALANCHE_DEFAULT_STAKE_UTXO_CONFIRMATIONS = 2016; + 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,6 +186,12 @@ 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(); @@ -196,6 +203,14 @@ "utxo-missing-or-spent"); } + if ((s.getHeight() + stakeUtxoMinConfirmations - 1) > 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)); + } + if (s.isCoinbase() != coin.IsCoinBase()) { return state.Invalid( ProofValidationResult::COINBASE_MISMATCH, "coinbase-mismatch", 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 @@ -121,7 +121,18 @@ } // namespace avalanche namespace { -struct NoCoolDownFixture : public TestingSetup { +struct PeerManagerFixture : public TestChain100Setup { + PeerManagerFixture() { + gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "1"); + } + ~PeerManagerFixture() { + gArgs.ClearForcedArg("-avaproofstakeutxoconfirmations"); + } +}; +} // namespace + +namespace { +struct NoCoolDownFixture : public PeerManagerFixture { NoCoolDownFixture() { gArgs.ForceSetArg("-avalancheconflictingproofcooldown", "0"); } @@ -131,7 +142,7 @@ }; } // namespace -BOOST_FIXTURE_TEST_SUITE(peermanager_tests, TestingSetup) +BOOST_FIXTURE_TEST_SUITE(peermanager_tests, PeerManagerFixture) BOOST_AUTO_TEST_CASE(select_peer_linear) { // No peers. @@ -698,6 +709,7 @@ } BOOST_AUTO_TEST_CASE(orphan_proofs) { + gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2"); avalanche::PeerManager pm; auto key = CKey::MakeCompressedKey(); @@ -705,10 +717,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); @@ -717,10 +731,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)); @@ -733,6 +749,7 @@ registerOrphan(proof2); registerOrphan(proof3); + registerOrphan(proof4); auto checkOrphan = [&](const ProofRef &proof, bool expectedOrphan) { const ProofId &proofid = proof->getId(); @@ -756,6 +773,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); @@ -767,6 +786,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); @@ -799,8 +823,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++) { @@ -838,6 +860,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(); diff --git a/src/avalanche/test/processor_tests.cpp b/src/avalanche/test/processor_tests.cpp --- a/src/avalanche/test/processor_tests.cpp +++ b/src/avalanche/test/processor_tests.cpp @@ -113,11 +113,15 @@ m_processor = Processor::MakeProcessor(*m_node.args, *m_node.chain, m_node.connman.get(), error); BOOST_CHECK(m_processor); + + gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "1"); } ~AvalancheTestingSetup() { m_connman->ClearNodes(); SyncWithValidationInterfaceQueue(); + + gArgs.ClearForcedArg("-avaproofstakeutxoconfirmations"); } CNode *ConnectNode(ServiceFlags nServices) { @@ -1137,7 +1141,7 @@ uint32_t minScore = Proof::amountToScore(minStake * currency.baseunit); const CKey key = CKey::MakeCompressedKey(); - auto localProof = buildRandomProof(minScore / 4, 1234, key); + auto localProof = buildRandomProof(minScore / 4, 100, key); gArgs.ForceSetArg("-avamasterkey", EncodeSecret(key)); gArgs.ForceSetArg("-avaproof", localProof->ToHex()); 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