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:
Alexander Block 2018-10-25 16:29:50 +02:00 committed by UdjinM6
parent 0ad2906c54
commit e3df910822
18 changed files with 465 additions and 317 deletions

View File

@ -151,7 +151,7 @@ class DIP3Test(BitcoinTestFramework):
self.force_finish_mnsync_list(before_dip3_mn.node) self.force_finish_mnsync_list(before_dip3_mn.node)
self.start_alias(self.nodes[0], before_dip3_mn.alias) 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() self.wait_for_mnlists_same()
# Test if nodes deny creating new non-ProTx MNs now # 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) self.start_alias(self.nodes[0], after_dip3_mn.alias, should_fail=True)
first_upgrade_count = 5 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) print("upgrading first %d MNs to use ProTx (but not deterministic MN lists)" % first_upgrade_count)
for i in range(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) 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) self.write_mnconf(mns)
print("wait for upgraded MNs to disappear from MN lists (their collateral was spent)") print("wait for freshly funded and upgraded MNs to disappear from MN lists (their collateral was spent)")
self.wait_for_mnlists(mns, True, False, check=True) self.wait_for_mnlists(mns_after_upgrade, check=True)
self.wait_for_mnlists_same() self.wait_for_mnlists_same()
print("restarting controller and upgraded MNs") print("restarting controller and upgraded MNs")
self.restart_controller_node() self.restart_controller_node()
self.force_finish_mnsync_list(self.nodes[0]) self.force_finish_mnsync_list(self.nodes[0])
for mn in mns: for mn in mns_to_restart:
if mn.is_protx:
print("restarting MN %s" % mn.alias) print("restarting MN %s" % mn.alias)
self.stop_node(mn.idx) self.stop_node(mn.idx)
self.start_mn(mn) self.start_mn(mn)
self.force_finish_mnsync_list(mn.node) self.force_finish_mnsync_list(mn.node)
print('start-alias on upgraded nodes') print('start-alias on upgraded nodes')
for mn in mns: for mn in mns_to_restart:
if mn.is_protx:
self.start_alias(self.nodes[0], mn.alias) self.start_alias(self.nodes[0], mn.alias)
print("wait for upgraded MNs to appear in MN list") 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() self.wait_for_mnlists_same()
print("testing MN payment votes (with mixed ProTx and legacy nodes)") 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): for i in range(spork15_offset - 1):
self.nodes[0].generate(1) self.nodes[0].generate(1)
self.sync_all() self.sync_all()
self.wait_for_mnlists(mns, True, True) self.wait_for_mnlists(mns)
self.wait_for_mnlists_same() self.wait_for_mnlists_same()
print("mining final block which should switch network to deterministic lists") 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 ##### 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") 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 # enable enforcement and keep it on from now on
self.nodes[0].spork('SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT', 0) 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") print("test that MNs disappear from the list when the ProTx collateral is spent")
spend_mns_count = 3 spend_mns_count = 3
mns_tmp = [] + mns mns_tmp = [] + mns_protx
dummy_txins = [] dummy_txins = []
for i in range(spend_mns_count): 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) dummy_txins.append(dummy_txin)
self.nodes[0].generate(1) self.nodes[0].generate(1)
self.sync_all() self.sync_all()
mns_tmp.remove(mns[i]) mns_tmp.remove(mns_protx[i])
self.assert_mnlists(mns_tmp, False, True) self.assert_mnlists(mns_tmp)
print("test that reverting the blockchain on a single node results in the mnlist to be reverted as well") 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): for i in range(spend_mns_count):
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
mns_tmp.append(mns[spend_mns_count - 1 - i]) mns_tmp.append(mns_protx[spend_mns_count - 1 - i])
self.assert_mnlist(self.nodes[0], mns_tmp, False, True) 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") 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.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.nodes[0].generate(spend_mns_count)
self.sync_all() self.sync_all()
self.assert_mnlists(mns_tmp, False, True) self.assert_mnlists(mns_tmp)
print("upgrade remaining MNs to ProTx") print("upgrade remaining MNs to ProTx")
for i in range(first_upgrade_count, len(mns)): 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] mn = mns[i]
self.nodes[0].generate(1) self.nodes[0].generate(1)
mns_protx.append(mn)
print("restarting MN %s" % mn.alias) print("restarting MN %s" % mn.alias)
self.stop_node(mn.idx) self.stop_node(mn.idx)
self.start_mn(mn) self.start_mn(mn)
self.sync_all() self.sync_all()
self.force_finish_mnsync(mn.node) 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") print("test mn payment enforcement with deterministic MNs")
for i in range(20): for i in range(20):
@ -269,7 +283,7 @@ class DIP3Test(BitcoinTestFramework):
self.test_instantsend(20, 5) self.test_instantsend(20, 5)
print("testing ProUpServTx") print("testing ProUpServTx")
for mn in mns: for mn in mns_protx:
self.test_protx_update_service(mn) self.test_protx_update_service(mn)
def create_mn(self, node, idx, alias): def create_mn(self, node, idx, alias):
@ -297,7 +311,7 @@ class DIP3Test(BitcoinTestFramework):
return mn return mn
def create_mn_protx(self, node, idx, alias): def create_mn_protx_base(self, node, idx, alias):
mn = Masternode() mn = Masternode()
mn.idx = idx mn.idx = idx
mn.alias = alias mn.alias = alias
@ -310,12 +324,19 @@ class DIP3Test(BitcoinTestFramework):
mn.votingAddr = mn.ownerAddr mn.votingAddr = mn.ownerAddr
mn.legacyMnkey = node.masternode('genkey') mn.legacyMnkey = node.masternode('genkey')
mn.blsMnkey = blsKey['secret'] 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_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) 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)
rawtx = node.getrawtransaction(mn.collateral_txid, 1) mn.collateral_txid = mn.protx_hash
mn.collateral_vout = -1 mn.collateral_vout = -1
rawtx = node.getrawtransaction(mn.collateral_txid, 1)
for txout in rawtx['vout']: for txout in rawtx['vout']:
if txout['value'] == Decimal(1000): if txout['value'] == Decimal(1000):
mn.collateral_vout = txout['n'] mn.collateral_vout = txout['n']
@ -324,6 +345,17 @@ class DIP3Test(BitcoinTestFramework):
return mn 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): def start_mn(self, mn):
while len(self.nodes) <= mn.idx: while len(self.nodes) <= mn.idx:
self.nodes.append(None) self.nodes.append(None)
@ -339,23 +371,26 @@ class DIP3Test(BitcoinTestFramework):
def spend_mn_collateral(self, mn, with_dummy_input_output=False): 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) return self.spend_input(mn.collateral_txid, mn.collateral_vout, 1000, with_dummy_input_output)
def upgrade_mn_protx(self, mn): def upgrade_mn_protx(self, mn, refund):
if refund:
self.spend_mn_collateral(mn) self.spend_mn_collateral(mn)
mn = self.create_mn_protx(self.nodes[0], mn.idx, 'mn-protx-%d' % mn.idx) 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 return mn
def test_protx_update_service(self, 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.nodes[0].generate(1)
self.sync_all() self.sync_all()
for node in self.nodes: 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') 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) assert_equal(mn_list['%s-%d' % (mn.collateral_txid, mn.collateral_vout)]['address'], '127.0.0.2:%d' % mn.p2p_port)
# undo # 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) self.nodes[0].generate(1)
def force_finish_mnsync(self, node): def force_finish_mnsync(self, node):
@ -510,29 +545,28 @@ class DIP3Test(BitcoinTestFramework):
time.sleep(0.5) time.sleep(0.5)
raise AssertionError("wait_for_winners for height {} timed out: {}".format(height, node.masternode('winners'))) 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: 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() st = time.time()
while time.time() < st + timeout: while time.time() < st + timeout:
if check: if check:
node.masternode('check') node.masternode('check')
if self.compare_mnlist(node, mns, include_legacy, include_protx): if self.compare_mnlist(node, mns):
return return
time.sleep(0.5) time.sleep(0.5)
raise AssertionError("wait_for_mnlist timed out") 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: 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): def assert_mnlist(self, node, mns):
if not self.compare_mnlist(node, mns, include_legacy, include_protx): if not self.compare_mnlist(node, mns):
expected = [] expected = []
for mn in mns: 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('mnlist: ' + str(node.masternode('list', 'status')))
print('expected: ' + str(expected)) print('expected: ' + str(expected))
@ -554,26 +588,13 @@ class DIP3Test(BitcoinTestFramework):
return False return False
return True return True
def compare_mnlist(self, node, mns, include_legacy, include_protx): def compare_mnlist(self, node, mns):
mnlist = node.masternode('list', 'status') mnlist = node.masternode('list', 'status')
for mn in mns: for mn in mns:
s = '%s-%d' % (mn.collateral_txid, mn.collateral_vout) s = '%s-%d' % (mn.collateral_txid, mn.collateral_vout)
in_list = s in mnlist in_list = s in mnlist
if mn.is_protx:
if include_protx:
if not in_list: if not in_list:
return False return False
else:
if in_list:
return False
else:
if include_legacy:
if not in_list:
return False
else:
if in_list:
return False
mnlist.pop(s, None) mnlist.pop(s, None)
if len(mnlist) != 0: if len(mnlist) != 0:
return False return False
@ -599,13 +620,13 @@ class DIP3Test(BitcoinTestFramework):
address = node.getnewaddress() address = node.getnewaddress()
key = node.getnewaddress() key = node.getnewaddress()
blsKey = node.bls('generate') 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): def test_success_create_protx(self, node):
address = node.getnewaddress() address = node.getnewaddress()
key = node.getnewaddress() key = node.getnewaddress()
blsKey = node.bls('generate') 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) rawtx = node.getrawtransaction(txid, 1)
self.mine_double_spend(node, rawtx['vin'], address, use_mnmerkleroot_from_tip=True) self.mine_double_spend(node, rawtx['vin'], address, use_mnmerkleroot_from_tip=True)
self.sync_all() self.sync_all()

View File

@ -85,7 +85,7 @@ void CActiveDeterministicMasternodeManager::Init()
return; return;
} }
activeMasternodeInfo.outpoint = COutPoint(mnListEntry->proTxHash, mnListEntry->nCollateralIndex); activeMasternodeInfo.outpoint = mnListEntry->collateralOutpoint;
state = MASTERNODE_READY; state = MASTERNODE_READY;
} }
@ -103,7 +103,8 @@ void CActiveDeterministicMasternodeManager::UpdatedBlockTip(const CBlockIndex* p
if (state == MASTERNODE_WAITING_FOR_PROTX) { if (state == MASTERNODE_WAITING_FOR_PROTX) {
Init(); Init();
} else if (state == MASTERNODE_READY) { } 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 // MN disappeared from MN list
state = MASTERNODE_REMOVED; state = MASTERNODE_REMOVED;
activeMasternodeInfo.outpoint.SetNull(); activeMasternodeInfo.outpoint.SetNull();
@ -367,7 +368,7 @@ void CActiveLegacyMasternodeManager::ManageStateRemote()
LogPrintf("CActiveLegacyMasternodeManager::ManageStateRemote -- %s: %s\n", GetStateString(), strNotCapableReason); LogPrintf("CActiveLegacyMasternodeManager::ManageStateRemote -- %s: %s\n", GetStateString(), strNotCapableReason);
return; return;
} }
auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(infoMn.outpoint.hash); auto dmn = deterministicMNManager->GetListAtChainTip().GetMNByCollateral(infoMn.outpoint);
if (dmn) { if (dmn) {
if (dmn->pdmnState->addr != infoMn.addr) { if (dmn->pdmnState->addr != infoMn.addr) {
nState = ACTIVE_MASTERNODE_NOT_CAPABLE; nState = ACTIVE_MASTERNODE_NOT_CAPABLE;

View File

@ -66,7 +66,7 @@ void CDeterministicMNState::ToJson(UniValue& obj) const
std::string CDeterministicMN::ToString() 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 void CDeterministicMN::ToJson(UniValue& obj) const
@ -78,7 +78,8 @@ void CDeterministicMN::ToJson(UniValue& obj) const
pdmnState->ToJson(stateObj); pdmnState->ToJson(stateObj);
obj.push_back(Pair("proTxHash", proTxHash.ToString())); 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("operatorReward", (double)nOperatorReward / 100));
obj.push_back(Pair("state", stateObj)); obj.push_back(Pair("state", stateObj));
} }
@ -141,6 +142,11 @@ CDeterministicMNCPtr CDeterministicMNList::GetMNByOperatorKey(const CBLSPublicKe
return nullptr; return nullptr;
} }
CDeterministicMNCPtr CDeterministicMNList::GetMNByCollateral(const COutPoint& collateralOutpoint) const
{
return GetUniquePropertyMN(collateralOutpoint);
}
static int CompareByLastPaid_GetHeight(const CDeterministicMN &dmn) static int CompareByLastPaid_GetHeight(const CDeterministicMN &dmn)
{ {
int h = dmn.pdmnState->nLastPaidHeight; int h = dmn.pdmnState->nLastPaidHeight;
@ -253,6 +259,7 @@ void CDeterministicMNList::AddMN(const CDeterministicMNCPtr &dmn)
{ {
assert(!mnMap.find(dmn->proTxHash)); assert(!mnMap.find(dmn->proTxHash));
mnMap = mnMap.set(dmn->proTxHash, dmn); mnMap = mnMap.set(dmn->proTxHash, dmn);
AddUniqueProperty(dmn, dmn->collateralOutpoint);
AddUniqueProperty(dmn, dmn->pdmnState->addr); AddUniqueProperty(dmn, dmn->pdmnState->addr);
AddUniqueProperty(dmn, dmn->pdmnState->keyIDOwner); AddUniqueProperty(dmn, dmn->pdmnState->keyIDOwner);
AddUniqueProperty(dmn, dmn->pdmnState->pubKeyOperator); AddUniqueProperty(dmn, dmn->pdmnState->pubKeyOperator);
@ -276,6 +283,7 @@ void CDeterministicMNList::RemoveMN(const uint256& proTxHash)
{ {
auto dmn = GetMN(proTxHash); auto dmn = GetMN(proTxHash);
assert(dmn != nullptr); assert(dmn != nullptr);
DeleteUniqueProperty(dmn, dmn->collateralOutpoint);
DeleteUniqueProperty(dmn, dmn->pdmnState->addr); DeleteUniqueProperty(dmn, dmn->pdmnState->addr);
DeleteUniqueProperty(dmn, dmn->pdmnState->keyIDOwner); DeleteUniqueProperty(dmn, dmn->pdmnState->keyIDOwner);
DeleteUniqueProperty(dmn, dmn->pdmnState->pubKeyOperator); 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 // check if any existing MN collateral is spent by this transaction
for (const auto& in : tx.vin) { for (const auto& in : tx.vin) {
const uint256& proTxHash = in.prevout.hash; auto dmn = newList.GetMNByCollateral(in.prevout);
auto dmn = newList.GetMN(proTxHash); if (dmn && dmn->collateralOutpoint == in.prevout) {
if (dmn && dmn->nCollateralIndex == in.prevout.n) { newList.RemoveMN(dmn->proTxHash);
newList.RemoveMN(proTxHash);
LogPrintf("CDeterministicMNManager::%s -- MN %s removed from list because collateral was spent. nHeight=%d, mapCurMNs.allMNsCount=%d\n", LogPrintf("CDeterministicMNManager::%s -- MN %s removed from list because collateral was spent. collateralOutpoint=%s, nHeight=%d, mapCurMNs.allMNsCount=%d\n",
__func__, proTxHash.ToString(), nHeight, newList.GetAllMNsCount()); __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)) if (newList.HasUniqueProperty(proTx.keyIDOwner) || newList.HasUniqueProperty(proTx.pubKeyOperator))
return _state.DoS(100, false, REJECT_CONFLICT, "bad-protx-dup-key"); 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; CDeterministicMNState dmnState = *dmn->pdmnState;
dmnState.nRegisteredHeight = nHeight; dmnState.nRegisteredHeight = nHeight;
@ -550,21 +575,18 @@ CDeterministicMNList CDeterministicMNManager::GetListAtChainTip()
return GetListForBlock(tipBlockHash); return GetListForBlock(tipBlockHash);
} }
CDeterministicMNCPtr CDeterministicMNManager::GetMN(const uint256& blockHash, const uint256& proTxHash) bool CDeterministicMNManager::HasValidMNCollateralAtChainTip(const COutPoint& outpoint)
{ {
auto mnList = GetListForBlock(blockHash); auto mnList = GetListAtChainTip();
return mnList.GetMN(proTxHash); 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); auto mnList = GetListAtChainTip();
return mnList.IsMNValid(proTxHash); auto dmn = mnList.GetMNByCollateral(outpoint);
} return dmn != nullptr;
bool CDeterministicMNManager::HasValidMNAtChainTip(const uint256& proTxHash)
{
return GetListAtChainTip().IsMNValid(proTxHash);
} }
int64_t CDeterministicMNManager::GetSpork15Value() int64_t CDeterministicMNManager::GetSpork15Value()
@ -572,6 +594,28 @@ int64_t CDeterministicMNManager::GetSpork15Value()
return sporkManager.GetSporkValue(SPORK_15_DETERMINISTIC_MNS_ENABLED); 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) bool CDeterministicMNManager::IsDeterministicMNsSporkActive(int nHeight)
{ {
LOCK(cs); LOCK(cs);

View File

@ -115,18 +115,11 @@ class CDeterministicMN
{ {
public: public:
CDeterministicMN() {} 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> template<typename Stream>
CDeterministicMN(deserialize_type, Stream& s) { s >> *this;} CDeterministicMN(deserialize_type, Stream& s) { s >> *this;}
uint256 proTxHash; uint256 proTxHash;
uint32_t nCollateralIndex; COutPoint collateralOutpoint;
uint16_t nOperatorReward; uint16_t nOperatorReward;
CDeterministicMNStateCPtr pdmnState; CDeterministicMNStateCPtr pdmnState;
@ -137,7 +130,7 @@ public:
inline void SerializationOp(Stream& s, Operation ser_action) inline void SerializationOp(Stream& s, Operation ser_action)
{ {
READWRITE(proTxHash); READWRITE(proTxHash);
READWRITE(nCollateralIndex); READWRITE(collateralOutpoint);
READWRITE(nOperatorReward); READWRITE(nOperatorReward);
READWRITE(pdmnState); READWRITE(pdmnState);
} }
@ -258,6 +251,8 @@ public:
bool IsMNValid(const uint256& proTxHash) const; bool IsMNValid(const uint256& proTxHash) const;
bool IsMNPoSeBanned(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 { bool HasMN(const uint256& proTxHash) const {
return GetMN(proTxHash) != nullptr; return GetMN(proTxHash) != nullptr;
@ -265,6 +260,7 @@ public:
CDeterministicMNCPtr GetMN(const uint256& proTxHash) const; CDeterministicMNCPtr GetMN(const uint256& proTxHash) const;
CDeterministicMNCPtr GetValidMN(const uint256& proTxHash) const; CDeterministicMNCPtr GetValidMN(const uint256& proTxHash) const;
CDeterministicMNCPtr GetMNByOperatorKey(const CBLSPublicKey& pubKey); CDeterministicMNCPtr GetMNByOperatorKey(const CBLSPublicKey& pubKey);
CDeterministicMNCPtr GetMNByCollateral(const COutPoint& collateralOutpoint) const;
CDeterministicMNCPtr GetMNPayee() const; CDeterministicMNCPtr GetMNPayee() const;
/** /**
@ -298,9 +294,6 @@ public:
} }
private: private:
bool IsMNValid(const CDeterministicMNCPtr& dmn) const;
bool IsMNPoSeBanned(const CDeterministicMNCPtr& dmn) const;
template<typename T> template<typename T>
void AddUniqueProperty(const CDeterministicMNCPtr& dmn, const T& v) void AddUniqueProperty(const CDeterministicMNCPtr& dmn, const T& v)
{ {
@ -396,14 +389,16 @@ public:
CDeterministicMNList GetListForBlock(const uint256& blockHash); CDeterministicMNList GetListForBlock(const uint256& blockHash);
CDeterministicMNList GetListAtChainTip(); CDeterministicMNList GetListAtChainTip();
CDeterministicMNCPtr GetMN(const uint256& blockHash, const uint256& proTxHash); // TODO remove after removal of old non-deterministic lists
bool HasValidMNAtBlock(const uint256& blockHash, const uint256& proTxHash); bool HasValidMNCollateralAtChainTip(const COutPoint& outpoint);
bool HasValidMNAtChainTip(const uint256& proTxHash); 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); bool IsDeterministicMNsSporkActive(int nHeight = -1);
private: private:
void UpdateSpork15Value();
int64_t GetSpork15Value(); int64_t GetSpork15Value();
void CleanupCache(int nHeight); void CleanupCache(int nHeight);
}; };

View File

@ -55,13 +55,22 @@ static bool CheckSig(const ProTx& proTx, const CBLSPublicKey& pubKey, CValidatio
return true; return true;
} }
template <typename ProTx, typename PubKey> template <typename ProTx>
static bool CheckInputsHashAndSig(const CTransaction &tx, const ProTx& proTx, const PubKey& pubKey, CValidationState& state) static bool CheckInputsHash(const CTransaction &tx, const ProTx& proTx, CValidationState& state)
{ {
uint256 inputsHash = CalcTxInputsHash(tx); uint256 inputsHash = CalcTxInputsHash(tx);
if (inputsHash != proTx.inputsHash) if (inputsHash != proTx.inputsHash)
return state.DoS(100, false, REJECT_INVALID, "bad-protx-inputs-hash"); 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)) if (!CheckSig(proTx, pubKey, state))
return false; return false;
@ -83,10 +92,18 @@ bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValid
if (ptx.nMode != 0) if (ptx.nMode != 0)
return state.DoS(100, false, REJECT_INVALID, "bad-protx-mode"); return state.DoS(100, false, REJECT_INVALID, "bad-protx-mode");
if (ptx.nCollateralIndex >= tx.vout.size()) // 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"); return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-index");
if (tx.vout[ptx.nCollateralIndex].nValue != 1000 * COIN) if (tx.vout[ptx.collateralOutpoint.n].nValue != 1000 * COIN)
return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral"); 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()) if (ptx.keyIDOwner.IsNull() || !ptx.pubKeyOperator.IsValid() || ptx.keyIDVoting.IsNull())
return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-null"); 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) // 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"); 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 // 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 any of both is set, it must be valid however
if (ptx.addr != CService() && !CheckService(tx.GetHash(), ptx, pindexPrev, state)) 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) if (ptx.nOperatorReward > 10000)
return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-reward"); 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) { if (pindexPrev) {
auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash()); auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash());
if (mnList.HasUniqueProperty(ptx.keyIDOwner) || mnList.HasUniqueProperty(ptx.pubKeyOperator)) { if (mnList.HasUniqueProperty(ptx.keyIDOwner) || mnList.HasUniqueProperty(ptx.pubKeyOperator)) {
return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-key"); 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 (!deterministicMNManager->IsDeterministicMNsSporkActive(pindexPrev->nHeight)) {
if (ptx.keyIDOwner != ptx.keyIDVoting) { if (ptx.keyIDOwner != ptx.keyIDVoting) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-not-same"); 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)) 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; 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; return true;
} }
@ -149,7 +200,8 @@ bool CheckProUpServTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVa
return false; return false;
if (pindexPrev) { if (pindexPrev) {
auto mn = deterministicMNManager->GetMN(pindexPrev->GetBlockHash(), ptx.proTxHash); auto mnList = deterministicMNManager->GetListForBlock(pindexPrev->GetBlockHash());
auto mn = mnList.GetMN(ptx.proTxHash);
if (!mn) if (!mn)
return state.DoS(100, false, REJECT_INVALID, "bad-protx-hash"); 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 // 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 // It is required while we are transitioning from the old MN list to the deterministic list
Coin coin; 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"); return state.DoS(100, false, REJECT_INVALID, "bad-protx-payee-collateral");
} }
if (coin.out.scriptPubKey != ptx.scriptPayout) if (coin.out.scriptPubKey != ptx.scriptPayout)
@ -273,8 +325,8 @@ std::string CProRegTx::ToString() const
payee = CBitcoinAddress(dest).ToString(); payee = CBitcoinAddress(dest).ToString();
} }
return strprintf("CProRegTx(nVersion=%d, nCollateralIndex=%d, addr=%s, nOperatorReward=%f, keyIDOwner=%s, pubKeyOperator=%s, keyIDVoting=%s, scriptPayout=%s)", return strprintf("CProRegTx(nVersion=%d, collateralOutpoint=%s, 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); nVersion, collateralOutpoint.ToStringShort(), addr.ToString(), (double)nOperatorReward / 100, keyIDOwner.ToString(), pubKeyOperator.ToString(), keyIDVoting.ToString(), payee);
} }
void CProRegTx::ToJson(UniValue& obj) const void CProRegTx::ToJson(UniValue& obj) const
@ -282,7 +334,8 @@ void CProRegTx::ToJson(UniValue& obj) const
obj.clear(); obj.clear();
obj.setObject(); obj.setObject();
obj.push_back(Pair("version", nVersion)); 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("service", addr.ToString(false)));
obj.push_back(Pair("keyIDOwner", keyIDOwner.ToString())); obj.push_back(Pair("keyIDOwner", keyIDOwner.ToString()));
obj.push_back(Pair("pubKeyOperator", pubKeyOperator.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("reason", (int)nReason));
obj.push_back(Pair("inputsHash", inputsHash.ToString())); 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;
}

View File

@ -24,7 +24,7 @@ public:
uint16_t nVersion{CURRENT_VERSION}; // message version uint16_t nVersion{CURRENT_VERSION}; // message version
uint16_t nType{0}; // only 0 supported for now uint16_t nType{0}; // only 0 supported for now
uint16_t nMode{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; CService addr;
CKeyID keyIDOwner; CKeyID keyIDOwner;
CBLSPublicKey pubKeyOperator; CBLSPublicKey pubKeyOperator;
@ -43,7 +43,7 @@ public:
READWRITE(nVersion); READWRITE(nVersion);
READWRITE(nType); READWRITE(nType);
READWRITE(nMode); READWRITE(nMode);
READWRITE(nCollateralIndex); READWRITE(collateralOutpoint);
READWRITE(addr); READWRITE(addr);
READWRITE(keyIDOwner); READWRITE(keyIDOwner);
READWRITE(pubKeyOperator); 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 CheckProUpRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state);
bool CheckProUpRevTx(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 #endif//DASH_PROVIDERTX_H

View File

@ -810,6 +810,13 @@ void ThreadImport(std::vector<boost::filesystem::path> vImportFiles)
if (activeMasternodeManager && fDIP0003ActiveAtTip) if (activeMasternodeManager && fDIP0003ActiveAtTip)
activeMasternodeManager->Init(); activeMasternodeManager->Init();
#ifdef ENABLE_WALLET
// we can't do this before DIP3 is fully initialized
if (pwalletMain) {
pwalletMain->AutoLockMasternodeCollaterals();
}
#endif
LoadMempool(); LoadMempool();
fDumpMempoolLater = !fRequestShutdown; fDumpMempoolLater = !fRequestShutdown;
} }
@ -1837,6 +1844,14 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
// ********************************************************* Step 9: data directory maintenance // ********************************************************* 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 // if pruning, unset the service bit and perform the initial blockstore prune
// after any wallet rescanning has taken place. // after any wallet rescanning has taken place.
if (fPruneMode) { 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()); 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 // ********************************************************* Step 11c: schedule Dash-specific tasks

View File

@ -627,7 +627,7 @@ bool CMasternodePayments::IsScheduled(const masternode_info_t& mnInfo, int nNotB
if (deterministicMNManager->IsDeterministicMNsSporkActive()) { if (deterministicMNManager->IsDeterministicMNsSporkActive()) {
auto projectedPayees = deterministicMNManager->GetListAtChainTip().GetProjectedMNPayees(8); auto projectedPayees = deterministicMNManager->GetListAtChainTip().GetProjectedMNPayees(8);
for (const auto &dmn : projectedPayees) { for (const auto &dmn : projectedPayees) {
if (dmn->proTxHash == mnInfo.outpoint.hash) { if (dmn->collateralOutpoint == mnInfo.outpoint) {
return true; return true;
} }
} }

View File

@ -56,7 +56,7 @@ CMasternode::CMasternode(const CMasternodeBroadcast& mnb) :
CMasternode::CMasternode(const uint256 &proTxHash, const CDeterministicMNCPtr& dmn) : CMasternode::CMasternode(const uint256 &proTxHash, const CDeterministicMNCPtr& dmn) :
masternode_info_t{ MASTERNODE_ENABLED, DMN_PROTO_VERSION, GetAdjustedTime(), 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) fAllowMixingTx(true)
{ {
CTxDestination dest; CTxDestination dest;
@ -352,7 +352,7 @@ void CMasternode::UpdateLastPaid(const CBlockIndex *pindex, int nMaxBlocksToScan
if(!pindex) return; if(!pindex) return;
if (deterministicMNManager->IsDeterministicMNsSporkActive(pindex->nHeight)) { 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) { if (!dmn || dmn->pdmnState->nLastPaidHeight == -1) {
LogPrint("masternode", "CMasternode::UpdateLastPaidBlock -- searching for block with payment to %s -- not found\n", outpoint.ToStringShort()); LogPrint("masternode", "CMasternode::UpdateLastPaidBlock -- searching for block with payment to %s -- not found\n", outpoint.ToStringShort());
} else { } else {

View File

@ -383,7 +383,7 @@ void CMasternodeMan::AddDeterministicMasternodes()
auto mnList = deterministicMNManager->GetListAtChainTip(); auto mnList = deterministicMNManager->GetListAtChainTip();
mnList.ForEachMN(true, [this](const CDeterministicMNCPtr& dmn) { mnList.ForEachMN(true, [this](const CDeterministicMNCPtr& dmn) {
// call Find() on each deterministic MN to force creation of CMasternode object // 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); assert(mn);
// make sure we use the splitted keys from now on // make sure we use the splitted keys from now on
@ -416,7 +416,7 @@ void CMasternodeMan::RemoveNonDeterministicMasternodes()
std::set<COutPoint> mnSet; std::set<COutPoint> mnSet;
auto mnList = deterministicMNManager->GetListAtChainTip(); auto mnList = deterministicMNManager->GetListAtChainTip();
mnList.ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) { mnList.ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) {
mnSet.insert(COutPoint(dmn->proTxHash, dmn->nCollateralIndex)); mnSet.insert(dmn->collateralOutpoint);
}); });
auto it = mapMasternodes.begin(); auto it = mapMasternodes.begin();
while (it != mapMasternodes.end()) { while (it != mapMasternodes.end()) {
@ -539,11 +539,8 @@ CMasternode* CMasternodeMan::Find(const COutPoint &outpoint)
// on-chain, like vote counts // on-chain, like vote counts
auto mnList = deterministicMNManager->GetListAtChainTip(); auto mnList = deterministicMNManager->GetListAtChainTip();
if (!mnList.IsMNValid(outpoint.hash)) { auto dmn = mnList.GetMNByCollateral(outpoint);
return nullptr; if (!dmn || !mnList.IsMNValid(dmn)) {
}
auto dmn = mnList.GetMN(outpoint.hash);
if (!dmn) {
return nullptr; return nullptr;
} }
@ -578,7 +575,7 @@ bool CMasternodeMan::GetMasternodeInfo(const uint256& proTxHash, masternode_info
auto dmn = deterministicMNManager->GetListAtChainTip().GetValidMN(proTxHash); auto dmn = deterministicMNManager->GetListAtChainTip().GetValidMN(proTxHash);
if (!dmn) if (!dmn)
return false; return false;
return GetMasternodeInfo(COutPoint(proTxHash, dmn->nCollateralIndex), mnInfoRet); return GetMasternodeInfo(dmn->collateralOutpoint, mnInfoRet);
} }
bool CMasternodeMan::GetMasternodeInfo(const COutPoint& outpoint, masternode_info_t& mnInfoRet) bool CMasternodeMan::GetMasternodeInfo(const COutPoint& outpoint, masternode_info_t& mnInfoRet)
@ -626,7 +623,7 @@ bool CMasternodeMan::Has(const COutPoint& outpoint)
{ {
LOCK(cs); LOCK(cs);
if (deterministicMNManager->IsDeterministicMNsSporkActive()) { if (deterministicMNManager->IsDeterministicMNsSporkActive()) {
return deterministicMNManager->HasValidMNAtChainTip(outpoint.hash); return deterministicMNManager->HasValidMNCollateralAtChainTip(outpoint);
} else { } else {
return mapMasternodes.find(outpoint) != mapMasternodes.end(); return mapMasternodes.find(outpoint) != mapMasternodes.end();
} }
@ -754,7 +751,7 @@ masternode_info_t CMasternodeMan::FindRandomNotInVec(const std::vector<COutPoint
} }
} }
if(fExclude) continue; if(fExclude) continue;
if (deterministicMNManager->IsDeterministicMNsSporkActive() && !deterministicMNManager->HasValidMNAtChainTip(pmn->outpoint.hash)) if (deterministicMNManager->IsDeterministicMNsSporkActive() && !deterministicMNManager->HasValidMNCollateralAtChainTip(pmn->outpoint))
continue; continue;
// found the one not in vecToExclude // found the one not in vecToExclude
LogPrint("masternode", "CMasternodeMan::FindRandomNotInVec -- found, masternode=%s\n", pmn->outpoint.ToStringShort()); LogPrint("masternode", "CMasternodeMan::FindRandomNotInVec -- found, masternode=%s\n", pmn->outpoint.ToStringShort());
@ -771,8 +768,10 @@ std::map<COutPoint, CMasternode> CMasternodeMan::GetFullMasternodeMap()
if (deterministicMNManager->IsDeterministicMNsSporkActive()) { if (deterministicMNManager->IsDeterministicMNsSporkActive()) {
std::map<COutPoint, CMasternode> result; std::map<COutPoint, CMasternode> result;
auto mnList = deterministicMNManager->GetListAtChainTip();
for (const auto &p : mapMasternodes) { 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); result.emplace(p.first, p.second);
} }
} }
@ -796,7 +795,7 @@ bool CMasternodeMan::GetMasternodeScores(const uint256& nBlockHash, CMasternodeM
// calculate scores // calculate scores
for (const auto& mnpair : mapMasternodes) { for (const auto& mnpair : mapMasternodes) {
if (deterministicMNManager->IsDeterministicMNsSporkActive() && !deterministicMNManager->HasValidMNAtChainTip(mnpair.second.outpoint.hash)) if (deterministicMNManager->IsDeterministicMNsSporkActive() && !deterministicMNManager->HasValidMNCollateralAtChainTip(mnpair.second.outpoint))
continue; continue;
if (mnpair.second.nProtocolVersion >= nMinProtocol) { if (mnpair.second.nProtocolVersion >= nMinProtocol) {

View File

@ -586,7 +586,7 @@ UniValue gobject_vote_many(const JSONRPCRequest& request)
continue; continue;
} }
if (nTxHash == dmn->proTxHash && (uint32_t)nOutputIndex == dmn->nCollateralIndex) { if (nTxHash == dmn->collateralOutpoint.hash && (uint32_t)nOutputIndex == dmn->collateralOutpoint.n) {
found = true; found = true;
break; break;
} }
@ -595,7 +595,7 @@ UniValue gobject_vote_many(const JSONRPCRequest& request)
CKey ownerKey; CKey ownerKey;
if (pwalletMain->GetKey(dmn->pdmnState->keyIDVoting, ownerKey)) { if (pwalletMain->GetKey(dmn->pdmnState->keyIDVoting, ownerKey)) {
CBitcoinSecret secret(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); entries.push_back(mne);
} }
} }

View File

@ -597,89 +597,6 @@ UniValue masternode_genkey(const JSONRPCRequest& request)
return CBitcoinSecret(secret).ToString(); 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() void masternode_list_conf_help()
{ {
throw std::runtime_error( throw std::runtime_error(
@ -741,7 +658,8 @@ UniValue masternode_status(const JSONRPCRequest& request)
auto dmn = activeMasternodeManager->GetDMN(); auto dmn = activeMasternodeManager->GetDMN();
if (dmn) { if (dmn) {
mnObj.push_back(Pair("proTxHash", dmn->proTxHash.ToString())); 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; UniValue stateObj;
dmn->pdmnState->ToJson(stateObj); dmn->pdmnState->ToJson(stateObj);
mnObj.push_back(Pair("dmnState", stateObj)); mnObj.push_back(Pair("dmnState", stateObj));
@ -902,8 +820,6 @@ UniValue masternode(const JSONRPCRequest& request)
#endif // ENABLE_WALLET #endif // ENABLE_WALLET
} else if (strCommand == "genkey") { } else if (strCommand == "genkey") {
return masternode_genkey(request); return masternode_genkey(request);
} else if (strCommand == "info") {
return masternode_info(request);
} else if (strCommand == "list-conf") { } else if (strCommand == "list-conf") {
return masternode_list_conf(request); return masternode_list_conf(request);
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET

View File

@ -116,9 +116,15 @@ static void FundSpecialTx(CMutableTransaction& tx, SpecialTxPayload payload)
} }
template<typename SpecialTxPayload> 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); payload.inputsHash = CalcTxInputsHash(tx);
}
template<typename SpecialTxPayload>
static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKey& key)
{
UpdateSpecialTxInputsHash(tx, payload);
payload.vchSig.clear(); payload.vchSig.clear();
uint256 hash = ::SerializeHash(payload); uint256 hash = ::SerializeHash(payload);
@ -130,7 +136,7 @@ static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload
template<typename SpecialTxPayload> template<typename SpecialTxPayload>
static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload& payload, const CBLSSecretKey& key) static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload& payload, const CBLSSecretKey& key)
{ {
payload.inputsHash = CalcTxInputsHash(tx); UpdateSpecialTxInputsHash(tx, payload);
uint256 hash = ::SerializeHash(payload); uint256 hash = ::SerializeHash(payload);
payload.sig = key.Sign(hash); payload.sig = key.Sign(hash);
@ -139,8 +145,9 @@ static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload
static std::string SignAndSendSpecialTx(const CMutableTransaction& tx) static std::string SignAndSendSpecialTx(const CMutableTransaction& tx)
{ {
LOCK(cs_main); LOCK(cs_main);
CValidationState state; CValidationState state;
if (!CheckSpecialTx(tx, NULL, state)) if (!CheckSpecialTx(tx, chainActive.Tip(), state))
throw std::runtime_error(FormatStateMessage(state)); throw std::runtime_error(FormatStateMessage(state));
CDataStream ds(SER_NETWORK, PROTOCOL_VERSION); CDataStream ds(SER_NETWORK, PROTOCOL_VERSION);
@ -157,11 +164,11 @@ static std::string SignAndSendSpecialTx(const CMutableTransaction& tx)
return sendrawtransaction(sendRequest).get_str(); return sendrawtransaction(sendRequest).get_str();
} }
void protx_register_help() void protx_fund_register_help()
{ {
throw std::runtime_error( throw std::runtime_error(
"protx register \"collateralAddress\" collateralAmount \"ipAndPort\" \"ownerKeyAddr\" \"operatorKeyAddr\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n" "protx fund_register \"collateralAddress\" \"ipAndPort\" \"ownerKeyAddr\" \"operatorKeyAddr\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n"
"\nCreates and sends a ProTx to the network. The resulting transaction will move the specified amount\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" "to the address specified by collateralAddress and will then function as the collateral of your\n"
"masternode.\n" "masternode.\n"
"A few of the limitations you see in the arguments are temporary and might be lifted after DIP3\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" "\nArguments:\n"
"1. \"collateralAddress\" (string, required) The dash address to send the collateral to.\n" "1. \"collateralAddress\" (string, required) The dash address to send the collateral to.\n"
" Must be a P2PKH address.\n" " Must be a P2PKH address.\n"
"2. \"collateralAmount\" (numeric or string, required) The collateral amount.\n" "2. \"ipAndPort\" (string, required) IP and port in the form \"IP:PORT\".\n"
" Must be exactly 1000 Dash.\n"
"3. \"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" " 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" " 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" " 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" " 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" " 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" " 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" " 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" " \"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\"." " Must match \"collateralAddress\"."
"\nExamples:\n" "\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) 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(); protx_register_help();
}
CBitcoinAddress collateralAddress(request.params[1].get_str()); size_t paramIdx = 1;
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());
CAmount collateralAmount; CAmount collateralAmount = 1000 * COIN;
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);
CMutableTransaction tx; CMutableTransaction tx;
tx.nVersion = 3; tx.nVersion = 3;
tx.nType = TRANSACTION_PROVIDER_REGISTER; tx.nType = TRANSACTION_PROVIDER_REGISTER;
tx.vout.emplace_back(collateralTxOut);
CProRegTx ptx; CProRegTx ptx;
ptx.nVersion = CProRegTx::CURRENT_VERSION; ptx.nVersion = CProRegTx::CURRENT_VERSION;
if (request.params[3].get_str() != "0" && request.params[3].get_str() != "") { if (isFundRegister) {
if (!Lookup(request.params[3].get_str().c_str(), ptx.addr, Params().GetDefaultPort(), false)) CBitcoinAddress collateralAddress(request.params[paramIdx].get_str());
throw std::runtime_error(strprintf("invalid network address %s", request.params[3].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));
} }
CKey keyOwner = ParsePrivKey(request.params[4].get_str(), true); ptx.collateralOutpoint = COutPoint(collateralHash, (uint32_t)collateralIndex);
CBLSPublicKey pubKeyOperator = ParseBLSPubKey(request.params[5].get_str(), "operator BLS address"); paramIdx += 2;
// TODO unlock on failure
LOCK(pwalletMain->cs_wallet);
pwalletMain->LockCoin(ptx.collateralOutpoint);
}
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(); CKeyID keyIDVoting = keyOwner.GetPubKey().GetID();
if (request.params[6].get_str() != "0" && request.params[6].get_str() != "") { if (request.params[paramIdx + 3].get_str() != "0" && request.params[paramIdx + 3].get_str() != "") {
keyIDVoting = ParsePubKeyIDFromAddress(request.params[6].get_str(), "voting address"); 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) if (operatorReward < 0 || operatorReward > 100)
throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorReward must be between 0 and 100"); throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorReward must be between 0 and 100");
ptx.nOperatorReward = (uint16_t)(operatorReward * 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()) 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.keyIDOwner = keyOwner.GetPubKey().GetID();
ptx.pubKeyOperator = pubKeyOperator; ptx.pubKeyOperator = pubKeyOperator;
ptx.keyIDVoting = keyIDVoting; ptx.keyIDVoting = keyIDVoting;
ptx.scriptPayout = GetScriptForDestination(payoutAddress.Get()); 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); FundSpecialTx(tx, ptx);
uint32_t collateralIndex = (uint32_t) - 1; if (isFundRegister) {
uint32_t collateralIndex = (uint32_t) -1;
for (uint32_t i = 0; i < tx.vout.size(); i++) { for (uint32_t i = 0; i < tx.vout.size(); i++) {
if (tx.vout[i] == collateralTxOut) { if (tx.vout[i].nValue == collateralAmount) {
collateralIndex = i; collateralIndex = i;
break; break;
} }
} }
assert(collateralIndex != (uint32_t) - 1); assert(collateralIndex != (uint32_t) -1);
ptx.nCollateralIndex = collateralIndex; ptx.collateralOutpoint.n = 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); SetTxPayload(tx, ptx);
return SignAndSendSpecialTx(tx); return SignAndSendSpecialTx(tx);
@ -487,7 +555,7 @@ UniValue BuildDMNListEntry(const CDeterministicMNCPtr& dmn, bool detailed)
dmn->ToJson(o); dmn->ToJson(o);
int confirmations = GetUTXOConfirmations(COutPoint(dmn->proTxHash, dmn->nCollateralIndex)); int confirmations = GetUTXOConfirmations(dmn->collateralOutpoint);
o.push_back(Pair("confirmations", confirmations)); o.push_back(Pair("confirmations", confirmations));
bool hasOwnerKey = pwalletMain->HaveKey(dmn->pdmnState->keyIDOwner); bool hasOwnerKey = pwalletMain->HaveKey(dmn->pdmnState->keyIDOwner);
@ -497,8 +565,8 @@ UniValue BuildDMNListEntry(const CDeterministicMNCPtr& dmn, bool detailed)
bool ownsCollateral = false; bool ownsCollateral = false;
CTransactionRef collateralTx; CTransactionRef collateralTx;
uint256 tmpHashBlock; uint256 tmpHashBlock;
if (GetTransaction(dmn->proTxHash, collateralTx, Params().GetConsensus(), tmpHashBlock)) { if (GetTransaction(dmn->collateralOutpoint.hash, collateralTx, Params().GetConsensus(), tmpHashBlock)) {
ownsCollateral = CheckWalletOwnsScript(collateralTx->vout[dmn->nCollateralIndex].scriptPubKey); ownsCollateral = CheckWalletOwnsScript(collateralTx->vout[dmn->collateralOutpoint.n].scriptPubKey);
} }
UniValue walletObj(UniValue::VOBJ); UniValue walletObj(UniValue::VOBJ);
@ -534,13 +602,13 @@ UniValue protx_list(const JSONRPCRequest& request)
std::vector<COutPoint> vOutpts; std::vector<COutPoint> vOutpts;
pwalletMain->ListProTxCoins(vOutpts); pwalletMain->ListProTxCoins(vOutpts);
std::set<uint256> setOutpts; std::set<COutPoint> setOutpts;
for (const auto& outpt : vOutpts) { for (const auto& outpt : vOutpts) {
setOutpts.emplace(outpt.hash); setOutpts.emplace(outpt);
} }
deterministicMNManager->GetListAtChainTip().ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) { 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->keyIDOwner) ||
pwalletMain->HaveKey(dmn->pdmnState->keyIDVoting) || pwalletMain->HaveKey(dmn->pdmnState->keyIDVoting) ||
CheckWalletOwnsScript(dmn->pdmnState->scriptPayout) || CheckWalletOwnsScript(dmn->pdmnState->scriptPayout) ||
@ -572,6 +640,30 @@ UniValue protx_list(const JSONRPCRequest& request)
return ret; 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() void protx_diff_help()
{ {
throw std::runtime_error( throw std::runtime_error(
@ -638,6 +730,7 @@ UniValue protx(const JSONRPCRequest& request)
"\nAvailable commands:\n" "\nAvailable commands:\n"
" register - Create and send ProTx to network\n" " register - Create and send ProTx to network\n"
" list - List ProTxs\n" " list - List ProTxs\n"
" info - Return information about a ProTx\n"
" update_service - Create and send ProUpServTx to network\n" " update_service - Create and send ProUpServTx to network\n"
" update_registrar - Create and send ProUpRegTx to network\n" " update_registrar - Create and send ProUpRegTx to network\n"
" revoke - Create and send ProUpRevTx 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(); std::string command = request.params[0].get_str();
if (command == "register") { if (command == "fund_register" || command == "register") {
return protx_register(request); return protx_register(request);
} else if (command == "list") { } else if (command == "list") {
return protx_list(request); return protx_list(request);
} else if (command == "info") {
return protx_info(request);
} else if (command == "update_service") { } else if (command == "update_service") {
return protx_update_service(request); return protx_update_service(request);
} else if (command == "update_registrar") { } else if (command == "update_registrar") {

View File

@ -103,7 +103,7 @@ static CMutableTransaction CreateProRegTx(SimpleUTXOMap& utxos, int port, const
auto inputs = SelectUTXOs(utxos, 1000 * COIN, change); auto inputs = SelectUTXOs(utxos, 1000 * COIN, change);
CProRegTx proTx; CProRegTx proTx;
proTx.nCollateralIndex = 0; proTx.collateralOutpoint.n = 0;
proTx.addr = LookupNumeric("1.1.1.1", port); proTx.addr = LookupNumeric("1.1.1.1", port);
proTx.keyIDOwner = ownerKeyRet.GetPubKey().GetID(); proTx.keyIDOwner = ownerKeyRet.GetPubKey().GetID();
proTx.pubKeyOperator = operatorKeyRet.GetPublicKey(); proTx.pubKeyOperator = operatorKeyRet.GetPublicKey();
@ -115,7 +115,6 @@ static CMutableTransaction CreateProRegTx(SimpleUTXOMap& utxos, int port, const
tx.nType = TRANSACTION_PROVIDER_REGISTER; tx.nType = TRANSACTION_PROVIDER_REGISTER;
FundTransaction(tx, utxos, scriptPayout, 1000 * COIN, coinbaseKey); FundTransaction(tx, utxos, scriptPayout, 1000 * COIN, coinbaseKey);
proTx.inputsHash = CalcTxInputsHash(tx); proTx.inputsHash = CalcTxInputsHash(tx);
CHashSigner::SignHash(::SerializeHash(proTx), ownerKeyRet, proTx.vchSig);
SetTxPayload(tx, proTx); SetTxPayload(tx, proTx);
SignTransaction(tx, coinbaseKey); SignTransaction(tx, coinbaseKey);

View File

@ -449,6 +449,9 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
mapProTxAddresses.emplace(proTx.addr, tx.GetHash()); mapProTxAddresses.emplace(proTx.addr, tx.GetHash());
mapProTxPubKeyIDs.emplace(proTx.keyIDOwner, tx.GetHash()); mapProTxPubKeyIDs.emplace(proTx.keyIDOwner, tx.GetHash());
mapProTxBlsPubKeyHashes.emplace(proTx.pubKeyOperator.GetHash(), 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) { } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) {
CProUpServTx proTx; CProUpServTx proTx;
if (!GetTxPayload(tx, proTx)) { if (!GetTxPayload(tx, proTx)) {
@ -646,6 +649,7 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
mapProTxAddresses.erase(proTx.addr); mapProTxAddresses.erase(proTx.addr);
mapProTxPubKeyIDs.erase(proTx.keyIDOwner); mapProTxPubKeyIDs.erase(proTx.keyIDOwner);
mapProTxBlsPubKeyHashes.erase(proTx.pubKeyOperator.GetHash()); mapProTxBlsPubKeyHashes.erase(proTx.pubKeyOperator.GetHash());
mapProTxCollaterals.erase(proTx.collateralOutpoint);
} else if (it->GetTx().nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) { } else if (it->GetTx().nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) {
CProUpServTx proTx; CProUpServTx proTx;
if (!GetTxPayload(it->GetTx(), 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) void CTxMemPool::removeProTxConflicts(const CTransaction &tx)
{ {
if (tx.nType == TRANSACTION_PROVIDER_REGISTER) { if (tx.nType == TRANSACTION_PROVIDER_REGISTER) {
@ -822,6 +836,9 @@ void CTxMemPool::removeProTxConflicts(const CTransaction &tx)
} }
removeProTxPubKeyConflicts(tx, proTx.keyIDOwner); removeProTxPubKeyConflicts(tx, proTx.keyIDOwner);
removeProTxPubKeyConflicts(tx, proTx.pubKeyOperator); removeProTxPubKeyConflicts(tx, proTx.pubKeyOperator);
if (!proTx.collateralOutpoint.hash.IsNull()) {
removeProTxCollateralConflicts(tx, proTx.collateralOutpoint);
}
} else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) { } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) {
CProUpServTx proTx; CProUpServTx proTx;
if (!GetTxPayload(tx, proTx)) { if (!GetTxPayload(tx, proTx)) {
@ -1127,7 +1144,11 @@ bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const {
CProRegTx proTx; CProRegTx proTx;
if (!GetTxPayload(tx, proTx)) if (!GetTxPayload(tx, proTx))
assert(false); 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) { } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) {
CProUpServTx proTx; CProUpServTx proTx;
if (!GetTxPayload(tx, proTx)) if (!GetTxPayload(tx, proTx))

View File

@ -537,6 +537,7 @@ private:
std::map<CService, uint256> mapProTxAddresses; std::map<CService, uint256> mapProTxAddresses;
std::map<CKeyID, uint256> mapProTxPubKeyIDs; std::map<CKeyID, uint256> mapProTxPubKeyIDs;
std::map<uint256, uint256> mapProTxBlsPubKeyHashes; std::map<uint256, uint256> mapProTxBlsPubKeyHashes;
std::map<COutPoint, uint256> mapProTxCollaterals;
void UpdateParent(txiter entry, txiter parent, bool add); void UpdateParent(txiter entry, txiter parent, bool add);
void UpdateChild(txiter entry, txiter child, bool add); void UpdateChild(txiter entry, txiter child, bool add);
@ -582,6 +583,7 @@ public:
void removeConflicts(const CTransaction &tx); void removeConflicts(const CTransaction &tx);
void removeProTxPubKeyConflicts(const CTransaction &tx, const CKeyID &keyId); void removeProTxPubKeyConflicts(const CTransaction &tx, const CKeyID &keyId);
void removeProTxPubKeyConflicts(const CTransaction &tx, const CBLSPublicKey &pubKey); void removeProTxPubKeyConflicts(const CTransaction &tx, const CBLSPublicKey &pubKey);
void removeProTxCollateralConflicts(const CTransaction &tx, const COutPoint &collateralOutpoint);
void removeProTxConflicts(const CTransaction &tx); void removeProTxConflicts(const CTransaction &tx);
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight); void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight);

View File

@ -1119,11 +1119,10 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
} }
AddToSpends(hash); AddToSpends(hash);
uint32_t proTxCollateralIdx = GetProTxCollateralIndex(*wtx.tx);
for(unsigned int i = 0; i < wtx.tx->vout.size(); ++i) { for(unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
if (IsMine(wtx.tx->vout[i]) && !IsSpent(hash, i)) { if (IsMine(wtx.tx->vout[i]) && !IsSpent(hash, i)) {
setWalletUTXO.insert(COutPoint(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)); LockCoin(COutPoint(hash, i));
} }
} }
@ -3967,13 +3966,9 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{ {
LOCK2(cs_main, cs_wallet); LOCK2(cs_main, cs_wallet);
for (auto& pair : mapWallet) { for (auto& pair : mapWallet) {
uint32_t proTxCollateralIdx = GetProTxCollateralIndex(*pair.second.tx);
for(unsigned int i = 0; i < pair.second.tx->vout.size(); ++i) { for(unsigned int i = 0; i < pair.second.tx->vout.size(); ++i) {
if (IsMine(pair.second.tx->vout[i]) && !IsSpent(pair.first, i)) { if (IsMine(pair.second.tx->vout[i]) && !IsSpent(pair.first, i)) {
setWalletUTXO.insert(COutPoint(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; 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) DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut)
{ {
if (!fFileBacked) if (!fFileBacked)
@ -4634,7 +4645,7 @@ void CWallet::ListProTxCoins(std::vector<COutPoint>& vOutpts)
for (const auto &o : setWalletUTXO) { for (const auto &o : setWalletUTXO) {
if (mapWallet.count(o.hash)) { if (mapWallet.count(o.hash)) {
const auto &p = mapWallet[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); vOutpts.emplace_back(o);
} }
} }

View File

@ -1004,6 +1004,7 @@ public:
void SetBestChain(const CBlockLocator& loc) override; void SetBestChain(const CBlockLocator& loc) override;
DBErrors LoadWallet(bool& fFirstRunRet); DBErrors LoadWallet(bool& fFirstRunRet);
void AutoLockMasternodeCollaterals();
DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx); DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx);
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut); DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);