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 <ablock84@gmail.com>
This commit is contained in:
parent
0ad2906c54
commit
e3df910822
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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<CDeterministicMN>(tx.GetHash(), proTx);
|
||||
auto dmn = std::make_shared<CDeterministicMN>();
|
||||
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<CDeterministicMNState>(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);
|
||||
|
@ -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<CDeterministicMNState>(_proTx);
|
||||
}
|
||||
template<typename Stream>
|
||||
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<typename T>
|
||||
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);
|
||||
};
|
||||
|
@ -55,13 +55,22 @@ static bool CheckSig(const ProTx& proTx, const CBLSPublicKey& pubKey, CValidatio
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename ProTx, typename PubKey>
|
||||
static bool CheckInputsHashAndSig(const CTransaction &tx, const ProTx& proTx, const PubKey& pubKey, CValidationState& state)
|
||||
template <typename ProTx>
|
||||
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 <typename ProTx, typename PubKey>
|
||||
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;
|
||||
}
|
||||
|
@ -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
|
||||
|
22
src/init.cpp
22
src/init.cpp
@ -810,6 +810,13 @@ void ThreadImport(std::vector<boost::filesystem::path> 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<CSporkManager> 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<CSporkManager> 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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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<COutPoint> 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::vector<COutPoint
|
||||
}
|
||||
}
|
||||
if(fExclude) continue;
|
||||
if (deterministicMNManager->IsDeterministicMNsSporkActive() && !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<COutPoint, CMasternode> CMasternodeMan::GetFullMasternodeMap()
|
||||
|
||||
if (deterministicMNManager->IsDeterministicMNsSporkActive()) {
|
||||
std::map<COutPoint, CMasternode> 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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<CDeterministicMN>(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
|
||||
|
@ -116,9 +116,15 @@ static void FundSpecialTx(CMutableTransaction& tx, SpecialTxPayload payload)
|
||||
}
|
||||
|
||||
template<typename SpecialTxPayload>
|
||||
static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKey& key)
|
||||
static void UpdateSpecialTxInputsHash(const CMutableTransaction& tx, SpecialTxPayload& payload)
|
||||
{
|
||||
payload.inputsHash = CalcTxInputsHash(tx);
|
||||
}
|
||||
|
||||
template<typename SpecialTxPayload>
|
||||
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<typename SpecialTxPayload>
|
||||
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<COutPoint> vOutpts;
|
||||
pwalletMain->ListProTxCoins(vOutpts);
|
||||
std::set<uint256> setOutpts;
|
||||
std::set<COutPoint> 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") {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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))
|
||||
|
@ -537,6 +537,7 @@ private:
|
||||
std::map<CService, uint256> mapProTxAddresses;
|
||||
std::map<CKeyID, uint256> mapProTxPubKeyIDs;
|
||||
std::map<uint256, uint256> mapProTxBlsPubKeyHashes;
|
||||
std::map<COutPoint, uint256> 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<CTransactionRef>& vtx, unsigned int nBlockHeight);
|
||||
|
||||
|
@ -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<uint256>& vHashIn, std::vector<uint256>& vHashOut)
|
||||
{
|
||||
if (!fFileBacked)
|
||||
@ -4634,7 +4645,7 @@ void CWallet::ListProTxCoins(std::vector<COutPoint>& 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);
|
||||
}
|
||||
}
|
||||
|
@ -1004,6 +1004,7 @@ public:
|
||||
void SetBestChain(const CBlockLocator& loc) override;
|
||||
|
||||
DBErrors LoadWallet(bool& fFirstRunRet);
|
||||
void AutoLockMasternodeCollaterals();
|
||||
DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx);
|
||||
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user