diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 7f7671f73e..abd9795881 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -4,6 +4,12 @@ #include "wallet/wallet.h" +#include "evo/deterministicmns.h" +#include "evo/evodb.h" +#include "evo/providertx.h" +#include "evo/specialtx.h" +#include "script/standard.h" + #include #include #include @@ -363,6 +369,98 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset) empty_wallet(); }*/ +BOOST_AUTO_TEST_CASE(auto_lock_masternode_collaterals) +{ + CWallet testWallet; + LOCK2(cs_main, testWallet.cs_wallet); + + CKey ownedKey, watchedKey, foreignKey; + ownedKey.MakeNewKey(true); + watchedKey.MakeNewKey(true); + foreignKey.MakeNewKey(true); + BOOST_REQUIRE(testWallet.AddKeyPubKey(ownedKey, ownedKey.GetPubKey())); + const CScript ownedScript = GetScriptForDestination(ownedKey.GetPubKey().GetID()); + const CScript watchedScript = GetScriptForDestination(watchedKey.GetPubKey().GetID()); + const CScript foreignScript = GetScriptForDestination(foreignKey.GetPubKey().GetID()); + BOOST_REQUIRE(testWallet.AddWatchOnly(watchedScript, 0)); + + uint32_t nonce = 0; + const auto addOutput = [&](const CScript& script, CAmount amount, bool internal) { + CMutableTransaction tx; + tx.nLockTime = ++nonce; + // The other output has the collateral amount too, but is not collateral. + tx.vout.emplace_back(1000 * COIN, ownedScript); + tx.vout.emplace_back(amount, script); + if (internal) { + tx.nVersion = 3; + tx.nType = TRANSACTION_PROVIDER_REGISTER; + CProRegTx proTx; + proTx.collateralOutpoint.n = 1; + SetTxPayload(tx, proTx); + } + const auto txRef = MakeTransactionRef(tx); + // Exercise the startup scan, not AddToWallet's independent auto-locking. + BOOST_REQUIRE(testWallet.LoadToWallet(CWalletTx(&testWallet, txRef))); + return COutPoint(txRef->GetHash(), 1); + }; + + const auto internalOwned = addOutput(ownedScript, 1000 * COIN, true); + const auto internalWatched = addOutput(watchedScript, 1000 * COIN, true); + const auto internalForeign = addOutput(foreignScript, 1000 * COIN, true); + const auto internalSpent = addOutput(ownedScript, 1000 * COIN, true); + const auto wrongAmount = addOutput(ownedScript, 999 * COIN, true); + const auto ordinary = addOutput(ownedScript, 1000 * COIN, false); + const auto externalOwned = addOutput(ownedScript, 1000 * COIN, false); + const auto externalForeign = addOutput(foreignScript, 1000 * COIN, false); + const auto externalSpent = addOutput(ownedScript, 1000 * COIN, false); + + // Supply an MN-list snapshot in the fixture's in-memory EvoDB. These + // ordinary transactions are collateral only through their registered MNs. + const CBlockIndex* tip = chainActive.Tip(); + BOOST_REQUIRE(tip); + CDeterministicMNList mnList(tip->GetBlockHash(), tip->nHeight, 3); + for (const auto& collateral : {externalOwned, externalForeign, externalSpent}) { + auto dmn = std::make_shared(); + dmn->proTxHash = SerializeHash(collateral); + dmn->internalId = mnList.GetAllMNsCount(); + dmn->collateralOutpoint = collateral; + dmn->nOperatorReward = 0; + auto dmnState = std::make_shared(); + CKey ownerKey; + ownerKey.MakeNewKey(true); + dmnState->keyIDOwner = ownerKey.GetPubKey().GetID(); + dmn->pdmnState = dmnState; + mnList.AddMN(dmn); + } + evoDb->Write(std::make_pair(std::string("dmn_S"), tip->GetBlockHash()), mnList); + deterministicMNManager->ClearCache(); + deterministicMNManager->UpdatedBlockTip(tip); + BOOST_REQUIRE(deterministicMNManager->GetListAtChainTip().HasMNByCollateral(externalOwned)); + + CMutableTransaction spend; + spend.vin.emplace_back(internalSpent); + spend.vin.emplace_back(externalSpent); + spend.vout.emplace_back(1 * COIN, ownedScript); + BOOST_REQUIRE(testWallet.LoadToWallet(CWalletTx(&testWallet, MakeTransactionRef(spend)))); + BOOST_REQUIRE(testWallet.IsSpent(internalSpent.hash, internalSpent.n)); + BOOST_REQUIRE(testWallet.IsSpent(externalSpent.hash, externalSpent.n)); + + testWallet.LockCoin(ordinary); // Preserve existing manual locks as well. + testWallet.AutoLockMasternodeCollaterals(); + std::vector locked; + testWallet.ListLockedCoins(locked); + const std::set expected{internalOwned, internalWatched, externalOwned, ordinary}; + BOOST_CHECK(std::set(locked.begin(), locked.end()) == expected); + BOOST_CHECK(!testWallet.IsLockedCoin(internalForeign.hash, internalForeign.n)); + BOOST_CHECK(!testWallet.IsLockedCoin(externalForeign.hash, externalForeign.n)); + BOOST_CHECK(!testWallet.IsLockedCoin(wrongAmount.hash, wrongAmount.n)); + + testWallet.AutoLockMasternodeCollaterals(); + locked.clear(); + testWallet.ListLockedCoins(locked); + BOOST_CHECK(std::set(locked.begin(), locked.end()) == expected); +} + BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) { LOCK(cs_main); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 704ec75121..510582f674 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4200,8 +4200,9 @@ void CWallet::AutoLockMasternodeCollaterals() LOCK2(cs_main, cs_wallet); for (const auto& pair : mapWallet) { for (unsigned int i = 0; i < pair.second.tx->vout.size(); ++i) { - if (IsMine(pair.second.tx->vout[i], *pair.second.tx) && !IsSpent(pair.first, i)) { - if (deterministicMNManager->IsProTxWithCollateral(pair.second.tx, i) || mnList.HasMNByCollateral(COutPoint(pair.first, i))) { + // Avoid Spark ownership/spent checks for outputs that are not collateral. + if (deterministicMNManager->IsProTxWithCollateral(pair.second.tx, i) || mnList.HasMNByCollateral(COutPoint(pair.first, i))) { + if (IsMine(pair.second.tx->vout[i], *pair.second.tx) && !IsSpent(pair.first, i)) { LockCoin(COutPoint(pair.first, i)); } }