From e3df91082288c168a19053342d06f44c10d24cb0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 25 Oct 2018 16:29:50 +0200 Subject: [PATCH] Allow referencing other TX outputs for ProRegTx collateral (#2366) * Pass CCoinsView reference to special TX handling methods * Allow referencing other TX outputs for ProRegTx collateral * Remove "collateralAmount" from "protx register" * Rename "protx register" to "protx fund_register" * Remove UpdateSpork15Value from CDeterministicMNManager Was not used/implemented anymore * Lock masternode collaterals after chain/DIP3 is fully initialized Otherwise detection of collaterals does not work. * Implement new "protx register" RPC which uses existing collaterals * Remove "masternode info" RPC It is not consistent with other "masternode" RPCs anymore as it requires the ProRegTx hash while all other RPCs work with the collateral. * Load sporks from disk cache before initializing the chain Otherwise spork15 is not loaded when we check it for the first time. * Implement "protx info" RPC * Use "protx info" instead of "masternode info" in DIP3 tests * Test external collaterals for ProTx * Handle review comments * Don't pass CCoinView reference when it's not used * Revert "Pass CCoinsView reference to special TX handling methods" This reverts commit 28688724e112c8fe18e44aef055768dbbc068d7d. * Use GetUTXOCoin instead of now removed coinsView Also remove collateral height check as GetUTXOCoin only returns confirmed coins. * Add conflict handling for external collaterals to mempool * Handle review comments (squashed Github suggestions) Co-Authored-By: codablock --- qa/rpc-tests/dip3-deterministicmns.py | 153 ++++++++++-------- src/activemasternode.cpp | 7 +- src/evo/deterministicmns.cpp | 84 +++++++--- src/evo/deterministicmns.h | 27 ++-- src/evo/providertx.cpp | 104 ++++++++---- src/evo/providertx.h | 7 +- src/init.cpp | 22 ++- src/masternode-payments.cpp | 2 +- src/masternode.cpp | 4 +- src/masternodeman.cpp | 23 ++- src/rpc/governance.cpp | 4 +- src/rpc/masternode.cpp | 88 +--------- src/rpc/rpcevo.cpp | 203 +++++++++++++++++------- src/test/evo_deterministicmns_tests.cpp | 3 +- src/txmempool.cpp | 23 ++- src/txmempool.h | 2 + src/wallet/wallet.cpp | 25 ++- src/wallet/wallet.h | 1 + 18 files changed, 465 insertions(+), 317 deletions(-) diff --git a/qa/rpc-tests/dip3-deterministicmns.py b/qa/rpc-tests/dip3-deterministicmns.py index 59776bbd8..9858c85c7 100755 --- a/qa/rpc-tests/dip3-deterministicmns.py +++ b/qa/rpc-tests/dip3-deterministicmns.py @@ -151,7 +151,7 @@ class DIP3Test(BitcoinTestFramework): self.force_finish_mnsync_list(before_dip3_mn.node) self.start_alias(self.nodes[0], before_dip3_mn.alias) - self.wait_for_mnlists(mns, True, False) + self.wait_for_mnlists(mns) self.wait_for_mnlists_same() # Test if nodes deny creating new non-ProTx MNs now @@ -159,32 +159,45 @@ class DIP3Test(BitcoinTestFramework): self.start_alias(self.nodes[0], after_dip3_mn.alias, should_fail=True) first_upgrade_count = 5 + mns_after_upgrade = [] + mns_to_restart = [] + mns_protx = [] print("upgrading first %d MNs to use ProTx (but not deterministic MN lists)" % first_upgrade_count) for i in range(first_upgrade_count): - mns[i] = self.upgrade_mn_protx(mns[i]) + # let a few of the protx MNs refer to the old collaterals + fund = (i % 2) == 0 + mns[i] = self.upgrade_mn_protx(mns[i], fund) self.nodes[0].generate(1) + + if fund: + # collateral has moved, so we need to start it again + mns_to_restart.append(mns[i]) + else: + # collateral has not moved, so it should still be in the masternode list even after upgrade + mns_after_upgrade.append(mns[i]) + mns_protx.append(mns[i]) + for i in range(first_upgrade_count, len(mns)): + mns_after_upgrade.append(mns[i]) self.write_mnconf(mns) - print("wait for upgraded MNs to disappear from MN lists (their collateral was spent)") - self.wait_for_mnlists(mns, True, False, check=True) + print("wait for freshly funded and upgraded MNs to disappear from MN lists (their collateral was spent)") + self.wait_for_mnlists(mns_after_upgrade, check=True) self.wait_for_mnlists_same() print("restarting controller and upgraded MNs") self.restart_controller_node() self.force_finish_mnsync_list(self.nodes[0]) - for mn in mns: - if mn.is_protx: - print("restarting MN %s" % mn.alias) - self.stop_node(mn.idx) - self.start_mn(mn) - self.force_finish_mnsync_list(mn.node) + for mn in mns_to_restart: + print("restarting MN %s" % mn.alias) + self.stop_node(mn.idx) + self.start_mn(mn) + self.force_finish_mnsync_list(mn.node) print('start-alias on upgraded nodes') - for mn in mns: - if mn.is_protx: - self.start_alias(self.nodes[0], mn.alias) + for mn in mns_to_restart: + self.start_alias(self.nodes[0], mn.alias) print("wait for upgraded MNs to appear in MN list") - self.wait_for_mnlists(mns, True, True) + self.wait_for_mnlists(mns) self.wait_for_mnlists_same() print("testing MN payment votes (with mixed ProTx and legacy nodes)") @@ -203,7 +216,7 @@ class DIP3Test(BitcoinTestFramework): for i in range(spork15_offset - 1): self.nodes[0].generate(1) self.sync_all() - self.wait_for_mnlists(mns, True, True) + self.wait_for_mnlists(mns) self.wait_for_mnlists_same() print("mining final block which should switch network to deterministic lists") @@ -214,7 +227,7 @@ class DIP3Test(BitcoinTestFramework): ##### From now on, we don't wait for mnlists to become correct anymore, we always assert that they are correct immediately print("assert that not upgraded MNs disappeared from MN list") - self.assert_mnlists(mns, False, True) + self.assert_mnlists(mns_protx) # enable enforcement and keep it on from now on self.nodes[0].spork('SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT', 0) @@ -222,41 +235,42 @@ class DIP3Test(BitcoinTestFramework): print("test that MNs disappear from the list when the ProTx collateral is spent") spend_mns_count = 3 - mns_tmp = [] + mns + mns_tmp = [] + mns_protx dummy_txins = [] for i in range(spend_mns_count): - dummy_txin = self.spend_mn_collateral(mns[i], with_dummy_input_output=True) + dummy_txin = self.spend_mn_collateral(mns_protx[i], with_dummy_input_output=True) dummy_txins.append(dummy_txin) self.nodes[0].generate(1) self.sync_all() - mns_tmp.remove(mns[i]) - self.assert_mnlists(mns_tmp, False, True) + mns_tmp.remove(mns_protx[i]) + self.assert_mnlists(mns_tmp) print("test that reverting the blockchain on a single node results in the mnlist to be reverted as well") for i in range(spend_mns_count): self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) - mns_tmp.append(mns[spend_mns_count - 1 - i]) - self.assert_mnlist(self.nodes[0], mns_tmp, False, True) + mns_tmp.append(mns_protx[spend_mns_count - 1 - i]) + self.assert_mnlist(self.nodes[0], mns_tmp) print("cause a reorg with a double spend and check that mnlists are still correct on all nodes") self.mine_double_spend(self.nodes[0], dummy_txins, self.nodes[0].getnewaddress(), use_mnmerkleroot_from_tip=True) self.nodes[0].generate(spend_mns_count) self.sync_all() - self.assert_mnlists(mns_tmp, False, True) + self.assert_mnlists(mns_tmp) print("upgrade remaining MNs to ProTx") for i in range(first_upgrade_count, len(mns)): - mns[i] = self.upgrade_mn_protx(mns[i]) + mns[i] = self.upgrade_mn_protx(mns[i], True) mn = mns[i] self.nodes[0].generate(1) + mns_protx.append(mn) print("restarting MN %s" % mn.alias) self.stop_node(mn.idx) self.start_mn(mn) self.sync_all() self.force_finish_mnsync(mn.node) - self.assert_mnlists(mns, False, True) + self.assert_mnlists(mns_protx) - self.assert_mnlists(mns, False, True) + self.assert_mnlists(mns_protx) print("test mn payment enforcement with deterministic MNs") for i in range(20): @@ -269,7 +283,7 @@ class DIP3Test(BitcoinTestFramework): self.test_instantsend(20, 5) print("testing ProUpServTx") - for mn in mns: + for mn in mns_protx: self.test_protx_update_service(mn) def create_mn(self, node, idx, alias): @@ -297,7 +311,7 @@ class DIP3Test(BitcoinTestFramework): return mn - def create_mn_protx(self, node, idx, alias): + def create_mn_protx_base(self, node, idx, alias): mn = Masternode() mn.idx = idx mn.alias = alias @@ -310,12 +324,19 @@ class DIP3Test(BitcoinTestFramework): mn.votingAddr = mn.ownerAddr mn.legacyMnkey = node.masternode('genkey') mn.blsMnkey = blsKey['secret'] + + return mn + + # create a protx MN and also fund it (using collateral inside ProRegTx) + def create_mn_protx_fund(self, node, idx, alias): + mn = self.create_mn_protx_base(node, idx, alias) mn.collateral_address = node.getnewaddress() - mn.collateral_txid = node.protx('register', mn.collateral_address, '1000', '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, 0, mn.collateral_address) - rawtx = node.getrawtransaction(mn.collateral_txid, 1) - + mn.protx_hash = node.protx('fund_register', mn.collateral_address, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, 0, mn.collateral_address) + mn.collateral_txid = mn.protx_hash mn.collateral_vout = -1 + + rawtx = node.getrawtransaction(mn.collateral_txid, 1) for txout in rawtx['vout']: if txout['value'] == Decimal(1000): mn.collateral_vout = txout['n'] @@ -324,6 +345,17 @@ class DIP3Test(BitcoinTestFramework): return mn + # create a protx MN which refers to an existing collateral + def create_mn_protx(self, node, idx, alias, collateral_txid, collateral_vout): + mn = self.create_mn_protx_base(node, idx, alias) + mn.rewards_address = node.getnewaddress() + + mn.protx_hash = node.protx('register', collateral_txid, collateral_vout, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, 0, mn.rewards_address) + mn.collateral_txid = collateral_txid + mn.collateral_vout = collateral_vout + + return mn + def start_mn(self, mn): while len(self.nodes) <= mn.idx: self.nodes.append(None) @@ -339,23 +371,26 @@ class DIP3Test(BitcoinTestFramework): def spend_mn_collateral(self, mn, with_dummy_input_output=False): return self.spend_input(mn.collateral_txid, mn.collateral_vout, 1000, with_dummy_input_output) - def upgrade_mn_protx(self, mn): - self.spend_mn_collateral(mn) - mn = self.create_mn_protx(self.nodes[0], mn.idx, 'mn-protx-%d' % mn.idx) + def upgrade_mn_protx(self, mn, refund): + if refund: + self.spend_mn_collateral(mn) + mn = self.create_mn_protx_fund(self.nodes[0], mn.idx, 'mn-protx-%d' % mn.idx) + else: + mn = self.create_mn_protx(self.nodes[0], mn.idx, 'mn-protx-%d' % mn.idx, mn.collateral_txid, mn.collateral_vout) return mn def test_protx_update_service(self, mn): - self.nodes[0].protx('update_service', mn.collateral_txid, '127.0.0.2:%d' % mn.p2p_port, mn.blsMnkey) + self.nodes[0].protx('update_service', mn.protx_hash, '127.0.0.2:%d' % mn.p2p_port, mn.blsMnkey) self.nodes[0].generate(1) self.sync_all() for node in self.nodes: - mn_info = node.masternode('info', mn.collateral_txid) + protx_info = node.protx('info', mn.protx_hash) mn_list = node.masternode('list') - assert_equal(mn_info['state']['addr'], '127.0.0.2:%d' % mn.p2p_port) + assert_equal(protx_info['state']['addr'], '127.0.0.2:%d' % mn.p2p_port) assert_equal(mn_list['%s-%d' % (mn.collateral_txid, mn.collateral_vout)]['address'], '127.0.0.2:%d' % mn.p2p_port) # undo - self.nodes[0].protx('update_service', mn.collateral_txid, '127.0.0.1:%d' % mn.p2p_port, mn.blsMnkey) + self.nodes[0].protx('update_service', mn.protx_hash, '127.0.0.1:%d' % mn.p2p_port, mn.blsMnkey) self.nodes[0].generate(1) def force_finish_mnsync(self, node): @@ -510,30 +545,29 @@ class DIP3Test(BitcoinTestFramework): time.sleep(0.5) raise AssertionError("wait_for_winners for height {} timed out: {}".format(height, node.masternode('winners'))) - def wait_for_mnlists(self, mns, include_legacy, include_protx, timeout=30, check=False): + def wait_for_mnlists(self, mns, timeout=30, check=False): for node in self.nodes: - self.wait_for_mnlist(node, mns, include_legacy, include_protx, timeout, check=check) + self.wait_for_mnlist(node, mns, timeout, check=check) - def wait_for_mnlist(self, node, mns, include_legacy, include_protx, timeout=30, check=False): + def wait_for_mnlist(self, node, mns, timeout=30, check=False): st = time.time() while time.time() < st + timeout: if check: node.masternode('check') - if self.compare_mnlist(node, mns, include_legacy, include_protx): + if self.compare_mnlist(node, mns): return time.sleep(0.5) raise AssertionError("wait_for_mnlist timed out") - def assert_mnlists(self, mns, include_legacy, include_protx): + def assert_mnlists(self, mns): for node in self.nodes: - self.assert_mnlist(node, mns, include_legacy, include_protx) + self.assert_mnlist(node, mns) - def assert_mnlist(self, node, mns, include_legacy, include_protx): - if not self.compare_mnlist(node, mns, include_legacy, include_protx): + def assert_mnlist(self, node, mns): + if not self.compare_mnlist(node, mns): expected = [] for mn in mns: - if (mn.is_protx and include_protx) or (not mn.is_protx and include_legacy): - expected.append('%s-%d' % (mn.collateral_txid, mn.collateral_vout)) + expected.append('%s-%d' % (mn.collateral_txid, mn.collateral_vout)) print('mnlist: ' + str(node.masternode('list', 'status'))) print('expected: ' + str(expected)) raise AssertionError("mnlists does not match provided mns") @@ -554,26 +588,13 @@ class DIP3Test(BitcoinTestFramework): return False return True - def compare_mnlist(self, node, mns, include_legacy, include_protx): + def compare_mnlist(self, node, mns): mnlist = node.masternode('list', 'status') for mn in mns: s = '%s-%d' % (mn.collateral_txid, mn.collateral_vout) in_list = s in mnlist - - if mn.is_protx: - if include_protx: - if not in_list: - return False - else: - if in_list: - return False - else: - if include_legacy: - if not in_list: - return False - else: - if in_list: - return False + if not in_list: + return False mnlist.pop(s, None) if len(mnlist) != 0: return False @@ -599,13 +620,13 @@ class DIP3Test(BitcoinTestFramework): address = node.getnewaddress() key = node.getnewaddress() blsKey = node.bls('generate') - assert_raises_jsonrpc(None, "bad-tx-type", node.protx, 'register', address, '1000', '127.0.0.1:10000', key, blsKey['public'], key, 0, address) + assert_raises_jsonrpc(None, "bad-tx-type", node.protx, 'fund_register', address, '127.0.0.1:10000', key, blsKey['public'], key, 0, address) def test_success_create_protx(self, node): address = node.getnewaddress() key = node.getnewaddress() blsKey = node.bls('generate') - txid = node.protx('register', address, '1000', '127.0.0.1:10000', key, blsKey['public'], key, 0, address) + txid = node.protx('fund_register', address, '127.0.0.1:10000', key, blsKey['public'], key, 0, address) rawtx = node.getrawtransaction(txid, 1) self.mine_double_spend(node, rawtx['vin'], address, use_mnmerkleroot_from_tip=True) self.sync_all() diff --git a/src/activemasternode.cpp b/src/activemasternode.cpp index 89dd94d22..bbe47aae8 100644 --- a/src/activemasternode.cpp +++ b/src/activemasternode.cpp @@ -85,7 +85,7 @@ void CActiveDeterministicMasternodeManager::Init() return; } - activeMasternodeInfo.outpoint = COutPoint(mnListEntry->proTxHash, mnListEntry->nCollateralIndex); + activeMasternodeInfo.outpoint = mnListEntry->collateralOutpoint; state = MASTERNODE_READY; } @@ -103,7 +103,8 @@ void CActiveDeterministicMasternodeManager::UpdatedBlockTip(const CBlockIndex* p if (state == MASTERNODE_WAITING_FOR_PROTX) { Init(); } else if (state == MASTERNODE_READY) { - if (!deterministicMNManager->HasValidMNAtBlock(pindexNew->GetBlockHash(), mnListEntry->proTxHash)) { + auto mnList = deterministicMNManager->GetListForBlock(pindexNew->GetBlockHash()); + if (!mnList.IsMNValid(mnListEntry->proTxHash)) { // MN disappeared from MN list state = MASTERNODE_REMOVED; activeMasternodeInfo.outpoint.SetNull(); @@ -367,7 +368,7 @@ void CActiveLegacyMasternodeManager::ManageStateRemote() LogPrintf("CActiveLegacyMasternodeManager::ManageStateRemote -- %s: %s\n", GetStateString(), strNotCapableReason); return; } - auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(infoMn.outpoint.hash); + auto dmn = deterministicMNManager->GetListAtChainTip().GetMNByCollateral(infoMn.outpoint); if (dmn) { if (dmn->pdmnState->addr != infoMn.addr) { nState = ACTIVE_MASTERNODE_NOT_CAPABLE; diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 9c7048d7b..534bfbc92 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -66,7 +66,7 @@ void CDeterministicMNState::ToJson(UniValue& obj) const std::string CDeterministicMN::ToString() const { - return strprintf("CDeterministicMN(proTxHash=%s, nCollateralIndex=%d, nOperatorReward=%f, state=%s", proTxHash.ToString(), nCollateralIndex, (double)nOperatorReward / 100, pdmnState->ToString()); + return strprintf("CDeterministicMN(proTxHash=%s, collateralOutpoint=%s, nOperatorReward=%f, state=%s", proTxHash.ToString(), collateralOutpoint.ToStringShort(), (double)nOperatorReward / 100, pdmnState->ToString()); } void CDeterministicMN::ToJson(UniValue& obj) const @@ -78,7 +78,8 @@ void CDeterministicMN::ToJson(UniValue& obj) const pdmnState->ToJson(stateObj); obj.push_back(Pair("proTxHash", proTxHash.ToString())); - obj.push_back(Pair("collateralIndex", (int)nCollateralIndex)); + obj.push_back(Pair("collateralHash", collateralOutpoint.hash.ToString())); + obj.push_back(Pair("collateralIndex", (int)collateralOutpoint.n)); obj.push_back(Pair("operatorReward", (double)nOperatorReward / 100)); obj.push_back(Pair("state", stateObj)); } @@ -141,6 +142,11 @@ CDeterministicMNCPtr CDeterministicMNList::GetMNByOperatorKey(const CBLSPublicKe return nullptr; } +CDeterministicMNCPtr CDeterministicMNList::GetMNByCollateral(const COutPoint& collateralOutpoint) const +{ + return GetUniquePropertyMN(collateralOutpoint); +} + static int CompareByLastPaid_GetHeight(const CDeterministicMN &dmn) { int h = dmn.pdmnState->nLastPaidHeight; @@ -253,6 +259,7 @@ void CDeterministicMNList::AddMN(const CDeterministicMNCPtr &dmn) { assert(!mnMap.find(dmn->proTxHash)); mnMap = mnMap.set(dmn->proTxHash, dmn); + AddUniqueProperty(dmn, dmn->collateralOutpoint); AddUniqueProperty(dmn, dmn->pdmnState->addr); AddUniqueProperty(dmn, dmn->pdmnState->keyIDOwner); AddUniqueProperty(dmn, dmn->pdmnState->pubKeyOperator); @@ -276,6 +283,7 @@ void CDeterministicMNList::RemoveMN(const uint256& proTxHash) { auto dmn = GetMN(proTxHash); assert(dmn != nullptr); + DeleteUniqueProperty(dmn, dmn->collateralOutpoint); DeleteUniqueProperty(dmn, dmn->pdmnState->addr); DeleteUniqueProperty(dmn, dmn->pdmnState->keyIDOwner); DeleteUniqueProperty(dmn, dmn->pdmnState->pubKeyOperator); @@ -368,13 +376,12 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const C // check if any existing MN collateral is spent by this transaction for (const auto& in : tx.vin) { - const uint256& proTxHash = in.prevout.hash; - auto dmn = newList.GetMN(proTxHash); - if (dmn && dmn->nCollateralIndex == in.prevout.n) { - newList.RemoveMN(proTxHash); + auto dmn = newList.GetMNByCollateral(in.prevout); + if (dmn && dmn->collateralOutpoint == in.prevout) { + newList.RemoveMN(dmn->proTxHash); - LogPrintf("CDeterministicMNManager::%s -- MN %s removed from list because collateral was spent. nHeight=%d, mapCurMNs.allMNsCount=%d\n", - __func__, proTxHash.ToString(), nHeight, newList.GetAllMNsCount()); + LogPrintf("CDeterministicMNManager::%s -- MN %s removed from list because collateral was spent. collateralOutpoint=%s, nHeight=%d, mapCurMNs.allMNsCount=%d\n", + __func__, dmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort(), nHeight, newList.GetAllMNsCount()); } } @@ -389,7 +396,25 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const C if (newList.HasUniqueProperty(proTx.keyIDOwner) || newList.HasUniqueProperty(proTx.pubKeyOperator)) return _state.DoS(100, false, REJECT_CONFLICT, "bad-protx-dup-key"); - auto dmn = std::make_shared(tx.GetHash(), proTx); + auto dmn = std::make_shared(); + dmn->proTxHash = tx.GetHash(); + + // collateralOutpoint is either pointing to an external collateral or to the ProRegTx itself + if (proTx.collateralOutpoint.hash.IsNull()) { + dmn->collateralOutpoint = COutPoint(tx.GetHash(), proTx.collateralOutpoint.n); + } else { + dmn->collateralOutpoint = proTx.collateralOutpoint; + } + + Coin coin; + if (!proTx.collateralOutpoint.hash.IsNull() && (!GetUTXOCoin(dmn->collateralOutpoint, coin) || coin.out.nValue != 1000 * COIN)) { + // should actually never get to this point as CheckProRegTx should have handled this case. + // We do this additional check nevertheless to be 100% sure + return _state.DoS(100, false, REJECT_INVALID, "bad-protx-collateral"); + } + + dmn->nOperatorReward = proTx.nOperatorReward; + dmn->pdmnState = std::make_shared(proTx); CDeterministicMNState dmnState = *dmn->pdmnState; dmnState.nRegisteredHeight = nHeight; @@ -550,21 +575,18 @@ CDeterministicMNList CDeterministicMNManager::GetListAtChainTip() return GetListForBlock(tipBlockHash); } -CDeterministicMNCPtr CDeterministicMNManager::GetMN(const uint256& blockHash, const uint256& proTxHash) +bool CDeterministicMNManager::HasValidMNCollateralAtChainTip(const COutPoint& outpoint) { - auto mnList = GetListForBlock(blockHash); - return mnList.GetMN(proTxHash); + auto mnList = GetListAtChainTip(); + auto dmn = mnList.GetMNByCollateral(outpoint); + return dmn && mnList.IsMNValid(dmn); } -bool CDeterministicMNManager::HasValidMNAtBlock(const uint256& blockHash, const uint256& proTxHash) +bool CDeterministicMNManager::HasMNCollateralAtChainTip(const COutPoint& outpoint) { - auto mnList = GetListForBlock(blockHash); - return mnList.IsMNValid(proTxHash); -} - -bool CDeterministicMNManager::HasValidMNAtChainTip(const uint256& proTxHash) -{ - return GetListAtChainTip().IsMNValid(proTxHash); + auto mnList = GetListAtChainTip(); + auto dmn = mnList.GetMNByCollateral(outpoint); + return dmn != nullptr; } int64_t CDeterministicMNManager::GetSpork15Value() @@ -572,6 +594,28 @@ int64_t CDeterministicMNManager::GetSpork15Value() return sporkManager.GetSporkValue(SPORK_15_DETERMINISTIC_MNS_ENABLED); } +bool CDeterministicMNManager::IsProTxWithCollateral(const CTransactionRef& tx, uint32_t n) +{ + if (tx->nVersion < 3 || tx->nType != TRANSACTION_PROVIDER_REGISTER) { + return false; + } + CProRegTx proTx; + if (!GetTxPayload(*tx, proTx)) { + return false; + } + + if (!proTx.collateralOutpoint.hash.IsNull()) { + return false; + } + if (proTx.collateralOutpoint.n >= tx->vout.size() || proTx.collateralOutpoint.n != n) { + return false; + } + if (tx->vout[n].nValue != 1000 * COIN) { + return false; + } + return true; +} + bool CDeterministicMNManager::IsDeterministicMNsSporkActive(int nHeight) { LOCK(cs); diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 027217ccc..09ba21d32 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -115,18 +115,11 @@ class CDeterministicMN { public: CDeterministicMN() {} - CDeterministicMN(const uint256& _proTxHash, const CProRegTx& _proTx) - { - proTxHash = _proTxHash; - nCollateralIndex = _proTx.nCollateralIndex; - nOperatorReward = _proTx.nOperatorReward; - pdmnState = std::make_shared(_proTx); - } template CDeterministicMN(deserialize_type, Stream& s) { s >> *this;} uint256 proTxHash; - uint32_t nCollateralIndex; + COutPoint collateralOutpoint; uint16_t nOperatorReward; CDeterministicMNStateCPtr pdmnState; @@ -137,7 +130,7 @@ public: inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(proTxHash); - READWRITE(nCollateralIndex); + READWRITE(collateralOutpoint); READWRITE(nOperatorReward); READWRITE(pdmnState); } @@ -258,6 +251,8 @@ public: bool IsMNValid(const uint256& proTxHash) const; bool IsMNPoSeBanned(const uint256& proTxHash) const; + bool IsMNValid(const CDeterministicMNCPtr& dmn) const; + bool IsMNPoSeBanned(const CDeterministicMNCPtr& dmn) const; bool HasMN(const uint256& proTxHash) const { return GetMN(proTxHash) != nullptr; @@ -265,6 +260,7 @@ public: CDeterministicMNCPtr GetMN(const uint256& proTxHash) const; CDeterministicMNCPtr GetValidMN(const uint256& proTxHash) const; CDeterministicMNCPtr GetMNByOperatorKey(const CBLSPublicKey& pubKey); + CDeterministicMNCPtr GetMNByCollateral(const COutPoint& collateralOutpoint) const; CDeterministicMNCPtr GetMNPayee() const; /** @@ -298,9 +294,6 @@ public: } private: - bool IsMNValid(const CDeterministicMNCPtr& dmn) const; - bool IsMNPoSeBanned(const CDeterministicMNCPtr& dmn) const; - template void AddUniqueProperty(const CDeterministicMNCPtr& dmn, const T& v) { @@ -396,14 +389,16 @@ public: CDeterministicMNList GetListForBlock(const uint256& blockHash); CDeterministicMNList GetListAtChainTip(); - CDeterministicMNCPtr GetMN(const uint256& blockHash, const uint256& proTxHash); - bool HasValidMNAtBlock(const uint256& blockHash, const uint256& proTxHash); - bool HasValidMNAtChainTip(const uint256& proTxHash); + // TODO remove after removal of old non-deterministic lists + bool HasValidMNCollateralAtChainTip(const COutPoint& outpoint); + bool HasMNCollateralAtChainTip(const COutPoint& outpoint); + + // Test if given TX is a ProRegTx which also contains the collateral at index n + bool IsProTxWithCollateral(const CTransactionRef& tx, uint32_t n); bool IsDeterministicMNsSporkActive(int nHeight = -1); private: - void UpdateSpork15Value(); int64_t GetSpork15Value(); void CleanupCache(int nHeight); }; diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp index 03c99ce48..6b7463dd4 100644 --- a/src/evo/providertx.cpp +++ b/src/evo/providertx.cpp @@ -55,13 +55,22 @@ static bool CheckSig(const ProTx& proTx, const CBLSPublicKey& pubKey, CValidatio return true; } -template -static bool CheckInputsHashAndSig(const CTransaction &tx, const ProTx& proTx, const PubKey& pubKey, CValidationState& state) +template +static bool CheckInputsHash(const CTransaction &tx, const ProTx& proTx, CValidationState& state) { uint256 inputsHash = CalcTxInputsHash(tx); if (inputsHash != proTx.inputsHash) return state.DoS(100, false, REJECT_INVALID, "bad-protx-inputs-hash"); + return true; +} + +template +static bool CheckInputsHashAndSig(const CTransaction &tx, const ProTx& proTx, const PubKey& pubKey, CValidationState& state) +{ + if (!CheckInputsHash(tx, proTx, state)) + return false; + if (!CheckSig(proTx, pubKey, state)) return false; @@ -83,10 +92,18 @@ bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValid if (ptx.nMode != 0) return state.DoS(100, false, REJECT_INVALID, "bad-protx-mode"); - if (ptx.nCollateralIndex >= tx.vout.size()) - return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-index"); - if (tx.vout[ptx.nCollateralIndex].nValue != 1000 * COIN) - return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral"); + // when collateralOutpoint refers to an external collateral, we check it further down + if (ptx.collateralOutpoint.hash.IsNull()) { + if (ptx.collateralOutpoint.n >= tx.vout.size()) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-index"); + if (tx.vout[ptx.collateralOutpoint.n].nValue != 1000 * COIN) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral"); + + // This is a temporary restriction that will be lifted later + // It is required while we are transitioning from the old MN list to the deterministic list + if (tx.vout[ptx.collateralOutpoint.n].scriptPubKey != ptx.scriptPayout) + return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-collateral"); + } if (ptx.keyIDOwner.IsNull() || !ptx.pubKeyOperator.IsValid() || ptx.keyIDVoting.IsNull()) return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-null"); // we may support P2SH later, but restrict it for now (while in transitioning phase from old MN list to deterministic list) @@ -103,11 +120,6 @@ bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValid return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-reuse"); } - // This is a temporary restriction that will be lifted later - // It is required while we are transitioning from the old MN list to the deterministic list - if (tx.vout[ptx.nCollateralIndex].scriptPubKey != ptx.scriptPayout) - return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-collateral"); - // It's allowed to set addr/protocolVersion to 0, which will put the MN into PoSe-banned state and require a ProUpServTx to be issues later // If any of both is set, it must be valid however if (ptx.addr != CService() && !CheckService(tx.GetHash(), ptx, pindexPrev, state)) @@ -116,12 +128,38 @@ bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValid if (ptx.nOperatorReward > 10000) return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-reward"); + CKeyID keyForPayloadSig; + + if (!ptx.collateralOutpoint.hash.IsNull()) { + Coin coin; + if (!GetUTXOCoin(ptx.collateralOutpoint, coin) || coin.out.nValue != 1000 * COIN) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral"); + } + + CTxDestination txDest; + if (!ExtractDestination(coin.out.scriptPubKey, txDest)) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-dest"); + } + + // Extract key from collateral. This only works for P2PK and P2PKH collaterals and will fail for P2SH. + // Issuer of this ProRegTx must prove ownership with this key by signing the ProRegTx + CBitcoinAddress txAddr(txDest); + if (!txAddr.GetKeyID(keyForPayloadSig)) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-pkh"); + } + } + if (pindexPrev) { auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash()); if (mnList.HasUniqueProperty(ptx.keyIDOwner) || mnList.HasUniqueProperty(ptx.pubKeyOperator)) { return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-key"); } + // check for collaterals which are already used in other MNs + if (!ptx.collateralOutpoint.hash.IsNull() && mnList.HasUniqueProperty(ptx.collateralOutpoint)) { + return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-collateral"); + } + if (!deterministicMNManager->IsDeterministicMNsSporkActive(pindexPrev->nHeight)) { if (ptx.keyIDOwner != ptx.keyIDVoting) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-not-same"); @@ -129,8 +167,21 @@ bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValid } } - if (!CheckInputsHashAndSig(tx, ptx, ptx.keyIDOwner, state)) - return false; + if (!keyForPayloadSig.IsNull()) { + // collateral is not part of this ProRegTx, so we must verify ownership of the collateral + if (!CheckInputsHashAndSig(tx, ptx, keyForPayloadSig, state)) { + return false; + } + } else { + // collateral is part of this ProRegTx, so we know the collateral is owned by the issuer + if (!ptx.vchSig.empty()) { + return state.DoS(100, false, REJECT_INVALID, "bad-protx-sig"); + } + if (!CheckInputsHash(tx, ptx, state)) { + return false; + } + } + return true; } @@ -149,7 +200,8 @@ bool CheckProUpServTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVa return false; if (pindexPrev) { - auto mn = deterministicMNManager->GetMN(pindexPrev->GetBlockHash(), ptx.proTxHash); + auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash()); + auto mn = mnList.GetMN(ptx.proTxHash); if (!mn) return state.DoS(100, false, REJECT_INVALID, "bad-protx-hash"); @@ -210,7 +262,7 @@ bool CheckProUpRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVal // This is a temporary restriction that will be lifted later // It is required while we are transitioning from the old MN list to the deterministic list Coin coin; - if (!GetUTXOCoin(COutPoint(dmn->proTxHash, dmn->nCollateralIndex), coin) || coin.IsSpent()) { + if (!GetUTXOCoin(dmn->collateralOutpoint, coin)) { return state.DoS(100, false, REJECT_INVALID, "bad-protx-payee-collateral"); } if (coin.out.scriptPubKey != ptx.scriptPayout) @@ -273,8 +325,8 @@ std::string CProRegTx::ToString() const payee = CBitcoinAddress(dest).ToString(); } - return strprintf("CProRegTx(nVersion=%d, nCollateralIndex=%d, addr=%s, nOperatorReward=%f, keyIDOwner=%s, pubKeyOperator=%s, keyIDVoting=%s, scriptPayout=%s)", - nVersion, nCollateralIndex, addr.ToString(), (double)nOperatorReward / 100, keyIDOwner.ToString(), pubKeyOperator.ToString(), keyIDVoting.ToString(), payee); + return strprintf("CProRegTx(nVersion=%d, collateralOutpoint=%s, addr=%s, nOperatorReward=%f, keyIDOwner=%s, pubKeyOperator=%s, keyIDVoting=%s, scriptPayout=%s)", + nVersion, collateralOutpoint.ToStringShort(), addr.ToString(), (double)nOperatorReward / 100, keyIDOwner.ToString(), pubKeyOperator.ToString(), keyIDVoting.ToString(), payee); } void CProRegTx::ToJson(UniValue& obj) const @@ -282,7 +334,8 @@ void CProRegTx::ToJson(UniValue& obj) const obj.clear(); obj.setObject(); obj.push_back(Pair("version", nVersion)); - obj.push_back(Pair("collateralIndex", (int)nCollateralIndex)); + obj.push_back(Pair("collateralHash", collateralOutpoint.hash.ToString())); + obj.push_back(Pair("collateralIndex", (int)collateralOutpoint.n)); obj.push_back(Pair("service", addr.ToString(false))); obj.push_back(Pair("keyIDOwner", keyIDOwner.ToString())); obj.push_back(Pair("pubKeyOperator", pubKeyOperator.ToString())); @@ -368,18 +421,3 @@ void CProUpRevTx::ToJson(UniValue& obj) const obj.push_back(Pair("reason", (int)nReason)); obj.push_back(Pair("inputsHash", inputsHash.ToString())); } - -bool IsProTxCollateral(const CTransaction& tx, uint32_t n) -{ - return GetProTxCollateralIndex(tx) == n; -} - -uint32_t GetProTxCollateralIndex(const CTransaction& tx) -{ - if (tx.nVersion < 3 || tx.nType != TRANSACTION_PROVIDER_REGISTER) - return (uint32_t) - 1; - CProRegTx proTx; - if (!GetTxPayload(tx, proTx)) - assert(false); - return proTx.nCollateralIndex; -} diff --git a/src/evo/providertx.h b/src/evo/providertx.h index 46edd83b1..a0a451efc 100644 --- a/src/evo/providertx.h +++ b/src/evo/providertx.h @@ -24,7 +24,7 @@ public: uint16_t nVersion{CURRENT_VERSION}; // message version uint16_t nType{0}; // only 0 supported for now uint16_t nMode{0}; // only 0 supported for now - uint32_t nCollateralIndex{(uint32_t) - 1}; + COutPoint collateralOutpoint{uint256(), (uint32_t)-1}; // if hash is null, we refer to a ProRegTx output CService addr; CKeyID keyIDOwner; CBLSPublicKey pubKeyOperator; @@ -43,7 +43,7 @@ public: READWRITE(nVersion); READWRITE(nType); READWRITE(nMode); - READWRITE(nCollateralIndex); + READWRITE(collateralOutpoint); READWRITE(addr); READWRITE(keyIDOwner); READWRITE(pubKeyOperator); @@ -179,7 +179,4 @@ bool CheckProUpServTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVa bool CheckProUpRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state); bool CheckProUpRevTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state); -bool IsProTxCollateral(const CTransaction& tx, uint32_t n); -uint32_t GetProTxCollateralIndex(const CTransaction& tx); - #endif//DASH_PROVIDERTX_H diff --git a/src/init.cpp b/src/init.cpp index 86626e3dc..eaa8b2a69 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -810,6 +810,13 @@ void ThreadImport(std::vector vImportFiles) if (activeMasternodeManager && fDIP0003ActiveAtTip) activeMasternodeManager->Init(); +#ifdef ENABLE_WALLET + // we can't do this before DIP3 is fully initialized + if (pwalletMain) { + pwalletMain->AutoLockMasternodeCollaterals(); + } +#endif + LoadMempool(); fDumpMempoolLater = !fRequestShutdown; } @@ -1837,6 +1844,14 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) // ********************************************************* Step 9: data directory maintenance + if (!fLiteMode) { + uiInterface.InitMessage(_("Loading sporks cache...")); + CFlatDB flatdb6("sporks.dat", "magicSporkCache"); + if (!flatdb6.Load(sporkManager)) { + return InitError(_("Failed to load sporks cache from") + "\n" + (GetDataDir() / "sporks.dat").string()); + } + } + // if pruning, unset the service bit and perform the initial blockstore prune // after any wallet rescanning has taken place. if (fPruneMode) { @@ -2044,13 +2059,6 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) return InitError(_("Failed to load InstantSend data cache from") + "\n" + (pathDB / strDBName).string()); } } - - strDBName = "sporks.dat"; - uiInterface.InitMessage(_("Loading sporks cache...")); - CFlatDB flatdb6(strDBName, "magicSporkCache"); - if(!flatdb6.Load(sporkManager)) { - return InitError(_("Failed to load sporks cache from") + "\n" + (pathDB / strDBName).string()); - } } // ********************************************************* Step 11c: schedule Dash-specific tasks diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index fb1ad6141..5403110f9 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -627,7 +627,7 @@ bool CMasternodePayments::IsScheduled(const masternode_info_t& mnInfo, int nNotB if (deterministicMNManager->IsDeterministicMNsSporkActive()) { auto projectedPayees = deterministicMNManager->GetListAtChainTip().GetProjectedMNPayees(8); for (const auto &dmn : projectedPayees) { - if (dmn->proTxHash == mnInfo.outpoint.hash) { + if (dmn->collateralOutpoint == mnInfo.outpoint) { return true; } } diff --git a/src/masternode.cpp b/src/masternode.cpp index dc01830fa..57dfa9c09 100644 --- a/src/masternode.cpp +++ b/src/masternode.cpp @@ -56,7 +56,7 @@ CMasternode::CMasternode(const CMasternodeBroadcast& mnb) : CMasternode::CMasternode(const uint256 &proTxHash, const CDeterministicMNCPtr& dmn) : masternode_info_t{ MASTERNODE_ENABLED, DMN_PROTO_VERSION, GetAdjustedTime(), - COutPoint(proTxHash, dmn->nCollateralIndex), dmn->pdmnState->addr, CKeyID(), dmn->pdmnState->keyIDOwner, dmn->pdmnState->pubKeyOperator, dmn->pdmnState->keyIDVoting}, + dmn->collateralOutpoint, dmn->pdmnState->addr, CKeyID(), dmn->pdmnState->keyIDOwner, dmn->pdmnState->pubKeyOperator, dmn->pdmnState->keyIDVoting}, fAllowMixingTx(true) { CTxDestination dest; @@ -352,7 +352,7 @@ void CMasternode::UpdateLastPaid(const CBlockIndex *pindex, int nMaxBlocksToScan if(!pindex) return; if (deterministicMNManager->IsDeterministicMNsSporkActive(pindex->nHeight)) { - auto dmn = deterministicMNManager->GetListForBlock(pindex->GetBlockHash()).GetMN(outpoint.hash); + auto dmn = deterministicMNManager->GetListForBlock(pindex->GetBlockHash()).GetMNByCollateral(outpoint); if (!dmn || dmn->pdmnState->nLastPaidHeight == -1) { LogPrint("masternode", "CMasternode::UpdateLastPaidBlock -- searching for block with payment to %s -- not found\n", outpoint.ToStringShort()); } else { diff --git a/src/masternodeman.cpp b/src/masternodeman.cpp index f40e7634f..a79082386 100644 --- a/src/masternodeman.cpp +++ b/src/masternodeman.cpp @@ -383,7 +383,7 @@ void CMasternodeMan::AddDeterministicMasternodes() auto mnList = deterministicMNManager->GetListAtChainTip(); mnList.ForEachMN(true, [this](const CDeterministicMNCPtr& dmn) { // call Find() on each deterministic MN to force creation of CMasternode object - auto mn = Find(COutPoint(dmn->proTxHash, dmn->nCollateralIndex)); + auto mn = Find(dmn->collateralOutpoint); assert(mn); // make sure we use the splitted keys from now on @@ -416,7 +416,7 @@ void CMasternodeMan::RemoveNonDeterministicMasternodes() std::set mnSet; auto mnList = deterministicMNManager->GetListAtChainTip(); mnList.ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) { - mnSet.insert(COutPoint(dmn->proTxHash, dmn->nCollateralIndex)); + mnSet.insert(dmn->collateralOutpoint); }); auto it = mapMasternodes.begin(); while (it != mapMasternodes.end()) { @@ -539,11 +539,8 @@ CMasternode* CMasternodeMan::Find(const COutPoint &outpoint) // on-chain, like vote counts auto mnList = deterministicMNManager->GetListAtChainTip(); - if (!mnList.IsMNValid(outpoint.hash)) { - return nullptr; - } - auto dmn = mnList.GetMN(outpoint.hash); - if (!dmn) { + auto dmn = mnList.GetMNByCollateral(outpoint); + if (!dmn || !mnList.IsMNValid(dmn)) { return nullptr; } @@ -578,7 +575,7 @@ bool CMasternodeMan::GetMasternodeInfo(const uint256& proTxHash, masternode_info auto dmn = deterministicMNManager->GetListAtChainTip().GetValidMN(proTxHash); if (!dmn) return false; - return GetMasternodeInfo(COutPoint(proTxHash, dmn->nCollateralIndex), mnInfoRet); + return GetMasternodeInfo(dmn->collateralOutpoint, mnInfoRet); } bool CMasternodeMan::GetMasternodeInfo(const COutPoint& outpoint, masternode_info_t& mnInfoRet) @@ -626,7 +623,7 @@ bool CMasternodeMan::Has(const COutPoint& outpoint) { LOCK(cs); if (deterministicMNManager->IsDeterministicMNsSporkActive()) { - return deterministicMNManager->HasValidMNAtChainTip(outpoint.hash); + return deterministicMNManager->HasValidMNCollateralAtChainTip(outpoint); } else { return mapMasternodes.find(outpoint) != mapMasternodes.end(); } @@ -754,7 +751,7 @@ masternode_info_t CMasternodeMan::FindRandomNotInVec(const std::vectorIsDeterministicMNsSporkActive() && !deterministicMNManager->HasValidMNAtChainTip(pmn->outpoint.hash)) + if (deterministicMNManager->IsDeterministicMNsSporkActive() && !deterministicMNManager->HasValidMNCollateralAtChainTip(pmn->outpoint)) continue; // found the one not in vecToExclude LogPrint("masternode", "CMasternodeMan::FindRandomNotInVec -- found, masternode=%s\n", pmn->outpoint.ToStringShort()); @@ -771,8 +768,10 @@ std::map CMasternodeMan::GetFullMasternodeMap() if (deterministicMNManager->IsDeterministicMNsSporkActive()) { std::map result; + auto mnList = deterministicMNManager->GetListAtChainTip(); for (const auto &p : mapMasternodes) { - if (deterministicMNManager->HasValidMNAtChainTip(p.first.hash)) { + auto dmn = mnList.GetMNByCollateral(p.first); + if (dmn && mnList.IsMNValid(dmn)) { result.emplace(p.first, p.second); } } @@ -796,7 +795,7 @@ bool CMasternodeMan::GetMasternodeScores(const uint256& nBlockHash, CMasternodeM // calculate scores for (const auto& mnpair : mapMasternodes) { - if (deterministicMNManager->IsDeterministicMNsSporkActive() && !deterministicMNManager->HasValidMNAtChainTip(mnpair.second.outpoint.hash)) + if (deterministicMNManager->IsDeterministicMNsSporkActive() && !deterministicMNManager->HasValidMNCollateralAtChainTip(mnpair.second.outpoint)) continue; if (mnpair.second.nProtocolVersion >= nMinProtocol) { diff --git a/src/rpc/governance.cpp b/src/rpc/governance.cpp index f0375085c..f08a4767c 100644 --- a/src/rpc/governance.cpp +++ b/src/rpc/governance.cpp @@ -586,7 +586,7 @@ UniValue gobject_vote_many(const JSONRPCRequest& request) continue; } - if (nTxHash == dmn->proTxHash && (uint32_t)nOutputIndex == dmn->nCollateralIndex) { + if (nTxHash == dmn->collateralOutpoint.hash && (uint32_t)nOutputIndex == dmn->collateralOutpoint.n) { found = true; break; } @@ -595,7 +595,7 @@ UniValue gobject_vote_many(const JSONRPCRequest& request) CKey ownerKey; if (pwalletMain->GetKey(dmn->pdmnState->keyIDVoting, ownerKey)) { CBitcoinSecret secret(ownerKey); - CMasternodeConfig::CMasternodeEntry mne(dmn->proTxHash.ToString(), dmn->pdmnState->addr.ToStringIPPort(false), secret.ToString(), dmn->proTxHash.ToString(), itostr(dmn->nCollateralIndex)); + CMasternodeConfig::CMasternodeEntry mne(dmn->proTxHash.ToString(), dmn->pdmnState->addr.ToStringIPPort(false), secret.ToString(), dmn->collateralOutpoint.hash.ToString(), itostr(dmn->collateralOutpoint.n)); entries.push_back(mne); } } diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index ec700665c..ad2e2da1f 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -597,89 +597,6 @@ UniValue masternode_genkey(const JSONRPCRequest& request) return CBitcoinSecret(secret).ToString(); } -void masternode_info_help() -{ - throw std::runtime_error( - "masternode info \"proTxHash\"\n" - "Print masternode information of specified masternode\n" - "\nArguments:\n" - "1. proTxHash (string, required) proTxHash of masternode\n" - ); -} - -UniValue masternode_info(const JSONRPCRequest& request) -{ - if (request.fHelp || request.params.size() != 2) - masternode_info_help(); - - std::string strProTxHash = request.params[1].get_str(); - if (!IsHex(strProTxHash) || strProTxHash.size() != 64) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid \"proTxHash\""); - - uint256 proTxHash; - proTxHash.SetHex(strProTxHash); - - CTransactionRef tx; - uint256 hashBlock; - - auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(proTxHash); - if (!dmn) { - tx = mempool.get(proTxHash); - if (tx) { - if (tx->nVersion < 3 || tx->nType != TRANSACTION_PROVIDER_REGISTER) - throw JSONRPCError(RPC_INVALID_PARAMETER, "TX is not a ProTx"); - CProRegTx tmpProTx; - if (!GetTxPayload(*tx, tmpProTx)) - throw JSONRPCError(RPC_INVALID_PARAMETER, "TX is not a valid ProTx"); - dmn = std::make_shared(tx->GetHash(), tmpProTx); - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "ProTx not found"); - } - } else { - if (!GetTransaction(proTxHash, tx, Params().GetConsensus(), hashBlock, true)) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Parent transaction of ProTx not found"); - - if (!mapBlockIndex.count(hashBlock)) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Parent transaction of ProTx not found"); - } - - UniValue obj(UniValue::VOBJ); - - UniValue stateObj; - dmn->pdmnState->ToJson(stateObj); - obj.push_back(Pair("state", stateObj)); - - if (!hashBlock.IsNull()) { - UniValue blockObj(UniValue::VOBJ); - blockObj.push_back(Pair("blockhash", hashBlock.GetHex())); - - LOCK(cs_main); - BlockMap::iterator mi = mapBlockIndex.find(hashBlock); - if (mi != mapBlockIndex.end() && (*mi).second) { - CBlockIndex *pindex = (*mi).second; - if (chainActive.Contains(pindex)) { - blockObj.push_back(Pair("height", pindex->nHeight)); - blockObj.push_back(Pair("confirmations", 1 + chainActive.Height() - pindex->nHeight)); - blockObj.push_back(Pair("time", pindex->GetBlockTime())); - blockObj.push_back(Pair("blocktime", pindex->GetBlockTime())); - } else { - blockObj.push_back(Pair("height", -1)); - blockObj.push_back(Pair("confirmations", 0)); - } - } - obj.push_back(Pair("block", blockObj)); - - if (GetUTXOHeight(COutPoint(proTxHash, dmn->nCollateralIndex)) < 0) { - obj.push_back(Pair("isSpent", true)); - } - - } else { - obj.push_back(Pair("fromMempool", true)); - } - - return obj; -} - void masternode_list_conf_help() { throw std::runtime_error( @@ -741,7 +658,8 @@ UniValue masternode_status(const JSONRPCRequest& request) auto dmn = activeMasternodeManager->GetDMN(); if (dmn) { mnObj.push_back(Pair("proTxHash", dmn->proTxHash.ToString())); - mnObj.push_back(Pair("collateralIndex", (int)dmn->nCollateralIndex)); + mnObj.push_back(Pair("collateralHash", dmn->collateralOutpoint.hash.ToString())); + mnObj.push_back(Pair("collateralIndex", (int)dmn->collateralOutpoint.n)); UniValue stateObj; dmn->pdmnState->ToJson(stateObj); mnObj.push_back(Pair("dmnState", stateObj)); @@ -902,8 +820,6 @@ UniValue masternode(const JSONRPCRequest& request) #endif // ENABLE_WALLET } else if (strCommand == "genkey") { return masternode_genkey(request); - } else if (strCommand == "info") { - return masternode_info(request); } else if (strCommand == "list-conf") { return masternode_list_conf(request); #ifdef ENABLE_WALLET diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index 66657b4d5..00a04961e 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -116,9 +116,15 @@ static void FundSpecialTx(CMutableTransaction& tx, SpecialTxPayload payload) } template -static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKey& key) +static void UpdateSpecialTxInputsHash(const CMutableTransaction& tx, SpecialTxPayload& payload) { payload.inputsHash = CalcTxInputsHash(tx); +} + +template +static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKey& key) +{ + UpdateSpecialTxInputsHash(tx, payload); payload.vchSig.clear(); uint256 hash = ::SerializeHash(payload); @@ -130,7 +136,7 @@ static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload template static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload& payload, const CBLSSecretKey& key) { - payload.inputsHash = CalcTxInputsHash(tx); + UpdateSpecialTxInputsHash(tx, payload); uint256 hash = ::SerializeHash(payload); payload.sig = key.Sign(hash); @@ -139,8 +145,9 @@ static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload static std::string SignAndSendSpecialTx(const CMutableTransaction& tx) { LOCK(cs_main); + CValidationState state; - if (!CheckSpecialTx(tx, NULL, state)) + if (!CheckSpecialTx(tx, chainActive.Tip(), state)) throw std::runtime_error(FormatStateMessage(state)); CDataStream ds(SER_NETWORK, PROTOCOL_VERSION); @@ -157,11 +164,11 @@ static std::string SignAndSendSpecialTx(const CMutableTransaction& tx) return sendrawtransaction(sendRequest).get_str(); } -void protx_register_help() +void protx_fund_register_help() { throw std::runtime_error( - "protx register \"collateralAddress\" collateralAmount \"ipAndPort\" \"ownerKeyAddr\" \"operatorKeyAddr\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n" - "\nCreates and sends a ProTx to the network. The resulting transaction will move the specified amount\n" + "protx fund_register \"collateralAddress\" \"ipAndPort\" \"ownerKeyAddr\" \"operatorKeyAddr\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n" + "\nCreates, funds and sends a ProTx to the network. The resulting transaction will move 1000 Dash\n" "to the address specified by collateralAddress and will then function as the collateral of your\n" "masternode.\n" "A few of the limitations you see in the arguments are temporary and might be lifted after DIP3\n" @@ -169,94 +176,155 @@ void protx_register_help() "\nArguments:\n" "1. \"collateralAddress\" (string, required) The dash address to send the collateral to.\n" " Must be a P2PKH address.\n" - "2. \"collateralAmount\" (numeric or string, required) The collateral amount.\n" - " Must be exactly 1000 Dash.\n" - "3. \"ipAndPort\" (string, required) IP and port in the form \"IP:PORT\".\n" + "2. \"ipAndPort\" (string, required) IP and port in the form \"IP:PORT\".\n" " Must be unique on the network. Can be set to 0, which will require a ProUpServTx afterwards.\n" - "4. \"ownerKeyAddr\" (string, required) The owner key used for payee updates and proposal voting.\n" + "3. \"ownerKeyAddr\" (string, required) The owner key used for payee updates and proposal voting.\n" " The private key belonging to this address be known in your wallet. The address must\n" " be unused and must differ from the collateralAddress\n" - "5. \"operatorKeyAddr\" (string, required) The operator key address. The private key does not have to be known by your wallet.\n" + "4. \"operatorKeyAddr\" (string, required) The operator key address. The private key does not have to be known by your wallet.\n" " It has to match the private key which is later used when operating the masternode.\n" " If set to \"0\" or an empty string, ownerAddr will be used.\n" - "6. \"votingKeyAddr\" (string, required) The voting key address. The private key does not have to be known by your wallet.\n" + "5. \"votingKeyAddr\" (string, required) The voting key address. The private key does not have to be known by your wallet.\n" " It has to match the private key which is later used when voting on proposals.\n" " If set to \"0\" or an empty string, ownerAddr will be used.\n" - "7. \"operatorReward\" (numeric, required) The fraction in %% to share with the operator. If non-zero,\n" + "6. \"operatorReward\" (numeric, required) The fraction in % to share with the operator. If non-zero,\n" " \"ipAndPort\" must be zero as well. The value must be between 0 and 100.\n" - "8. \"payoutAddress\" (string, required) The dash address to use for masternode reward payments\n" + "7. \"payoutAddress\" (string, required) The dash address to use for masternode reward payments\n" " Must match \"collateralAddress\"." "\nExamples:\n" - + HelpExampleCli("protx", "register \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\" 1000 \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\"") + + HelpExampleCli("protx", "fund_register \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\" \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\"") ); } +void protx_register_help() +{ + throw std::runtime_error( + "protx register \"collateralHash\" collateralIndex \"ipAndPort\" \"ownerKeyAddr\" \"operatorKeyAddr\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n" + "\nSame as \"protx fund_register\", but with an externally referenced collateral.\n" + "The collateral is specified through \"collateralHash\" and \"collateralIndex\" and must be an unspent\n" + "transaction output. It must also not be used by any other masternode.\n" + "\nArguments:\n" + "1. \"collateralHash\" (string, required) The collateral transaction hash.\n" + "2. collateralIndex (numeric, required) The collateral transaction output index.\n" + "3., 4., 5. ... See help text of \"protx fund_register\"\n" + "\nExamples:\n" + + HelpExampleCli("protx", "register \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\"") + ); +} + +// handles register and fund_register in one method UniValue protx_register(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 9) + bool isFundRegister = request.params[0].get_str() == "fund_register"; + + if (isFundRegister && (request.fHelp || request.params.size() != 8)) { + protx_fund_register_help(); + } else if (!isFundRegister && (request.fHelp || request.params.size() != 9)) { protx_register_help(); + } - CBitcoinAddress collateralAddress(request.params[1].get_str()); - if (!collateralAddress.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid collaterall address: %s", request.params[1].get_str())); - CScript collateralScript = GetScriptForDestination(collateralAddress.Get()); + size_t paramIdx = 1; - CAmount collateralAmount; - if (!ParseMoney(request.params[2].get_str(), collateralAmount)) - throw std::runtime_error(strprintf("invalid collateral amount %s", request.params[2].get_str())); - if (collateralAmount != 1000 * COIN) - throw std::runtime_error(strprintf("invalid collateral amount %d. only 1000 DASH is supported at the moment", collateralAmount)); - - CTxOut collateralTxOut(collateralAmount, collateralScript); + CAmount collateralAmount = 1000 * COIN; CMutableTransaction tx; tx.nVersion = 3; tx.nType = TRANSACTION_PROVIDER_REGISTER; - tx.vout.emplace_back(collateralTxOut); CProRegTx ptx; ptx.nVersion = CProRegTx::CURRENT_VERSION; - if (request.params[3].get_str() != "0" && request.params[3].get_str() != "") { - if (!Lookup(request.params[3].get_str().c_str(), ptx.addr, Params().GetDefaultPort(), false)) - throw std::runtime_error(strprintf("invalid network address %s", request.params[3].get_str())); + if (isFundRegister) { + CBitcoinAddress collateralAddress(request.params[paramIdx].get_str()); + if (!collateralAddress.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid collaterall address: %s", request.params[paramIdx].get_str())); + CScript collateralScript = GetScriptForDestination(collateralAddress.Get()); + + CTxOut collateralTxOut(collateralAmount, collateralScript); + tx.vout.emplace_back(collateralTxOut); + + paramIdx++; + } else { + uint256 collateralHash = ParseHashV(request.params[paramIdx], "collateralHash"); + int32_t collateralIndex = ParseInt32V(request.params[paramIdx + 1], "collateralIndex"); + if (collateralHash.IsNull() || collateralIndex < 0) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid hash or index: %s-%d", collateralHash.ToString(), collateralIndex)); + } + + ptx.collateralOutpoint = COutPoint(collateralHash, (uint32_t)collateralIndex); + paramIdx += 2; + + // TODO unlock on failure + LOCK(pwalletMain->cs_wallet); + pwalletMain->LockCoin(ptx.collateralOutpoint); } - CKey keyOwner = ParsePrivKey(request.params[4].get_str(), true); - CBLSPublicKey pubKeyOperator = ParseBLSPubKey(request.params[5].get_str(), "operator BLS address"); + if (request.params[paramIdx].get_str() != "0" && request.params[paramIdx].get_str() != "") { + if (!Lookup(request.params[paramIdx].get_str().c_str(), ptx.addr, Params().GetDefaultPort(), false)) + throw std::runtime_error(strprintf("invalid network address %s", request.params[paramIdx].get_str())); + } + + CKey keyOwner = ParsePrivKey(request.params[paramIdx + 1].get_str(), true); + CBLSPublicKey pubKeyOperator = ParseBLSPubKey(request.params[paramIdx + 2].get_str(), "operator BLS address"); CKeyID keyIDVoting = keyOwner.GetPubKey().GetID(); - if (request.params[6].get_str() != "0" && request.params[6].get_str() != "") { - keyIDVoting = ParsePubKeyIDFromAddress(request.params[6].get_str(), "voting address"); + if (request.params[paramIdx + 3].get_str() != "0" && request.params[paramIdx + 3].get_str() != "") { + keyIDVoting = ParsePubKeyIDFromAddress(request.params[paramIdx + 3].get_str(), "voting address"); } - double operatorReward = ParseDoubleV(request.params[7], "operatorReward"); + double operatorReward = ParseDoubleV(request.params[paramIdx + 4], "operatorReward"); if (operatorReward < 0 || operatorReward > 100) throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorReward must be between 0 and 100"); ptx.nOperatorReward = (uint16_t)(operatorReward * 100); - CBitcoinAddress payoutAddress(request.params[8].get_str()); + CBitcoinAddress payoutAddress(request.params[paramIdx + 5].get_str()); if (!payoutAddress.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[8].get_str())); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[paramIdx + 5].get_str())); ptx.keyIDOwner = keyOwner.GetPubKey().GetID(); ptx.pubKeyOperator = pubKeyOperator; ptx.keyIDVoting = keyIDVoting; ptx.scriptPayout = GetScriptForDestination(payoutAddress.Get()); - SignSpecialTxPayload(tx, ptx, keyOwner); // make sure sig is set, otherwise fee calculation won't be correct + if (!isFundRegister) { + // make sure fee calculation works + ptx.vchSig.resize(65); + } + + UpdateSpecialTxInputsHash(tx, ptx); FundSpecialTx(tx, ptx); - uint32_t collateralIndex = (uint32_t) - 1; - for (uint32_t i = 0; i < tx.vout.size(); i++) { - if (tx.vout[i] == collateralTxOut) { - collateralIndex = i; - break; + if (isFundRegister) { + uint32_t collateralIndex = (uint32_t) -1; + for (uint32_t i = 0; i < tx.vout.size(); i++) { + if (tx.vout[i].nValue == collateralAmount) { + collateralIndex = i; + break; + } } + assert(collateralIndex != (uint32_t) -1); + ptx.collateralOutpoint.n = collateralIndex; } - assert(collateralIndex != (uint32_t) - 1); - ptx.nCollateralIndex = collateralIndex; - SignSpecialTxPayload(tx, ptx, keyOwner); // redo signing + if (isFundRegister) { + UpdateSpecialTxInputsHash(tx, ptx); + } else { + // referencing external collateral, so lets prove we own it + + Coin coin; + if (!GetUTXOCoin(ptx.collateralOutpoint, coin)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("collateral not found: %s", ptx.collateralOutpoint.ToStringShort())); + } + CTxDestination txDest; + CKeyID keyID; + if (!ExtractDestination(coin.out.scriptPubKey, txDest) || !CBitcoinAddress(txDest).GetKeyID(keyID)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("collateral type not supported: %s", ptx.collateralOutpoint.ToStringShort())); + } + CKey key; + if (!pwalletMain->GetKey(keyID, key)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("collateral key not in wallet: %s", CBitcoinAddress(keyID).ToString())); + } + SignSpecialTxPayload(tx, ptx, key); + } SetTxPayload(tx, ptx); return SignAndSendSpecialTx(tx); @@ -487,7 +555,7 @@ UniValue BuildDMNListEntry(const CDeterministicMNCPtr& dmn, bool detailed) dmn->ToJson(o); - int confirmations = GetUTXOConfirmations(COutPoint(dmn->proTxHash, dmn->nCollateralIndex)); + int confirmations = GetUTXOConfirmations(dmn->collateralOutpoint); o.push_back(Pair("confirmations", confirmations)); bool hasOwnerKey = pwalletMain->HaveKey(dmn->pdmnState->keyIDOwner); @@ -497,8 +565,8 @@ UniValue BuildDMNListEntry(const CDeterministicMNCPtr& dmn, bool detailed) bool ownsCollateral = false; CTransactionRef collateralTx; uint256 tmpHashBlock; - if (GetTransaction(dmn->proTxHash, collateralTx, Params().GetConsensus(), tmpHashBlock)) { - ownsCollateral = CheckWalletOwnsScript(collateralTx->vout[dmn->nCollateralIndex].scriptPubKey); + if (GetTransaction(dmn->collateralOutpoint.hash, collateralTx, Params().GetConsensus(), tmpHashBlock)) { + ownsCollateral = CheckWalletOwnsScript(collateralTx->vout[dmn->collateralOutpoint.n].scriptPubKey); } UniValue walletObj(UniValue::VOBJ); @@ -534,13 +602,13 @@ UniValue protx_list(const JSONRPCRequest& request) std::vector vOutpts; pwalletMain->ListProTxCoins(vOutpts); - std::set setOutpts; + std::set setOutpts; for (const auto& outpt : vOutpts) { - setOutpts.emplace(outpt.hash); + setOutpts.emplace(outpt); } deterministicMNManager->GetListAtChainTip().ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) { - if (setOutpts.count(dmn->proTxHash) || + if (setOutpts.count(dmn->collateralOutpoint) || pwalletMain->HaveKey(dmn->pdmnState->keyIDOwner) || pwalletMain->HaveKey(dmn->pdmnState->keyIDVoting) || CheckWalletOwnsScript(dmn->pdmnState->scriptPayout) || @@ -572,6 +640,30 @@ UniValue protx_list(const JSONRPCRequest& request) return ret; } +void protx_info_help() +{ + throw std::runtime_error( + "protx info \"proTxHash\"\n" + "\nReturns detailed information about a deterministic masternode.\n" + "\nArguments:\n" + "1. \"proTxHash\" (string, required) The hash of the initial ProRegTx.\n" + ); +} + +UniValue protx_info(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 2) + protx_info_help(); + + uint256 proTxHash = ParseHashV(request.params[1], "proTxHash"); + auto mnList = deterministicMNManager->GetListAtChainTip(); + auto dmn = mnList.GetMN(proTxHash); + if (!dmn) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s not found", proTxHash.ToString())); + } + return BuildDMNListEntry(dmn, true); +} + void protx_diff_help() { throw std::runtime_error( @@ -638,6 +730,7 @@ UniValue protx(const JSONRPCRequest& request) "\nAvailable commands:\n" " register - Create and send ProTx to network\n" " list - List ProTxs\n" + " info - Return information about a ProTx\n" " update_service - Create and send ProUpServTx to network\n" " update_registrar - Create and send ProUpRegTx to network\n" " revoke - Create and send ProUpRevTx to network\n" @@ -647,10 +740,12 @@ UniValue protx(const JSONRPCRequest& request) std::string command = request.params[0].get_str(); - if (command == "register") { + if (command == "fund_register" || command == "register") { return protx_register(request); } else if (command == "list") { return protx_list(request); + } else if (command == "info") { + return protx_info(request); } else if (command == "update_service") { return protx_update_service(request); } else if (command == "update_registrar") { diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 33d775e6b..ca0eb0bcd 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -103,7 +103,7 @@ static CMutableTransaction CreateProRegTx(SimpleUTXOMap& utxos, int port, const auto inputs = SelectUTXOs(utxos, 1000 * COIN, change); CProRegTx proTx; - proTx.nCollateralIndex = 0; + proTx.collateralOutpoint.n = 0; proTx.addr = LookupNumeric("1.1.1.1", port); proTx.keyIDOwner = ownerKeyRet.GetPubKey().GetID(); proTx.pubKeyOperator = operatorKeyRet.GetPublicKey(); @@ -115,7 +115,6 @@ static CMutableTransaction CreateProRegTx(SimpleUTXOMap& utxos, int port, const tx.nType = TRANSACTION_PROVIDER_REGISTER; FundTransaction(tx, utxos, scriptPayout, 1000 * COIN, coinbaseKey); proTx.inputsHash = CalcTxInputsHash(tx); - CHashSigner::SignHash(::SerializeHash(proTx), ownerKeyRet, proTx.vchSig); SetTxPayload(tx, proTx); SignTransaction(tx, coinbaseKey); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index f27cb704a..9021314c4 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -449,6 +449,9 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, mapProTxAddresses.emplace(proTx.addr, tx.GetHash()); mapProTxPubKeyIDs.emplace(proTx.keyIDOwner, tx.GetHash()); mapProTxBlsPubKeyHashes.emplace(proTx.pubKeyOperator.GetHash(), tx.GetHash()); + if (!proTx.collateralOutpoint.hash.IsNull()) { + mapProTxCollaterals.emplace(proTx.collateralOutpoint, tx.GetHash()); + } } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) { CProUpServTx proTx; if (!GetTxPayload(tx, proTx)) { @@ -646,6 +649,7 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) mapProTxAddresses.erase(proTx.addr); mapProTxPubKeyIDs.erase(proTx.keyIDOwner); mapProTxBlsPubKeyHashes.erase(proTx.pubKeyOperator.GetHash()); + mapProTxCollaterals.erase(proTx.collateralOutpoint); } else if (it->GetTx().nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) { CProUpServTx proTx; if (!GetTxPayload(it->GetTx(), proTx)) { @@ -806,6 +810,16 @@ void CTxMemPool::removeProTxPubKeyConflicts(const CTransaction &tx, const CBLSPu } } +void CTxMemPool::removeProTxCollateralConflicts(const CTransaction &tx, const COutPoint &collateralOutpoint) +{ + if (mapProTxCollaterals.count(collateralOutpoint)) { + uint256 conflictHash = mapProTxCollaterals[collateralOutpoint]; + if (conflictHash != tx.GetHash() && mapTx.count(conflictHash)) { + removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT); + } + } +} + void CTxMemPool::removeProTxConflicts(const CTransaction &tx) { if (tx.nType == TRANSACTION_PROVIDER_REGISTER) { @@ -822,6 +836,9 @@ void CTxMemPool::removeProTxConflicts(const CTransaction &tx) } removeProTxPubKeyConflicts(tx, proTx.keyIDOwner); removeProTxPubKeyConflicts(tx, proTx.pubKeyOperator); + if (!proTx.collateralOutpoint.hash.IsNull()) { + removeProTxCollateralConflicts(tx, proTx.collateralOutpoint); + } } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) { CProUpServTx proTx; if (!GetTxPayload(tx, proTx)) { @@ -1127,7 +1144,11 @@ bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const { CProRegTx proTx; if (!GetTxPayload(tx, proTx)) assert(false); - return mapProTxAddresses.count(proTx.addr) || mapProTxPubKeyIDs.count(proTx.keyIDOwner) || mapProTxBlsPubKeyHashes.count(proTx.pubKeyOperator.GetHash()); + if (mapProTxAddresses.count(proTx.addr) || mapProTxPubKeyIDs.count(proTx.keyIDOwner) || mapProTxBlsPubKeyHashes.count(proTx.pubKeyOperator.GetHash())) + return true; + if (!proTx.collateralOutpoint.hash.IsNull() && mapProTxCollaterals.count(proTx.collateralOutpoint)) + return true; + return false; } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) { CProUpServTx proTx; if (!GetTxPayload(tx, proTx)) diff --git a/src/txmempool.h b/src/txmempool.h index fa5eb1354..81ceedc53 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -537,6 +537,7 @@ private: std::map mapProTxAddresses; std::map mapProTxPubKeyIDs; std::map mapProTxBlsPubKeyHashes; + std::map mapProTxCollaterals; void UpdateParent(txiter entry, txiter parent, bool add); void UpdateChild(txiter entry, txiter child, bool add); @@ -582,6 +583,7 @@ public: void removeConflicts(const CTransaction &tx); void removeProTxPubKeyConflicts(const CTransaction &tx, const CKeyID &keyId); void removeProTxPubKeyConflicts(const CTransaction &tx, const CBLSPublicKey &pubKey); + void removeProTxCollateralConflicts(const CTransaction &tx, const COutPoint &collateralOutpoint); void removeProTxConflicts(const CTransaction &tx); void removeForBlock(const std::vector& vtx, unsigned int nBlockHeight); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 27bfea7d9..f11e6b9fc 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1119,11 +1119,10 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) } AddToSpends(hash); - uint32_t proTxCollateralIdx = GetProTxCollateralIndex(*wtx.tx); for(unsigned int i = 0; i < wtx.tx->vout.size(); ++i) { if (IsMine(wtx.tx->vout[i]) && !IsSpent(hash, i)) { setWalletUTXO.insert(COutPoint(hash, i)); - if (i == proTxCollateralIdx) { + if (deterministicMNManager->IsProTxWithCollateral(wtx.tx, i) || deterministicMNManager->HasMNCollateralAtChainTip(COutPoint(hash, i))) { LockCoin(COutPoint(hash, i)); } } @@ -3967,13 +3966,9 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { LOCK2(cs_main, cs_wallet); for (auto& pair : mapWallet) { - uint32_t proTxCollateralIdx = GetProTxCollateralIndex(*pair.second.tx); for(unsigned int i = 0; i < pair.second.tx->vout.size(); ++i) { if (IsMine(pair.second.tx->vout[i]) && !IsSpent(pair.first, i)) { setWalletUTXO.insert(COutPoint(pair.first, i)); - if (i == proTxCollateralIdx) { - LockCoin(COutPoint(pair.first, i)); - } } } } @@ -3988,6 +3983,22 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) return DB_LOAD_OK; } +// Goes through all wallet transactions and checks if they are masternode collaterals, in which case these are locked +// This avoids accidential spending of collaterals. They can still be unlocked manually if a spend is really intended. +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]) && !IsSpent(pair.first, i)) { + if (deterministicMNManager->IsProTxWithCollateral(pair.second.tx, i) || deterministicMNManager->HasMNCollateralAtChainTip(COutPoint(pair.first, i))) { + LockCoin(COutPoint(pair.first, i)); + } + } + } + } +} + DBErrors CWallet::ZapSelectTx(std::vector& vHashIn, std::vector& vHashOut) { if (!fFileBacked) @@ -4634,7 +4645,7 @@ void CWallet::ListProTxCoins(std::vector& vOutpts) for (const auto &o : setWalletUTXO) { if (mapWallet.count(o.hash)) { const auto &p = mapWallet[o.hash]; - if (IsProTxCollateral(*p.tx, o.n)) { + if (deterministicMNManager->IsProTxWithCollateral(p.tx, o.n) || deterministicMNManager->HasMNCollateralAtChainTip(o)) { vOutpts.emplace_back(o); } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index fad981547..67feaa891 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1004,6 +1004,7 @@ public: void SetBestChain(const CBlockLocator& loc) override; DBErrors LoadWallet(bool& fFirstRunRet); + void AutoLockMasternodeCollaterals(); DBErrors ZapWalletTx(std::vector& vWtx); DBErrors ZapSelectTx(std::vector& vHashIn, std::vector& vHashOut);