Skip to content
10 changes: 9 additions & 1 deletion src/evo/deterministicmns.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "base58.h"
#include "chainparams.h"
#include "consensus/upgrades.h"
#include "core_io.h"
#include "evo/specialtx.h"
#include "guiinterface.h"
Expand Down Expand Up @@ -762,6 +763,7 @@ CDeterministicMNList CDeterministicMNManager::GetListForBlock(const CBlockIndex*
{
LOCK(cs);

// Return early before enforcement
if (!IsDIP3Enforced(pindex->nHeight)) {
return {};
}
Expand Down Expand Up @@ -792,7 +794,13 @@ CDeterministicMNList CDeterministicMNManager::GetListForBlock(const CBlockIndex*

CDeterministicMNListDiff diff;
if (!evoDb.Read(std::make_pair(DB_LIST_DIFF, pindex->GetBlockHash()), diff)) {
// no snapshot and no diff on disk means that it's the initial snapshot
// no snapshot and no diff on disk means that it's initial snapshot (empty list)
// If we get here, then this must be the block before the enforcement of DIP3.
if (!IsActivationHeight(pindex->nHeight + 1, Params().GetConsensus(), Consensus::UPGRADE_V6_0)) {
std::string err = strprintf("No masternode list data found for block %s at height %d. "
"Possible corrupt database.", pindex->GetBlockHash().ToString(), pindex->nHeight);
throw std::runtime_error(err);
}
snapshot = CDeterministicMNList(pindex->GetBlockHash(), -1, 0);
mnListsCache.emplace(pindex->GetBlockHash(), snapshot);
break;
Expand Down
1 change: 1 addition & 0 deletions src/evo/deterministicmns.h
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,7 @@ class CDeterministicMNManager
bool BuildNewListFromBlock(const CBlock& block, const CBlockIndex* pindexPrev, CValidationState& state, CDeterministicMNList& mnListRet, bool debugLogs);
void DecreasePoSePenalties(CDeterministicMNList& mnList);

// to return a valid list, it must have been built first, so never call it with a block not-yet connected (e.g. from CheckBlock).
CDeterministicMNList GetListForBlock(const CBlockIndex* pindex);
CDeterministicMNList GetListAtChainTip();

Expand Down
17 changes: 17 additions & 0 deletions src/evo/providertx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,20 @@ void ProRegPL::ToJson(UniValue& obj) const
obj.pushKV("operatorReward", (double)nOperatorReward / 100);
obj.pushKV("inputsHash", inputsHash.ToString());
}

bool GetProRegCollateral(const CTransactionRef& tx, COutPoint& outRet)
{
if (tx == nullptr) {
Comment thread
furszy marked this conversation as resolved.
return false;
}
if (!tx->IsSpecialTx() || tx->nType != CTransaction::TxType::PROREG) {
return false;
}
ProRegPL pl;
if (!GetTxPayload(*tx, pl)) {
return false;
}
outRet = pl.collateralOutpoint.hash.IsNull() ? COutPoint(tx->GetHash(), pl.collateralOutpoint.n)
: pl.collateralOutpoint;
return true;
}
4 changes: 4 additions & 0 deletions src/evo/providertx.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,8 @@ class ProRegPL

bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state);

// If tx is a ProRegTx, return the collateral outpoint in outRet.
bool GetProRegCollateral(const CTransactionRef& tx, COutPoint& outRet);


#endif //PIVX_PROVIDERTX_H
10 changes: 10 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "checkpoints.h"
#include "compat/sanity.h"
#include "consensus/upgrades.h"
#include "evo/deterministicmns.h"
#include "evo/evonotificationinterface.h"
#include "fs.h"
#include "httpserver.h"
Expand Down Expand Up @@ -1860,6 +1861,7 @@ bool AppInitMain()
strBudgetMode = gArgs.GetArg("-budgetvotemode", "auto");

#ifdef ENABLE_WALLET
// !TODO: remove after complete transition to DMN
// use only the first wallet here. This section can be removed after transition to DMN
if (gArgs.GetBoolArg("-mnconflock", DEFAULT_MNCONFLOCK) && !vpwallets.empty() && vpwallets[0]) {
LOCK(vpwallets[0]->cs_wallet);
Expand All @@ -1873,6 +1875,14 @@ bool AppInitMain()
mne.getAlias(), mne.getTxHash(), mne.getOutputIndex());
}
}

// automatic lock for DMN
if (gArgs.GetBoolArg("-mnconflock", DEFAULT_MNCONFLOCK)) {
const auto& mnList = deterministicMNManager->GetListAtChainTip();
for (CWallet* pwallet : vpwallets) {
pwallet->ScanMasternodeCollateralsAndLock(mnList);
}
}
#endif

// lite mode disables all Masternode related functionality
Expand Down
29 changes: 19 additions & 10 deletions src/test/evo_deterministicmns_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "script/sign.h"
#include "spork.h"
#include "validation.h"
#include "validationinterface.h"

#include <boost/test/unit_test.hpp>

Expand Down Expand Up @@ -192,13 +193,20 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup)

CBlockIndex* chainTip = chainActive.Tip();
int nHeight = chainTip->nHeight;
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, nHeight);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, nHeight + 2);

// load empty list (last block before enforcement)
CreateAndProcessBlock({}, coinbaseKey);
chainTip = chainActive.Tip();
BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);

// force mnsync complete and enable spork 8
masternodeSync.RequestedMasternodeAssets = MASTERNODE_SYNC_FINISHED;
// enable SPORK_8
int64_t nTime = GetTime() - 10;
const CSporkMessage& sporkMnPayment = CSporkMessage(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT, nTime + 1, nTime);
sporkManager.AddOrUpdateSporkMessage(sporkMnPayment);
BOOST_CHECK(sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT));

int port = 1;

std::vector<uint256> dmnHashes;
Expand Down Expand Up @@ -232,8 +240,7 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup)
CreateAndProcessBlock({tx}, coinbaseKey);
chainTip = chainActive.Tip();
BOOST_CHECK_EQUAL(chainTip->nHeight, nHeight + 1);

deterministicMNManager->UpdatedBlockTip(chainTip);
SyncWithValidationInterfaceQueue();
BOOST_CHECK(deterministicMNManager->GetListAtChainTip().HasMN(txid));

// Add change to the utxos map
Expand All @@ -252,10 +259,10 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup)
// Mine 20 blocks, checking MN reward payments
std::map<uint256, int> mapPayments;
for (size_t i = 0; i < 20; i++) {
SyncWithValidationInterfaceQueue();
auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee();
CBlock block = CreateAndProcessBlock({}, coinbaseKey);
chainTip = chainActive.Tip();
deterministicMNManager->UpdatedBlockTip(chainTip);
BOOST_ASSERT(!block.vtx.empty());
BOOST_CHECK(IsMNPayeeInBlock(block, dmnExpectedPayee->pdmnState->scriptPayout));
mapPayments[dmnExpectedPayee->proTxHash]++;
Expand Down Expand Up @@ -356,8 +363,7 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup)
CreateAndProcessBlock(txns, coinbaseKey);
chainTip = chainActive.Tip();
BOOST_CHECK_EQUAL(chainTip->nHeight, nHeight + 1);

deterministicMNManager->UpdatedBlockTip(chainTip);
SyncWithValidationInterfaceQueue();
auto mnList = deterministicMNManager->GetListAtChainTip();
for (size_t j = 0; j < 3; j++) {
BOOST_CHECK(mnList.HasMN(txns[j].GetHash()));
Expand All @@ -369,10 +375,10 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup)
// Mine 30 blocks, checking MN reward payments
mapPayments.clear();
for (size_t i = 0; i < 30; i++) {
SyncWithValidationInterfaceQueue();
auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee();
CBlock block = CreateAndProcessBlock({}, coinbaseKey);
chainTip = chainActive.Tip();
deterministicMNManager->UpdatedBlockTip(chainTip);
BOOST_ASSERT(!block.vtx.empty());
BOOST_CHECK(IsMNPayeeInBlock(block, dmnExpectedPayee->pdmnState->scriptPayout));
mapPayments[dmnExpectedPayee->proTxHash]++;
Expand Down Expand Up @@ -403,8 +409,11 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup)
pblock->vtx[0] = MakeTransactionRef(invalidCoinbaseTx);
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
CValidationState state;
BOOST_CHECK_MESSAGE(!ProcessNewBlock(state, nullptr, pblock, nullptr), "Error, invalid block paying to an already paid DMN passed");
BOOST_CHECK(WITH_LOCK(cs_main, return chainActive.Height()) == nHeight); // no block connected
ProcessNewBlock(state, nullptr, pblock, nullptr);
// block not connected
chainTip = WITH_LOCK(cs_main, return chainActive.Tip());
BOOST_CHECK(chainTip->nHeight == nHeight);
BOOST_CHECK(chainTip->GetBlockHash() != pblock->GetHash());

UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
Expand Down
10 changes: 8 additions & 2 deletions src/test/test_pivx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "guiinterface.h"
#include "evo/deterministicmns.h"
#include "evo/evodb.h"
#include "evo/evonotificationinterface.h"
#include "miner.h"
#include "net_processing.h"
#include "rpc/server.h"
Expand Down Expand Up @@ -81,6 +82,12 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha
// our unit tests aren't testing multiple parts of the code at once.
GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);

// Register EvoNotificationInterface
g_connman = std::unique_ptr<CConnman>(new CConnman(0x1337, 0x1337)); // Deterministic randomness for tests.
connman = g_connman.get();
pEvoNotificationInterface = new EvoNotificationInterface(*connman);
RegisterValidationInterface(pEvoNotificationInterface);

// Ideally we'd move all the RPC tests to the functional testing framework
// instead of unit tests, but for now we need these here.
RegisterAllCoreRPCCommands(tableRPC);
Expand All @@ -100,8 +107,6 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha
nScriptCheckThreads = 3;
for (int i=0; i < nScriptCheckThreads-1; i++)
threadGroup.create_thread(&ThreadScriptCheck);
g_connman = std::unique_ptr<CConnman>(new CConnman(0x1337, 0x1337)); // Deterministic randomness for tests.
connman = g_connman.get();
RegisterNodeSignals(GetNodeSignals());
}

Expand All @@ -114,6 +119,7 @@ TestingSetup::~TestingSetup()
UnregisterAllValidationInterfaces();
GetMainSignals().UnregisterBackgroundSignalScheduler();
UnloadBlockIndex();
delete pEvoNotificationInterface;
delete pcoinsTip;
delete pcoinsdbview;
delete pblocktree;
Expand Down
2 changes: 2 additions & 0 deletions src/test/test_pivx.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,13 @@ struct BasicTestingSetup {
* and wallet (if enabled) setup.
*/
class CConnman;
class EvoNotificationInterface;
struct TestingSetup: public BasicTestingSetup
{
CCoinsViewDB *pcoinsdbview;
boost::thread_group threadGroup;
CConnman* connman;
EvoNotificationInterface* pEvoNotificationInterface;
CScheduler scheduler;

TestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
Expand Down
6 changes: 6 additions & 0 deletions src/tiertwo_networksync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ void CMasternodeSync::RequestDataTo(CNode* pnode, const char* msg, bool forceReq

void CMasternodeSync::SyncRegtest(CNode* pnode)
{
// skip mn list and winners sync if legacy mn are obsolete
if (deterministicMNManager->LegacyMNObsolete() &&
(RequestedMasternodeAssets == MASTERNODE_SYNC_LIST || RequestedMasternodeAssets == MASTERNODE_SYNC_MNW)) {
RequestedMasternodeAssets = MASTERNODE_SYNC_BUDGET;
}

// Initial sync, verify that the other peer answered to all of the messages successfully
if (RequestedMasternodeAssets == MASTERNODE_SYNC_SPORKS) {
RequestDataTo(pnode, NetMsgType::GETSPORKS, false);
Expand Down
22 changes: 16 additions & 6 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1717,6 +1717,15 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd
REJECT_INVALID, "bad-blk-amount");
}

// Masternode/Budget payments
// !TODO: after transition to DMN is complete, check this also during IBD
if (!fInitialBlockDownload) {
if (!IsBlockPayeeValid(block, pindex->pprev)) {
mapRejectedBlocks.emplace(block.GetHash(), GetTime());
return state.DoS(0, false, REJECT_INVALID, "bad-cb-payee", false, "Couldn't find masternode/budget payment");
Comment thread
furszy marked this conversation as resolved.
Outdated
}
}

// For blocks v10+: Check that the coinbase pays the exact amount
if (isPoSActive && pindex->nVersion >= 10 && !IsCoinbaseValueValid(block.vtx[0], nBudgetAmt, state)) {
// pass the state returned by the function above
Expand Down Expand Up @@ -2834,12 +2843,6 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo
// set Cold Staking Spork
fColdStakingActive = !sporkManager.IsSporkActive(SPORK_19_COLDSTAKING_MAINTENANCE);

// check masternode/budget payment
// !TODO: after transition to DMN is complete, check this also during IBD
if (!IsBlockPayeeValid(block, pindexPrev)) {
mapRejectedBlocks.emplace(block.GetHash(), GetTime());
return state.DoS(0, false, REJECT_INVALID, "bad-cb-payee", false, "Couldn't find masternode/budget payment");
}
} else {
LogPrintf("%s: Masternode/Budget payment checks skipped on sync\n", __func__);
}
Expand Down Expand Up @@ -3743,6 +3746,13 @@ static bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs,
// Pass check = true as every addition may be an overwrite.
AddCoins(inputs, *tx, pindex->nHeight, true, fSkipInvalid);
}

CValidationState state;
if (!ProcessSpecialTxsInBlock(block, pindex, state, false /*fJustCheck*/)) {
return error("%s: Special tx processing failed for block %s with %s",
__func__, pindex->GetBlockHash().ToString(), FormatStateMessage(state));
}

return true;
}

Expand Down
47 changes: 46 additions & 1 deletion src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "budget/budgetmanager.h"
#include "coincontrol.h"
#include "evo/deterministicmns.h"
#include "init.h"
#include "guiinterfaceutil.h"
#include "masternode.h"
Expand Down Expand Up @@ -963,7 +964,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
{
LOCK(cs_wallet);
CWalletDB walletdb(*dbw, "r+", fFlushOnClose);
uint256 hash = wtxIn.GetHash();
const uint256& hash = wtxIn.GetHash();

// Inserts only if not already there, returns tx inserted or tx found
std::pair<std::map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.emplace(hash, wtxIn);
Expand Down Expand Up @@ -1164,6 +1165,9 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CWallet
}
}

// If this is a ProRegTx and the wallet owns the collateral, lock the corresponding coin
LockIfMyCollateral(ptx);

bool isFromMe = IsFromMe(ptx);
if (fExisted || IsMine(ptx) || isFromMe || (saplingNoteData && !saplingNoteData->empty())) {

Expand Down Expand Up @@ -4153,6 +4157,47 @@ void CWallet::AutoCombineDust(CConnman* connman)
}
}

void CWallet::LockOutpointIfMine(const CTransactionRef& ptx, const COutPoint& c)
{
AssertLockHeld(cs_wallet);
CTxOut txout;
if (ptx && c.hash == ptx->GetHash() && c.n < ptx->vout.size()) {
// the collateral is an output of this tx
txout = ptx->vout[c.n];
} else {
// the collateral is a reference to an utxo inside this wallet
const auto& it = mapWallet.find(c.hash);
if (it != mapWallet.end()) {
txout = it->second.tx->vout[c.n];
}
}
if (!txout.IsNull() && IsMine(txout) != ISMINE_NO && !IsSpent(c)) {
LockCoin(c);
}
}

// Called during Init
void CWallet::ScanMasternodeCollateralsAndLock(const CDeterministicMNList& mnList)
{
LOCK(cs_wallet);

LogPrintf("Locking masternode collaterals...\n");
mnList.ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) {
LockOutpointIfMine(nullptr, dmn->collateralOutpoint);
});
}

// Called from AddToWalletIfInvolvingMe
void CWallet::LockIfMyCollateral(const CTransactionRef& ptx)
{
AssertLockHeld(cs_wallet);

COutPoint o;
if (GetProRegCollateral(ptx, o)) {
LockOutpointIfMine(ptx, o);
}
}

std::string CWallet::GetWalletHelpString(bool showDebug)
{
std::string strUsage = HelpMessageGroup(_("Wallet options:"));
Expand Down
Loading