Skip to content

Commit 8622e36

Browse files
committed
Functional test coverage
1 parent 3544a74 commit 8622e36

File tree

7 files changed

+166
-96
lines changed

7 files changed

+166
-96
lines changed

src/evo/providertx.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "bls/key_io.h"
99
#include "key_io.h"
10+
#include "primitives/transaction.h"
1011
#include "uint256.h"
1112

1213
std::string ProRegPL::MakeSignString() const
@@ -40,6 +41,7 @@ void ProRegPL::ToJson(UniValue& obj) const
4041
obj.pushKV("version", nVersion);
4142
obj.pushKV("collateralHash", collateralOutpoint.hash.ToString());
4243
obj.pushKV("collateralIndex", (int)collateralOutpoint.n);
44+
obj.pushKV("nullifier", shieldCollateral.input.nullifier.ToString());
4345
obj.pushKV("service", addr.ToString());
4446
obj.pushKV("ownerAddress", EncodeDestination(keyIDOwner));
4547
obj.pushKV("operatorPubKey", bls::EncodePublic(Params(), pubKeyOperator));

src/rpc/masternode.cpp

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,16 +145,9 @@ static inline bool filter(const std::string& str, const std::string& strFilter)
145145
return str.find(strFilter) != std::string::npos;
146146
}
147147

148-
static inline bool filterMasternode(const UniValue& dmno, const std::string& strFilter, bool fEnabled)
148+
static inline bool filterMasternode(const UniValue& dmno, const std::string& strFilter, bool fEnabled, bool isShield)
149149
{
150-
return strFilter.empty() || (filter("ENABLED", strFilter) && fEnabled)
151-
|| (filter("POSE_BANNED", strFilter) && !fEnabled)
152-
|| (filter(dmno["proTxHash"].get_str(), strFilter))
153-
|| (filter(dmno["collateralHash"].get_str(), strFilter))
154-
|| (filter(dmno["collateralAddress"].get_str(), strFilter))
155-
|| (filter(dmno["dmnstate"]["ownerAddress"].get_str(), strFilter))
156-
|| (filter(dmno["dmnstate"]["operatorPubKey"].get_str(), strFilter))
157-
|| (filter(dmno["dmnstate"]["votingAddress"].get_str(), strFilter));
150+
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));
158151
}
159152

160153
UniValue listmasternodes(const JSONRPCRequest& request)
@@ -197,7 +190,7 @@ UniValue listmasternodes(const JSONRPCRequest& request)
197190
auto mnList = deterministicMNManager->GetListAtChainTip();
198191
mnList.ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) {
199192
UniValue obj = DmnToJson(dmn);
200-
if (filterMasternode(obj, strFilter, !dmn->IsPoSeBanned())) {
193+
if (filterMasternode(obj, strFilter, !dmn->IsPoSeBanned(), !dmn->nullifier.IsNull())) {
201194
ret.push_back(obj);
202195
}
203196
});
@@ -223,7 +216,7 @@ UniValue listmasternodes(const JSONRPCRequest& request)
223216
if (dmn) {
224217
UniValue obj = DmnToJson(dmn);
225218
bool fEnabled = !dmn->IsPoSeBanned();
226-
if (filterMasternode(obj, strFilter, fEnabled)) {
219+
if (filterMasternode(obj, strFilter, fEnabled, false)) {
227220
// Added for backward compatibility with legacy masternodes
228221
obj.pushKV("type", "deterministic");
229222
obj.pushKV("txhash", obj["proTxHash"].get_str());

test/functional/test_framework/messages.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,10 @@ def __repr__(self):
334334

335335

336336
class COutPoint:
337-
def __init__(self, hash=0, n=0):
337+
def __init__(self, hash=0, n=0, transparent=True):
338338
self.hash = hash
339339
self.n = n
340+
self.transparent = transparent
340341

341342
def deserialize(self, f):
342343
self.hash = deser_uint256(f)
@@ -362,7 +363,8 @@ def __repr__(self):
362363
return "COutPoint(hash=%064x n=%i)" % (self.hash, self.n)
363364

364365
def to_json(self):
365-
return {"txid": "%064x" % self.hash, "vout": self.n}
366+
voutStr = "vout" if self.transparent else "vShieldedOutput"
367+
return {"txid": "%064x" % self.hash, voutStr: self.n}
366368

367369
NullOutPoint = COutPoint(0, 0xffffffff)
368370

@@ -1478,8 +1480,10 @@ def serialize(self):
14781480

14791481

14801482
# PIVX Classes
1483+
# NB: for shielded masternode the field collateral is the ShieldOutPoint of the shield collateral
1484+
# notice the difference from the ProRegTx in which the collateral is the Null default value
14811485
class Masternode(object):
1482-
def __init__(self, idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk):
1486+
def __init__(self, idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk, transparent):
14831487
self.idx = idx
14841488
self.owner = owner_addr
14851489
self.operator_pk = operator_pk
@@ -1489,16 +1493,18 @@ def __init__(self, idx, owner_addr, operator_pk, voting_addr, ipport, payout_add
14891493
self.operator_sk = operator_sk
14901494
self.proTx = None
14911495
self.collateral = None
1496+
self.nullifier = None
1497+
self.transparent = transparent
14921498

14931499
def revoked(self):
14941500
self.ipport = "[::]:0"
14951501
self.operator_pk = ""
14961502
self.operator_sk = None
14971503

14981504
def __repr__(self):
1499-
return "Masternode(idx=%d, owner=%s, operator=%s, voting=%s, ip=%s, payee=%s, opkey=%s, protx=%s, collateral=%s)" % (
1505+
return "Masternode(idx=%d, owner=%s, operator=%s, voting=%s, ip=%s, payee=%s, opkey=%s, protx=%s, collateral=%s, transparent=%s)" % (
15001506
self.idx, str(self.owner), str(self.operator_pk), str(self.voting), str(self.ipport),
1501-
str(self.payee), str(self.operator_sk), str(self.proTx), str(self.collateral)
1507+
str(self.payee), str(self.operator_sk), str(self.proTx), str(self.collateral), str(self.transparent)
15021508
)
15031509

15041510
def __str__(self):

test/functional/test_framework/test_framework.py

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,13 +1104,13 @@ def setupDMN(self,
11041104
break
11051105
assert_greater_than(collateralTxId_n, -1)
11061106
assert_greater_than(json_tx["confirmations"], 0)
1107-
proTxId = mnOwner.protx_register(collateralTxId, collateralTxId_n, ipport, ownerAdd,
1107+
proTxId = mnOwner.protx_register(collateralTxId, collateralTxId_n, True, ipport, ownerAdd,
11081108
bls_keypair["public"], votingAdd, collateralAdd)
11091109
elif strType == "external":
11101110
self.log.info("Setting up ProRegTx with collateral externally-signed...")
11111111
# send the tx from the miner
11121112
payoutAdd = mnOwner.getnewaddress("payout")
1113-
register_res = miner.protx_register_prepare(outpoint.hash, outpoint.n, ipport, ownerAdd,
1113+
register_res = miner.protx_register_prepare(outpoint.hash, outpoint.n, True, ipport, ownerAdd,
11141114
bls_keypair["public"], votingAdd, payoutAdd)
11151115
self.log.info("ProTx prepared")
11161116
message_to_sign = register_res["signMessage"]
@@ -1205,19 +1205,20 @@ def protx_register_fund(self, miner, controller, dmn, collateral_addr, op_rew=No
12051205
Create a ProReg tx, which references an 100 PIV UTXO as collateral.
12061206
The controller node owns the collateral and creates the ProReg tx.
12071207
"""
1208-
def protx_register(self, miner, controller, dmn, collateral_addr):
1208+
def protx_register(self, miner, controller, dmn, collateral_addr, transparent):
12091209
# send to the owner the exact collateral tx amount
12101210
funding_txid = miner.sendtoaddress(collateral_addr, Decimal('100'))
12111211
# send another output to be used for the fee of the proReg tx
1212-
miner.sendtoaddress(collateral_addr, Decimal('1'))
1212+
feeAddr = collateral_addr if transparent else controller.getnewaddress("feeAddr")
1213+
miner.sendtoaddress(feeAddr, Decimal('1'))
12131214
# confirm and verify reception
12141215
miner.generate(1)
12151216
self.sync_blocks([miner, controller])
12161217
json_tx = controller.getrawtransaction(funding_txid, True)
12171218
assert_greater_than(json_tx["confirmations"], 0)
1218-
# create and send the ProRegTx
1219-
dmn.collateral = COutPoint(int(funding_txid, 16), get_collateral_vout(json_tx))
1220-
dmn.proTx = controller.protx_register(funding_txid, dmn.collateral.n, dmn.ipport, dmn.owner,
1219+
# 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))
1220+
dmn.collateral = COutPoint(int(funding_txid, 16), get_collateral_vout(json_tx)) if transparent else COutPoint(int(funding_txid, 16), 0, transparent)
1221+
dmn.proTx = controller.protx_register(funding_txid, dmn.collateral.n, transparent, dmn.ipport, dmn.owner,
12211222
dmn.operator_pk, dmn.voting, dmn.payee)
12221223

12231224
"""
@@ -1236,7 +1237,7 @@ def protx_register_ext(self, miner, controller, dmn, outpoint, fSubmit):
12361237
outpoint = COutPoint(int(funding_txid, 16), get_collateral_vout(json_tx))
12371238
dmn.collateral = outpoint
12381239
# Prepare the message to be signed externally by the owner of the collateral (the controller)
1239-
reg_tx = miner.protx_register_prepare("%064x" % outpoint.hash, outpoint.n, dmn.ipport, dmn.owner,
1240+
reg_tx = miner.protx_register_prepare("%064x" % outpoint.hash, outpoint.n, True, dmn.ipport, dmn.owner,
12401241
dmn.operator_pk, dmn.voting, dmn.payee)
12411242
sig = controller.signmessage(reg_tx["collateralAddress"], reg_tx["signMessage"])
12421243
if fSubmit:
@@ -1257,7 +1258,7 @@ def protx_register_ext(self, miner, controller, dmn, outpoint, fSubmit):
12571258
If not provided, a new address-key pair is generated.
12581259
:return: dmn: (Masternode) the deterministic masternode object
12591260
"""
1260-
def register_new_dmn(self, idx, miner_idx, controller_idx, strType,
1261+
def register_new_dmn(self, idx, miner_idx, controller_idx, strType, transparent,
12611262
payout_addr=None, outpoint=None, op_blskeys=None):
12621263
# Prepare remote node
12631264
assert idx != miner_idx
@@ -1267,19 +1268,21 @@ def register_new_dmn(self, idx, miner_idx, controller_idx, strType,
12671268
mn_node = self.nodes[idx]
12681269

12691270
# Generate ip and addresses/keys
1270-
collateral_addr = controller_node.getnewaddress("mncollateral-%d" % idx)
1271+
collateral_addr = controller_node.getnewaddress("mncollateral-%d" % idx) if transparent else controller_node.getnewshieldaddress("shieldmncollateral-%d" % idx)
12711272
if payout_addr is None:
1272-
payout_addr = collateral_addr
1273-
dmn = create_new_dmn(idx, controller_node, payout_addr, op_blskeys)
1273+
payout_addr = collateral_addr if transparent else controller_node.getnewaddress("mncollateral-%d" % idx)
1274+
dmn = create_new_dmn(idx, controller_node, payout_addr, op_blskeys, transparent)
12741275

12751276
# Create ProRegTx
12761277
self.log.info("Creating%s proRegTx for deterministic masternode idx=%d..." % (
12771278
" and funding" if strType == "fund" else "", idx))
12781279
if strType == "fund":
1280+
assert (transparent)
12791281
self.protx_register_fund(miner_node, controller_node, dmn, collateral_addr)
12801282
elif strType == "internal":
1281-
self.protx_register(miner_node, controller_node, dmn, collateral_addr)
1283+
self.protx_register(miner_node, controller_node, dmn, collateral_addr, transparent)
12821284
elif strType == "external":
1285+
assert (transparent)
12831286
self.protx_register_ext(miner_node, controller_node, dmn, outpoint, True)
12841287
else:
12851288
raise Exception("Type %s not available" % strType)
@@ -1294,7 +1297,7 @@ def register_new_dmn(self, idx, miner_idx, controller_idx, strType,
12941297
assert dmn.proTx in mn_node.protx_list(False)
12951298

12961299
# check coin locking
1297-
assert is_coin_locked_by(controller_node, dmn.collateral)
1300+
assert is_coin_locked_by(controller_node, dmn.collateral, dmn.transparent)
12981301

12991302
# check json payload against local dmn object
13001303
self.check_proreg_payload(dmn, json_tx)
@@ -1324,23 +1327,38 @@ def check_mn_list_on_node(self, idx, mns):
13241327
assert_equal(mn.voting, mn2["dmnstate"]["votingAddress"])
13251328
assert_equal(mn.ipport, mn2["dmnstate"]["service"])
13261329
assert_equal(mn.payee, mn2["dmnstate"]["payoutAddress"])
1327-
assert_equal(collateral["txid"], mn2["collateralHash"])
1328-
assert_equal(collateral["vout"], mn2["collateralIndex"])
1330+
assert_equal(mn.nullifier, mn2["nullifier"])
1331+
# Usual story, For shield Dmns the value we store in collateral (i.e. the sapling outpoint referring to the note)
1332+
# Is different from the default null collateral in the ProRegTx
1333+
if mn.transparent:
1334+
assert_equal(collateral["txid"], mn2["collateralHash"])
1335+
assert_equal(collateral["vout"], mn2["collateralIndex"])
1336+
else:
1337+
assert_equal("%064x" % 0, mn2["collateralHash"])
1338+
assert_equal(-1, mn2["collateralIndex"])
13291339

13301340
def check_proreg_payload(self, dmn, json_tx):
13311341
assert "payload" in json_tx
13321342
# null hash if funding collateral
13331343
collateral_hash = 0 if int(json_tx["txid"], 16) == dmn.collateral.hash \
13341344
else dmn.collateral.hash
1345+
collateral_n = dmn.collateral.n
1346+
# null Outpoint if dmn is shielded
1347+
if not dmn.transparent:
1348+
collateral_hash = 0
1349+
collateral_n = -1
13351350
pl = json_tx["payload"]
1336-
assert_equal(pl["version"], 1)
1351+
assert_equal(pl["version"], 2)
13371352
assert_equal(pl["collateralHash"], "%064x" % collateral_hash)
1338-
assert_equal(pl["collateralIndex"], dmn.collateral.n)
1353+
assert_equal(pl["collateralIndex"], collateral_n)
13391354
assert_equal(pl["service"], dmn.ipport)
13401355
assert_equal(pl["ownerAddress"], dmn.owner)
13411356
assert_equal(pl["votingAddress"], dmn.voting)
13421357
assert_equal(pl["operatorPubKey"], dmn.operator_pk)
13431358
assert_equal(pl["payoutAddress"], dmn.payee)
1359+
# fix the nullifier
1360+
dmn.nullifier = pl["nullifier"]
1361+
13441362

13451363
# ------------------------------------------------------
13461364

@@ -1370,17 +1388,18 @@ def __init__(self,
13701388
class PivxDMNTestFramework(PivxTestFramework):
13711389

13721390
def set_base_test_params(self):
1373-
# 1 miner, 1 controller, 6 remote mns
1391+
# 1 miner, 1 controller, 6 remote mns 2 of which shielded
13741392
self.num_nodes = 8
13751393
self.minerPos = 0
13761394
self.controllerPos = 1
13771395
self.setup_clean_chain = True
13781396

1379-
def add_new_dmn(self, strType, op_keys=None, from_out=None):
1397+
def add_new_dmn(self, strType, transparent=True, op_keys=None, from_out=None):
13801398
self.mns.append(self.register_new_dmn(2 + len(self.mns),
13811399
self.minerPos,
13821400
self.controllerPos,
13831401
strType,
1402+
transparent,
13841403
outpoint=from_out,
13851404
op_blskeys=op_keys))
13861405

@@ -1440,10 +1459,14 @@ def setup_test(self):
14401459
# Create 6 DMNs and init the remote nodes
14411460
self.log.info("Initializing masternodes...")
14421461
for _ in range(2):
1443-
self.add_new_dmn("internal")
1462+
self.add_new_dmn("internal", False)
14441463
self.add_new_dmn("external")
14451464
self.add_new_dmn("fund")
14461465
assert_equal(len(self.mns), 6)
1466+
# Sanity check that we have 2 shielded masternodes
1467+
assert_equal(len(self.nodes[self.controllerPos].listlockunspent()["shielded"]), 2)
1468+
assert_equal(self.mns[0].transparent, False)
1469+
assert_equal(self.mns[3].transparent, False)
14471470
for mn in self.mns:
14481471
self.nodes[mn.idx].initmasternode(mn.operator_sk)
14491472
time.sleep(1)

test/functional/test_framework/util.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,9 @@ def get_coinstake_address(node, expected_utxos=None):
589589
return addrs[0]
590590

591591
# Deterministic masternodes
592-
def is_coin_locked_by(node, outpoint):
593-
return outpoint.to_json() in node.listlockunspent()["transparent"]
592+
def is_coin_locked_by(node, outpoint, transparent=True):
593+
returnStr = "transparent" if transparent else "shielded"
594+
return outpoint.to_json() in node.listlockunspent()[returnStr]
594595

595596
def get_collateral_vout(json_tx):
596597
funding_txidn = -1
@@ -603,7 +604,7 @@ def get_collateral_vout(json_tx):
603604

604605
# owner and voting keys are created from controller node.
605606
# operator keys are created, if operator_keys is None.
606-
def create_new_dmn(idx, controller, payout_addr, operator_keys):
607+
def create_new_dmn(idx, controller, payout_addr, operator_keys, transparent):
607608
port = p2p_port(idx) if idx <= MAX_NODES else p2p_port(MAX_NODES) + (idx - MAX_NODES)
608609
ipport = "127.0.0.1:" + str(port)
609610
owner_addr = controller.getnewaddress("mnowner-%d" % idx)
@@ -615,7 +616,7 @@ def create_new_dmn(idx, controller, payout_addr, operator_keys):
615616
else:
616617
operator_pk = operator_keys[0]
617618
operator_sk = operator_keys[1]
618-
return messages.Masternode(idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk)
619+
return messages.Masternode(idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk, transparent)
619620

620621
def spend_mn_collateral(spender, dmn):
621622
inputs = [dmn.collateral.to_json()]

0 commit comments

Comments
 (0)