From dd1478f7dc82d2ba02257ff4c0ea8673aa92933c Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Tue, 1 Aug 2023 14:33:31 +0200 Subject: [PATCH] Functional test coverage --- src/evo/providertx.cpp | 2 + src/rpc/masternode.cpp | 15 +- test/functional/test_framework/messages.py | 16 ++- .../test_framework/test_framework.py | 65 ++++++--- test/functional/test_framework/util.py | 9 +- test/functional/tiertwo_deterministicmns.py | 129 ++++++++++++------ 6 files changed, 153 insertions(+), 83 deletions(-) diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp index acc9f47e4c455e..6e0d2b70e9e70f 100644 --- a/src/evo/providertx.cpp +++ b/src/evo/providertx.cpp @@ -7,6 +7,7 @@ #include "bls/key_io.h" #include "key_io.h" +#include "primitives/transaction.h" #include "uint256.h" std::string ProRegPL::MakeSignString() const @@ -40,6 +41,7 @@ void ProRegPL::ToJson(UniValue& obj) const obj.pushKV("version", nVersion); obj.pushKV("collateralHash", collateralOutpoint.hash.ToString()); obj.pushKV("collateralIndex", (int)collateralOutpoint.n); + obj.pushKV("nullifier", shieldCollateral.input.nullifier.ToString()); obj.pushKV("service", addr.ToString()); obj.pushKV("ownerAddress", EncodeDestination(keyIDOwner)); obj.pushKV("operatorPubKey", bls::EncodePublic(Params(), pubKeyOperator)); diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index 7b146c543b4fbb..e0be8b67a764a8 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -145,16 +145,9 @@ static inline bool filter(const std::string& str, const std::string& strFilter) return str.find(strFilter) != std::string::npos; } -static inline bool filterMasternode(const UniValue& dmno, const std::string& strFilter, bool fEnabled) +static inline bool filterMasternode(const UniValue& dmno, const std::string& strFilter, bool fEnabled, bool isShield) { - return strFilter.empty() || (filter("ENABLED", strFilter) && fEnabled) - || (filter("POSE_BANNED", strFilter) && !fEnabled) - || (filter(dmno["proTxHash"].get_str(), strFilter)) - || (filter(dmno["collateralHash"].get_str(), strFilter)) - || (filter(dmno["collateralAddress"].get_str(), strFilter)) - || (filter(dmno["dmnstate"]["ownerAddress"].get_str(), strFilter)) - || (filter(dmno["dmnstate"]["operatorPubKey"].get_str(), strFilter)) - || (filter(dmno["dmnstate"]["votingAddress"].get_str(), strFilter)); + return strFilter.empty() || (filter("ENABLED", strFilter) && fEnabled) || (filter("POSE_BANNED", strFilter) && !fEnabled) || (filter("SHIELD", strFilter) && isShield) || (filter(dmno["proTxHash"].get_str(), strFilter)) || (filter(dmno["collateralHash"].get_str(), strFilter)) || (!isShield && filter(dmno["collateralAddress"].get_str(), strFilter)) || (filter(dmno["dmnstate"]["ownerAddress"].get_str(), strFilter)) || (filter(dmno["dmnstate"]["operatorPubKey"].get_str(), strFilter)) || (filter(dmno["dmnstate"]["votingAddress"].get_str(), strFilter)); } UniValue listmasternodes(const JSONRPCRequest& request) @@ -197,7 +190,7 @@ UniValue listmasternodes(const JSONRPCRequest& request) auto mnList = deterministicMNManager->GetListAtChainTip(); mnList.ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) { UniValue obj = DmnToJson(dmn); - if (filterMasternode(obj, strFilter, !dmn->IsPoSeBanned())) { + if (filterMasternode(obj, strFilter, !dmn->IsPoSeBanned(), !dmn->nullifier.IsNull())) { ret.push_back(obj); } }); @@ -223,7 +216,7 @@ UniValue listmasternodes(const JSONRPCRequest& request) if (dmn) { UniValue obj = DmnToJson(dmn); bool fEnabled = !dmn->IsPoSeBanned(); - if (filterMasternode(obj, strFilter, fEnabled)) { + if (filterMasternode(obj, strFilter, fEnabled, false)) { // Added for backward compatibility with legacy masternodes obj.pushKV("type", "deterministic"); obj.pushKV("txhash", obj["proTxHash"].get_str()); diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index d0f8d3b1e0547d..44d2512e9061ae 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -334,9 +334,10 @@ def __repr__(self): class COutPoint: - def __init__(self, hash=0, n=0): + def __init__(self, hash=0, n=0, transparent=True): self.hash = hash self.n = n + self.transparent = transparent def deserialize(self, f): self.hash = deser_uint256(f) @@ -362,7 +363,8 @@ def __repr__(self): return "COutPoint(hash=%064x n=%i)" % (self.hash, self.n) def to_json(self): - return {"txid": "%064x" % self.hash, "vout": self.n} + voutStr = "vout" if self.transparent else "vShieldedOutput" + return {"txid": "%064x" % self.hash, voutStr: self.n} NullOutPoint = COutPoint(0, 0xffffffff) @@ -1478,8 +1480,10 @@ def serialize(self): # PIVX Classes +# NB: for shielded masternode the field collateral is the ShieldOutPoint of the shield collateral +# notice the difference from the ProRegTx in which the collateral is the Null default value class Masternode(object): - def __init__(self, idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk): + def __init__(self, idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk, transparent): self.idx = idx self.owner = owner_addr self.operator_pk = operator_pk @@ -1489,6 +1493,8 @@ def __init__(self, idx, owner_addr, operator_pk, voting_addr, ipport, payout_add self.operator_sk = operator_sk self.proTx = None self.collateral = None + self.nullifier = None + self.transparent = transparent def revoked(self): self.ipport = "[::]:0" @@ -1496,9 +1502,9 @@ def revoked(self): self.operator_sk = None def __repr__(self): - return "Masternode(idx=%d, owner=%s, operator=%s, voting=%s, ip=%s, payee=%s, opkey=%s, protx=%s, collateral=%s)" % ( + return "Masternode(idx=%d, owner=%s, operator=%s, voting=%s, ip=%s, payee=%s, opkey=%s, protx=%s, collateral=%s, transparent=%s)" % ( self.idx, str(self.owner), str(self.operator_pk), str(self.voting), str(self.ipport), - str(self.payee), str(self.operator_sk), str(self.proTx), str(self.collateral) + str(self.payee), str(self.operator_sk), str(self.proTx), str(self.collateral), str(self.transparent) ) def __str__(self): diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 06303b5dc1b847..349bb64435d63a 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1104,13 +1104,13 @@ def setupDMN(self, break assert_greater_than(collateralTxId_n, -1) assert_greater_than(json_tx["confirmations"], 0) - proTxId = mnOwner.protx_register(collateralTxId, collateralTxId_n, ipport, ownerAdd, + proTxId = mnOwner.protx_register(collateralTxId, collateralTxId_n, True, ipport, ownerAdd, bls_keypair["public"], votingAdd, collateralAdd) elif strType == "external": self.log.info("Setting up ProRegTx with collateral externally-signed...") # send the tx from the miner payoutAdd = mnOwner.getnewaddress("payout") - register_res = miner.protx_register_prepare(outpoint.hash, outpoint.n, ipport, ownerAdd, + register_res = miner.protx_register_prepare(outpoint.hash, outpoint.n, True, ipport, ownerAdd, bls_keypair["public"], votingAdd, payoutAdd) self.log.info("ProTx prepared") message_to_sign = register_res["signMessage"] @@ -1205,19 +1205,20 @@ def protx_register_fund(self, miner, controller, dmn, collateral_addr, op_rew=No Create a ProReg tx, which references an 100 PIV UTXO as collateral. The controller node owns the collateral and creates the ProReg tx. """ - def protx_register(self, miner, controller, dmn, collateral_addr): + def protx_register(self, miner, controller, dmn, collateral_addr, transparent): # send to the owner the exact collateral tx amount funding_txid = miner.sendtoaddress(collateral_addr, Decimal('100')) # send another output to be used for the fee of the proReg tx - miner.sendtoaddress(collateral_addr, Decimal('1')) + feeAddr = collateral_addr if transparent else controller.getnewaddress("feeAddr") + miner.sendtoaddress(feeAddr, Decimal('1')) # confirm and verify reception miner.generate(1) self.sync_blocks([miner, controller]) json_tx = controller.getrawtransaction(funding_txid, True) assert_greater_than(json_tx["confirmations"], 0) - # create and send the ProRegTx - dmn.collateral = COutPoint(int(funding_txid, 16), get_collateral_vout(json_tx)) - dmn.proTx = controller.protx_register(funding_txid, dmn.collateral.n, dmn.ipport, dmn.owner, + # create and send the ProRegTx, FOR SHIELD DMNS THIS IS NOT THE COLLATERAL CONTAINED IN THE PROREGTX (which is instead the null COutPoint (0,-1)) + dmn.collateral = COutPoint(int(funding_txid, 16), get_collateral_vout(json_tx)) if transparent else COutPoint(int(funding_txid, 16), 0, transparent) + dmn.proTx = controller.protx_register(funding_txid, dmn.collateral.n, transparent, dmn.ipport, dmn.owner, dmn.operator_pk, dmn.voting, dmn.payee) """ @@ -1236,7 +1237,7 @@ def protx_register_ext(self, miner, controller, dmn, outpoint, fSubmit): outpoint = COutPoint(int(funding_txid, 16), get_collateral_vout(json_tx)) dmn.collateral = outpoint # Prepare the message to be signed externally by the owner of the collateral (the controller) - reg_tx = miner.protx_register_prepare("%064x" % outpoint.hash, outpoint.n, dmn.ipport, dmn.owner, + reg_tx = miner.protx_register_prepare("%064x" % outpoint.hash, outpoint.n, True, dmn.ipport, dmn.owner, dmn.operator_pk, dmn.voting, dmn.payee) sig = controller.signmessage(reg_tx["collateralAddress"], reg_tx["signMessage"]) if fSubmit: @@ -1257,7 +1258,7 @@ def protx_register_ext(self, miner, controller, dmn, outpoint, fSubmit): If not provided, a new address-key pair is generated. :return: dmn: (Masternode) the deterministic masternode object """ - def register_new_dmn(self, idx, miner_idx, controller_idx, strType, + def register_new_dmn(self, idx, miner_idx, controller_idx, strType, transparent, payout_addr=None, outpoint=None, op_blskeys=None): # Prepare remote node assert idx != miner_idx @@ -1267,19 +1268,21 @@ def register_new_dmn(self, idx, miner_idx, controller_idx, strType, mn_node = self.nodes[idx] # Generate ip and addresses/keys - collateral_addr = controller_node.getnewaddress("mncollateral-%d" % idx) + collateral_addr = controller_node.getnewaddress("mncollateral-%d" % idx) if transparent else controller_node.getnewshieldaddress("shieldmncollateral-%d" % idx) if payout_addr is None: - payout_addr = collateral_addr - dmn = create_new_dmn(idx, controller_node, payout_addr, op_blskeys) + payout_addr = collateral_addr if transparent else controller_node.getnewaddress("mncollateral-%d" % idx) + dmn = create_new_dmn(idx, controller_node, payout_addr, op_blskeys, transparent) # Create ProRegTx self.log.info("Creating%s proRegTx for deterministic masternode idx=%d..." % ( " and funding" if strType == "fund" else "", idx)) if strType == "fund": + assert (transparent) self.protx_register_fund(miner_node, controller_node, dmn, collateral_addr) elif strType == "internal": - self.protx_register(miner_node, controller_node, dmn, collateral_addr) + self.protx_register(miner_node, controller_node, dmn, collateral_addr, transparent) elif strType == "external": + assert (transparent) self.protx_register_ext(miner_node, controller_node, dmn, outpoint, True) else: raise Exception("Type %s not available" % strType) @@ -1294,7 +1297,7 @@ def register_new_dmn(self, idx, miner_idx, controller_idx, strType, assert dmn.proTx in mn_node.protx_list(False) # check coin locking - assert is_coin_locked_by(controller_node, dmn.collateral) + assert is_coin_locked_by(controller_node, dmn.collateral, dmn.transparent) # check json payload against local dmn object self.check_proreg_payload(dmn, json_tx) @@ -1324,23 +1327,38 @@ def check_mn_list_on_node(self, idx, mns): assert_equal(mn.voting, mn2["dmnstate"]["votingAddress"]) assert_equal(mn.ipport, mn2["dmnstate"]["service"]) assert_equal(mn.payee, mn2["dmnstate"]["payoutAddress"]) - assert_equal(collateral["txid"], mn2["collateralHash"]) - assert_equal(collateral["vout"], mn2["collateralIndex"]) + assert_equal(mn.nullifier, mn2["nullifier"]) + # Usual story, For shield Dmns the value we store in collateral (i.e. the sapling outpoint referring to the note) + # Is different from the default null collateral in the ProRegTx + if mn.transparent: + assert_equal(collateral["txid"], mn2["collateralHash"]) + assert_equal(collateral["vout"], mn2["collateralIndex"]) + else: + assert_equal("%064x" % 0, mn2["collateralHash"]) + assert_equal(-1, mn2["collateralIndex"]) def check_proreg_payload(self, dmn, json_tx): assert "payload" in json_tx # null hash if funding collateral collateral_hash = 0 if int(json_tx["txid"], 16) == dmn.collateral.hash \ else dmn.collateral.hash + collateral_n = dmn.collateral.n + # null Outpoint if dmn is shielded + if not dmn.transparent: + collateral_hash = 0 + collateral_n = -1 pl = json_tx["payload"] - assert_equal(pl["version"], 1) + assert_equal(pl["version"], 2) assert_equal(pl["collateralHash"], "%064x" % collateral_hash) - assert_equal(pl["collateralIndex"], dmn.collateral.n) + assert_equal(pl["collateralIndex"], collateral_n) assert_equal(pl["service"], dmn.ipport) assert_equal(pl["ownerAddress"], dmn.owner) assert_equal(pl["votingAddress"], dmn.voting) assert_equal(pl["operatorPubKey"], dmn.operator_pk) assert_equal(pl["payoutAddress"], dmn.payee) + # fix the nullifier + dmn.nullifier = pl["nullifier"] + # ------------------------------------------------------ @@ -1370,17 +1388,18 @@ def __init__(self, class PivxDMNTestFramework(PivxTestFramework): def set_base_test_params(self): - # 1 miner, 1 controller, 6 remote mns + # 1 miner, 1 controller, 6 remote mns 2 of which shielded self.num_nodes = 8 self.minerPos = 0 self.controllerPos = 1 self.setup_clean_chain = True - def add_new_dmn(self, strType, op_keys=None, from_out=None): + def add_new_dmn(self, strType, transparent=True, op_keys=None, from_out=None): self.mns.append(self.register_new_dmn(2 + len(self.mns), self.minerPos, self.controllerPos, strType, + transparent, outpoint=from_out, op_blskeys=op_keys)) @@ -1440,10 +1459,14 @@ def setup_test(self): # Create 6 DMNs and init the remote nodes self.log.info("Initializing masternodes...") for _ in range(2): - self.add_new_dmn("internal") + self.add_new_dmn("internal", False) self.add_new_dmn("external") self.add_new_dmn("fund") assert_equal(len(self.mns), 6) + # Sanity check that we have 2 shielded masternodes + assert_equal(len(self.nodes[self.controllerPos].listlockunspent()["shielded"]), 2) + assert_equal(self.mns[0].transparent, False) + assert_equal(self.mns[3].transparent, False) for mn in self.mns: self.nodes[mn.idx].initmasternode(mn.operator_sk) time.sleep(1) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 408c178dbbe3ae..d1294c638da1eb 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -589,8 +589,9 @@ def get_coinstake_address(node, expected_utxos=None): return addrs[0] # Deterministic masternodes -def is_coin_locked_by(node, outpoint): - return outpoint.to_json() in node.listlockunspent()["transparent"] +def is_coin_locked_by(node, outpoint, transparent=True): + returnStr = "transparent" if transparent else "shielded" + return outpoint.to_json() in node.listlockunspent()[returnStr] def get_collateral_vout(json_tx): funding_txidn = -1 @@ -603,7 +604,7 @@ def get_collateral_vout(json_tx): # owner and voting keys are created from controller node. # operator keys are created, if operator_keys is None. -def create_new_dmn(idx, controller, payout_addr, operator_keys): +def create_new_dmn(idx, controller, payout_addr, operator_keys, transparent): port = p2p_port(idx) if idx <= MAX_NODES else p2p_port(MAX_NODES) + (idx - MAX_NODES) ipport = "127.0.0.1:" + str(port) owner_addr = controller.getnewaddress("mnowner-%d" % idx) @@ -615,7 +616,7 @@ def create_new_dmn(idx, controller, payout_addr, operator_keys): else: operator_pk = operator_keys[0] operator_sk = operator_keys[1] - return messages.Masternode(idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk) + return messages.Masternode(idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk, transparent) def spend_mn_collateral(spender, dmn): inputs = [dmn.collateral.to_json()] diff --git a/test/functional/tiertwo_deterministicmns.py b/test/functional/tiertwo_deterministicmns.py index 5176afe7f5bd3d..e109d227e3ab99 100755 --- a/test/functional/tiertwo_deterministicmns.py +++ b/test/functional/tiertwo_deterministicmns.py @@ -27,19 +27,20 @@ class DIP3Test(PivxTestFramework): def set_test_params(self): - # 1 miner, 1 controller, 6 remote mns - self.num_nodes = 8 + # 1 miner, 1 controller, 7 remote mns + self.num_nodes = 9 self.minerPos = 0 self.controllerPos = 1 self.setup_clean_chain = True self.extra_args = [["-nuparams=v5_shield:1", "-nuparams=v6_evo:130"]] * self.num_nodes self.extra_args[0].append("-sporkkey=932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi") - def add_new_dmn(self, mns, strType, op_keys=None, from_out=None): + def add_new_dmn(self, mns, strType, transparent=True, op_keys=None, from_out=None): mns.append(self.register_new_dmn(2 + len(mns), self.minerPos, self.controllerPos, strType, + transparent, outpoint=from_out, op_blskeys=op_keys)) @@ -120,7 +121,7 @@ def run_test(self): assert_raises_rpc_error(-1, "Evo upgrade is not active yet", self.add_new_dmn, mns, "internal") assert_raises_rpc_error(-1, "Evo upgrade is not active yet", self.add_new_dmn, mns, "fund") # Can create the raw proReg - dmn = create_new_dmn(2, controller, dummy_add, None) + dmn = create_new_dmn(2, controller, dummy_add, None, True) tx, sig = self.protx_register_ext(miner, controller, dmn, None, False) # but cannot send it assert_raises_rpc_error(-1, "Evo upgrade is not active yet", miner.protx_register_submit, tx, sig) @@ -128,6 +129,7 @@ def run_test(self): # Check that no coin has been locked by the controller yet assert_equal(len(controller.listlockunspent()["transparent"]), 0) + assert_equal(len(controller.listlockunspent()["shielded"]), 0) # DIP3 activates at block 130. miner.generate(130 - miner.getblockcount()) @@ -165,16 +167,20 @@ def run_test(self): time.sleep(1) # Now send the ProReg txes and check list - self.add_new_dmn(mns, "internal", op_keys[0]) - self.add_new_dmn(mns, "external", op_keys[1]) - self.add_new_dmn(mns, "fund", op_keys[2]) + self.add_new_dmn(mns, "internal", True, op_keys[0]) + self.add_new_dmn(mns, "external", True, op_keys[1]) + self.add_new_dmn(mns, "fund", True, op_keys[2]) + # Finally create the shielded DMN + self.add_new_dmn(mns, "internal", False) + self.nodes[mns[-1].idx].initmasternode(mns[-1].operator_sk) + miner.generate(2) self.sync_blocks() time.sleep(1) self.log.info("Masternodes started.") - # enabled/total masternodes: 6/6 - self.check_mn_enabled_count(6, 6) + # enabled/total masternodes: 7/7 + self.check_mn_enabled_count(7, 7) self.check_mn_list(mns) # Check status from remote nodes @@ -187,14 +193,19 @@ def run_test(self): self.restart_controller() time.sleep(1) for mn in mns: - if not is_coin_locked_by(controller, mn.collateral): + if not is_coin_locked_by(controller, mn.collateral, mn.transparent): raise Exception( "Collateral %s of mn with idx=%d is not locked" % (mn.collateral, mn.idx) ) self.log.info("Collaterals still locked.") - # Test collateral spending - dmn = mns.pop(randrange(len(mns))) # pop one at random + # Test collateral spending of a non-shield DMN + i = 0 + while (True): + i = randrange(len(mns)) + if mns[i].transparent: + break + dmn = mns.pop(i) # pop one at random self.log.info("Spending collateral of mn with idx=%d..." % dmn.idx) spend_txid = spend_mn_collateral(controller, dmn) self.sync_mempools([miner, controller]) @@ -202,41 +213,45 @@ def run_test(self): self.sync_blocks() assert_greater_than(miner.getrawtransaction(spend_txid, True)["confirmations"], 0) - # enabled/total masternodes: 5/5 - self.check_mn_enabled_count(5, 5) + # enabled/total masternodes: 6/6 + self.check_mn_enabled_count(6, 6) self.check_mn_list(mns) # Register dmn again, with the collateral of dmn2 # dmn must be added again to the list, and dmn2 must be removed - dmn2 = mns.pop(randrange(len(mns))) # pop one at random + while (True): + i = randrange(len(mns)) + if mns[i].transparent: + break + dmn2 = mns.pop(i) # pop one at random (again not the one shielded) dmn_keys = [dmn.operator_pk, dmn.operator_sk] dmn2_keys = [dmn2.operator_pk, dmn2.operator_sk] self.log.info("Reactivating node %d reusing the collateral of node %d..." % (dmn.idx, dmn2.idx)) - mns.append(self.register_new_dmn(dmn.idx, self.minerPos, self.controllerPos, "external", + mns.append(self.register_new_dmn(dmn.idx, self.minerPos, self.controllerPos, "external", True, outpoint=dmn2.collateral, op_blskeys=dmn_keys)) miner.generate(1) self.sync_blocks() - # enabled/total masternodes: 5/5 - self.check_mn_enabled_count(5, 5) + # enabled/total masternodes: 6/6 + self.check_mn_enabled_count(6, 6) self.check_mn_list(mns) # Now try to register dmn2 again with an already-used IP self.log.info("Trying duplicate IP...") rand_idx = mns[randrange(len(mns))].idx assert_raises_rpc_error(-1, "bad-protx-dup-IP-address", - self.register_new_dmn, rand_idx, self.minerPos, self.controllerPos, "fund", + self.register_new_dmn, rand_idx, self.minerPos, self.controllerPos, "fund", True, op_blskeys=dmn2_keys) # Now try with duplicate operator key self.log.info("Trying duplicate operator key...") - dmn2b = create_new_dmn(dmn2.idx, controller, dummy_add, dmn_keys) + dmn2b = create_new_dmn(dmn2.idx, controller, dummy_add, dmn_keys, True) assert_raises_rpc_error(-1, "bad-protx-dup-operator-key", self.protx_register_fund, miner, controller, dmn2b, dummy_add) # Now try with duplicate owner key self.log.info("Trying duplicate owner key...") - dmn2c = create_new_dmn(dmn2.idx, controller, dummy_add, dmn2_keys) + dmn2c = create_new_dmn(dmn2.idx, controller, dummy_add, dmn2_keys, True) dmn2c.owner = mns[randrange(len(mns))].owner assert_raises_rpc_error(-1, "bad-protx-dup-owner-key", self.protx_register_fund, miner, controller, dmn2c, dummy_add) @@ -244,7 +259,7 @@ def run_test(self): # Finally, register it properly. This time setting 10% of the reward for the operator op_rew = {"reward": 10.00, "address": self.nodes[dmn2.idx].getnewaddress()} self.log.info("Reactivating the node with a new registration (with operator reward)...") - dmn2c = create_new_dmn(dmn2.idx, controller, dummy_add, dmn2_keys) + dmn2c = create_new_dmn(dmn2.idx, controller, dummy_add, dmn2_keys, True) self.protx_register_fund(miner, controller, dmn2c, dummy_add, op_rew) mns.append(dmn2c) time.sleep(1) @@ -255,20 +270,20 @@ def run_test(self): assert_greater_than(json_tx['confirmations'], 0) self.check_proreg_payload(dmn2c, json_tx) - # enabled/total masternodes: 6/6 - self.check_mn_enabled_count(6, 6) - self.check_mn_list(mns) # 6 masternodes again + # enabled/total masternodes: 7/7 + self.check_mn_enabled_count(7, 7) + self.check_mn_list(mns) # 7 masternodes again # Test payments. - # Mine 12 blocks and check that each masternode has been paid exactly twice. - # Save last paid masternode. Check that it's the last paid also after the 12 blocks. + # Mine 14 blocks and check that each masternode has been paid exactly twice. + # Save last paid masternode. Check that it's the last paid also after the 14 blocks. # Note: dmn2 sends (2 * 0.3 PIV) to the operator, and (2 * 2.7 PIV) to the owner self.log.info("Testing masternode payments...") last_paid_mn = self.get_last_paid_mn() starting_balances = {"operator": self.get_addr_balance(self.nodes[dmn2c.idx], op_rew["address"])} for mn in mns: starting_balances[mn.payee] = self.get_addr_balance(controller, mn.payee) - miner.generate(12) + miner.generate(14) self.sync_blocks() for mn in mns: bal = self.get_addr_balance(controller, mn.payee) @@ -348,9 +363,9 @@ def run_test(self): miner.generate(1) self.sync_blocks() - # enabled/total masternodes: 5/6 + # enabled/total masternodes: 6/7 # Updating the operator key, clears the IP (and puts the mn in PoSe banned state) - self.check_mn_enabled_count(5, 6) + self.check_mn_enabled_count(6, 7) mns[0].ipport = "[::]:0" self.check_mn_list(mns) @@ -363,7 +378,7 @@ def run_test(self): miner.protx_update_registrar(mns[0].proTx, mns[0].operator_pk, "", "", ownerKey) miner.generate(1) self.sync_blocks() - self.check_mn_enabled_count(5, 6) # stil not valid until new operator sends proUpServ + self.check_mn_enabled_count(6, 7) # stil not valid until new operator sends proUpServ self.check_mn_list(mns) self.log.info("Update voting address...") mns[1].voting = controller.getnewaddress() @@ -371,7 +386,7 @@ def run_test(self): self.sync_mempools([miner, controller]) miner.generate(1) self.sync_blocks() - self.check_mn_enabled_count(5, 6) + self.check_mn_enabled_count(6, 7) self.check_mn_list(mns) self.log.info("Update payout address...") old_payee = mns[2].payee @@ -383,7 +398,7 @@ def run_test(self): old_mn2_bal = self.get_addr_balance(controller, old_payee) miner.generate(len(mns)-1) self.sync_blocks() - self.check_mn_enabled_count(5, 6) + self.check_mn_enabled_count(6, 7) self.check_mn_list(mns) # Check payment to new address self.log.info("Checking payments...") @@ -397,6 +412,7 @@ def run_test(self): assert_raises_rpc_error(-8, "not found", miner.protx_revoke, "%064x" % getrandbits(256)) self.log.info("Trying to revoke with invalid reason...") + assert (mns[3].transparent) assert_raises_rpc_error(-8, "invalid reason", controller.protx_revoke, mns[3].proTx, mns[3].operator_sk, 100) self.log.info("Revoke masternode...") # Do it from the remote node (so no need to pass the operator BLS secret key) @@ -411,28 +427,29 @@ def run_test(self): self.sync_mempools([miner, remote_node]) miner.generate(1) self.sync_blocks() - self.check_mn_enabled_count(4, 6) # mn3 has been revoked + self.check_mn_enabled_count(5, 7) # mn3 has been revoked self.check_mn_list(mns) old_mn3_bal = self.get_addr_balance(controller, mns[3].payee) # This time send the ProUpRev tx directly from the miner, giving the operator BLS secret key self.log.info("Revoke masternode (with external key)...") - miner.protx_revoke(mns[4].proTx, mns[4].operator_sk, 2) - mns[4].revoked() + assert (mns[5].transparent) + miner.protx_revoke(mns[5].proTx, mns[5].operator_sk, 2) + mns[5].revoked() miner.generate(1) self.sync_blocks() self.check_mn_list(mns) - old_mn4_bal = self.get_addr_balance(controller, mns[4].payee) + old_mn5_bal = self.get_addr_balance(controller, mns[5].payee) miner.generate(len(mns) + 1) self.sync_blocks() - # enabled/total masternodes: 3/6 (mn0 banned, mn3 and mn4 revoked) - self.check_mn_enabled_count(3, 6) + # enabled/total masternodes: 4/7 (mn0 banned, mn3 and mn5 revoked) + self.check_mn_enabled_count(4, 7) self.check_mn_list(mns) # Check (no) payments self.log.info("Checking payments...") assert_equal(self.get_addr_balance(controller, mns[3].payee), old_mn3_bal) - assert_equal(self.get_addr_balance(controller, mns[4].payee), old_mn4_bal) + assert_equal(self.get_addr_balance(controller, mns[5].payee), old_mn5_bal) # Test reviving a masternode self.log.info("Reviving a masternode...") @@ -446,13 +463,41 @@ def run_test(self): miner.generate(len(mns)) self.sync_blocks() - # enabled/total masternodes: 4/6 (mn3 is back) - self.check_mn_enabled_count(4, 6) + # enabled/total masternodes: 5/7 (mn3 is back) + self.check_mn_enabled_count(5, 7) self.check_mn_list(mns) self.log.info("Checking payments...") assert_equal(self.get_addr_balance(controller, mns[3].payee), old_mn3_bal + Decimal('3')) + s_dmn = [dmn for dmn in mns if not dmn.transparent][0] + controller.protx_register("%064x" % s_dmn.collateral.hash, s_dmn.collateral.n, False, "127.0.0.1:51476", controller.getnewaddress("s1"), controller.generateblskeypair()["public"], controller.getnewaddress("s2"), controller.getnewaddress("s3")) + self.sync_mempools([miner, controller]) + miner.generate(2) + self.sync_all() + + # Verify that we have only one shield masternode which is the one that we just created + for i in range(self.num_nodes): + assert_equal(len(self.nodes[i].listmasternodes("SHIELD")), 1) + assert_equal(self.nodes[i].listmasternodes("SHIELD")[0]["dmnstate"]["service"], "127.0.0.1:51476") + + # Try to spend the shield collateral + shield_collateral = {"txid": "%064x" % s_dmn.collateral.hash, "vout": s_dmn.collateral.n} + controller.lockunspent(True, False, [shield_collateral]) + + # Sanity check on the controller notes + assert_equal(len(controller.listlockunspent()["shielded"]), 0) + assert_equal(controller.getsaplingnotescount(), 1) + + recipient2 = [{"address": controller.getnewshieldaddress(), "amount": Decimal('90')}] + controller.shieldsendmany("from_shield", recipient2) + self.sync_mempools([miner, controller]) + miner.generate(2) + self.sync_all() + + # No more shield masternodes + for i in range(self.num_nodes): + assert_equal(len(self.nodes[i].listmasternodes("SHIELD")), 0) self.log.info("All good.")