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 28688724e1.

* 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.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()

View File

@ -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;

View File

@ -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);

View File

@ -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);
};

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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

View File

@ -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") {

View File

@ -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);

View File

@ -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))

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);