Skip to content

Commit c6ad3e0

Browse files
committed
Functional test coverage
1 parent 9bf4592 commit c6ad3e0

File tree

7 files changed

+168
-98
lines changed

7 files changed

+168
-98
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
@@ -146,16 +146,9 @@ static inline bool filter(const std::string& str, const std::string& strFilter)
146146
return str.find(strFilter) != std::string::npos;
147147
}
148148

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

161154
UniValue listmasternodes(const JSONRPCRequest& request)
@@ -198,7 +191,7 @@ UniValue listmasternodes(const JSONRPCRequest& request)
198191
auto mnList = deterministicMNManager->GetListAtChainTip();
199192
mnList.ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) {
200193
UniValue obj = DmnToJson(dmn);
201-
if (filterMasternode(obj, strFilter, !dmn->IsPoSeBanned())) {
194+
if (filterMasternode(obj, strFilter, !dmn->IsPoSeBanned(), !dmn->nullifier.IsNull())) {
202195
ret.push_back(obj);
203196
}
204197
});
@@ -224,7 +217,7 @@ UniValue listmasternodes(const JSONRPCRequest& request)
224217
if (dmn) {
225218
UniValue obj = DmnToJson(dmn);
226219
bool fEnabled = !dmn->IsPoSeBanned();
227-
if (filterMasternode(obj, strFilter, fEnabled)) {
220+
if (filterMasternode(obj, strFilter, fEnabled, false)) {
228221
// Added for backward compatibility with legacy masternodes
229222
obj.pushKV("type", "deterministic");
230223
obj.pushKV("txhash", obj["proTxHash"].get_str());

test/functional/test_framework/messages.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -350,11 +350,12 @@ def __repr__(self):
350350

351351

352352
class COutPoint:
353-
__slots__ = ("hash", "n")
353+
__slots__ = ("hash", "n", "transparent")
354354

355-
def __init__(self, hash=0, n=0):
355+
def __init__(self, hash=0, n=0, transparent=True):
356356
self.hash = hash
357357
self.n = n
358+
self.transparent = transparent
358359

359360
def deserialize(self, f):
360361
self.hash = deser_uint256(f)
@@ -380,7 +381,8 @@ def __repr__(self):
380381
return "COutPoint(hash=%064x n=%i)" % (self.hash, self.n)
381382

382383
def to_json(self):
383-
return {"txid": "%064x" % self.hash, "vout": self.n}
384+
voutStr = "vout" if self.transparent else "vShieldedOutput"
385+
return {"txid": "%064x" % self.hash, voutStr: self.n}
384386

385387

386388
NullOutPoint = COutPoint(0, 0xffffffff)
@@ -1575,10 +1577,12 @@ def serialize(self):
15751577

15761578

15771579
# PIVX Classes
1580+
# NB: for shielded masternode the field collateral is the ShieldOutPoint of the shield collateral
1581+
# notice the difference from the ProRegTx in which the collateral is the Null default value
15781582
class Masternode(object):
1579-
__slots__ = ("idx", "owner", "operator_pk", "voting", "ipport", "payee", "operator_sk", "proTx", "collateral")
1583+
__slots__ = ("idx", "owner", "operator_pk", "voting", "ipport", "payee", "operator_sk", "proTx", "collateral", "nullifier", "transparent")
15801584

1581-
def __init__(self, idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk):
1585+
def __init__(self, idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk, transparent):
15821586
self.idx = idx
15831587
self.owner = owner_addr
15841588
self.operator_pk = operator_pk
@@ -1588,16 +1592,18 @@ def __init__(self, idx, owner_addr, operator_pk, voting_addr, ipport, payout_add
15881592
self.operator_sk = operator_sk
15891593
self.proTx = None
15901594
self.collateral = None
1595+
self.nullifier = "%064x" % 0 if transparent else None
1596+
self.transparent = transparent
15911597

15921598
def revoked(self):
15931599
self.ipport = "[::]:0"
15941600
self.operator_pk = ""
15951601
self.operator_sk = None
15961602

15971603
def __repr__(self):
1598-
return "Masternode(idx=%d, owner=%s, operator=%s, voting=%s, ip=%s, payee=%s, opkey=%s, protx=%s, collateral=%s)" % (
1604+
return "Masternode(idx=%d, owner=%s, operator=%s, voting=%s, ip=%s, payee=%s, opkey=%s, protx=%s, collateral=%s, transparent=%s)" % (
15991605
self.idx, str(self.owner), str(self.operator_pk), str(self.voting), str(self.ipport),
1600-
str(self.payee), str(self.operator_sk), str(self.proTx), str(self.collateral)
1606+
str(self.payee), str(self.operator_sk), str(self.proTx), str(self.collateral), str(self.transparent)
16011607
)
16021608

16031609
def __str__(self):

test/functional/test_framework/test_framework.py

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,13 +1117,13 @@ def setupDMN(self,
11171117
break
11181118
assert_greater_than(collateralTxId_n, -1)
11191119
assert_greater_than(json_tx["confirmations"], 0)
1120-
proTxId = mnOwner.protx_register(collateralTxId, collateralTxId_n, ipport, ownerAdd,
1120+
proTxId = mnOwner.protx_register(collateralTxId, collateralTxId_n, True, ipport, ownerAdd,
11211121
bls_keypair["public"], votingAdd, collateralAdd)
11221122
elif strType == "external":
11231123
self.log.info("Setting up ProRegTx with collateral externally-signed...")
11241124
# send the tx from the miner
11251125
payoutAdd = mnOwner.getnewaddress("payout")
1126-
register_res = miner.protx_register_prepare(outpoint.hash, outpoint.n, ipport, ownerAdd,
1126+
register_res = miner.protx_register_prepare(outpoint.hash, outpoint.n, True, ipport, ownerAdd,
11271127
bls_keypair["public"], votingAdd, payoutAdd)
11281128
self.log.info("ProTx prepared")
11291129
message_to_sign = register_res["signMessage"]
@@ -1218,19 +1218,20 @@ def protx_register_fund(self, miner, controller, dmn, collateral_addr, op_rew=No
12181218
Create a ProReg tx, which references an 100 PIV UTXO as collateral.
12191219
The controller node owns the collateral and creates the ProReg tx.
12201220
"""
1221-
def protx_register(self, miner, controller, dmn, collateral_addr):
1221+
def protx_register(self, miner, controller, dmn, collateral_addr, transparent):
12221222
# send to the owner the exact collateral tx amount
12231223
funding_txid = miner.sendtoaddress(collateral_addr, Decimal('100'))
12241224
# send another output to be used for the fee of the proReg tx
1225-
miner.sendtoaddress(collateral_addr, Decimal('1'))
1225+
feeAddr = collateral_addr if transparent else controller.getnewaddress("feeAddr")
1226+
miner.sendtoaddress(feeAddr, Decimal('1'))
12261227
# confirm and verify reception
12271228
miner.generate(1)
12281229
self.sync_blocks([miner, controller])
12291230
json_tx = controller.getrawtransaction(funding_txid, True)
12301231
assert_greater_than(json_tx["confirmations"], 0)
1231-
# create and send the ProRegTx
1232-
dmn.collateral = COutPoint(int(funding_txid, 16), get_collateral_vout(json_tx))
1233-
dmn.proTx = controller.protx_register(funding_txid, dmn.collateral.n, dmn.ipport, dmn.owner,
1232+
# 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))
1233+
dmn.collateral = COutPoint(int(funding_txid, 16), get_collateral_vout(json_tx)) if transparent else COutPoint(int(funding_txid, 16), 0, transparent)
1234+
dmn.proTx = controller.protx_register(funding_txid, dmn.collateral.n, transparent, dmn.ipport, dmn.owner,
12341235
dmn.operator_pk, dmn.voting, dmn.payee)
12351236

12361237
"""
@@ -1249,7 +1250,7 @@ def protx_register_ext(self, miner, controller, dmn, outpoint, fSubmit):
12491250
outpoint = COutPoint(int(funding_txid, 16), get_collateral_vout(json_tx))
12501251
dmn.collateral = outpoint
12511252
# Prepare the message to be signed externally by the owner of the collateral (the controller)
1252-
reg_tx = miner.protx_register_prepare("%064x" % outpoint.hash, outpoint.n, dmn.ipport, dmn.owner,
1253+
reg_tx = miner.protx_register_prepare("%064x" % outpoint.hash, outpoint.n, True, dmn.ipport, dmn.owner,
12531254
dmn.operator_pk, dmn.voting, dmn.payee)
12541255
sig = controller.signmessage(reg_tx["collateralAddress"], reg_tx["signMessage"])
12551256
if fSubmit:
@@ -1270,7 +1271,7 @@ def protx_register_ext(self, miner, controller, dmn, outpoint, fSubmit):
12701271
If not provided, a new address-key pair is generated.
12711272
:return: dmn: (Masternode) the deterministic masternode object
12721273
"""
1273-
def register_new_dmn(self, idx, miner_idx, controller_idx, strType,
1274+
def register_new_dmn(self, idx, miner_idx, controller_idx, strType, transparent,
12741275
payout_addr=None, outpoint=None, op_blskeys=None):
12751276
# Prepare remote node
12761277
assert idx != miner_idx
@@ -1280,19 +1281,21 @@ def register_new_dmn(self, idx, miner_idx, controller_idx, strType,
12801281
mn_node = self.nodes[idx]
12811282

12821283
# Generate ip and addresses/keys
1283-
collateral_addr = controller_node.getnewaddress("mncollateral-%d" % idx)
1284+
collateral_addr = controller_node.getnewaddress("mncollateral-%d" % idx) if transparent else controller_node.getnewshieldaddress("shieldmncollateral-%d" % idx)
12841285
if payout_addr is None:
1285-
payout_addr = collateral_addr
1286-
dmn = create_new_dmn(idx, controller_node, payout_addr, op_blskeys)
1286+
payout_addr = collateral_addr if transparent else controller_node.getnewaddress("mncollateral-%d" % idx)
1287+
dmn = create_new_dmn(idx, controller_node, payout_addr, op_blskeys, transparent)
12871288

12881289
# Create ProRegTx
12891290
self.log.info("Creating%s proRegTx for deterministic masternode idx=%d..." % (
12901291
" and funding" if strType == "fund" else "", idx))
12911292
if strType == "fund":
1293+
assert (transparent)
12921294
self.protx_register_fund(miner_node, controller_node, dmn, collateral_addr)
12931295
elif strType == "internal":
1294-
self.protx_register(miner_node, controller_node, dmn, collateral_addr)
1296+
self.protx_register(miner_node, controller_node, dmn, collateral_addr, transparent)
12951297
elif strType == "external":
1298+
assert (transparent)
12961299
self.protx_register_ext(miner_node, controller_node, dmn, outpoint, True)
12971300
else:
12981301
raise Exception("Type %s not available" % strType)
@@ -1307,7 +1310,7 @@ def register_new_dmn(self, idx, miner_idx, controller_idx, strType,
13071310
assert dmn.proTx in mn_node.protx_list(False)
13081311

13091312
# check coin locking
1310-
assert is_coin_locked_by(controller_node, dmn.collateral)
1313+
assert is_coin_locked_by(controller_node, dmn.collateral, dmn.transparent)
13111314

13121315
# check json payload against local dmn object
13131316
self.check_proreg_payload(dmn, json_tx)
@@ -1337,23 +1340,38 @@ def check_mn_list_on_node(self, idx, mns):
13371340
assert_equal(mn.voting, mn2["dmnstate"]["votingAddress"])
13381341
assert_equal(mn.ipport, mn2["dmnstate"]["service"])
13391342
assert_equal(mn.payee, mn2["dmnstate"]["payoutAddress"])
1340-
assert_equal(collateral["txid"], mn2["collateralHash"])
1341-
assert_equal(collateral["vout"], mn2["collateralIndex"])
1343+
assert_equal(mn.nullifier, mn2["nullifier"])
1344+
# Usual story, For shield Dmns the value we store in collateral (i.e. the sapling outpoint referring to the note)
1345+
# Is different from the default null collateral in the ProRegTx
1346+
if mn.transparent:
1347+
assert_equal(collateral["txid"], mn2["collateralHash"])
1348+
assert_equal(collateral["vout"], mn2["collateralIndex"])
1349+
else:
1350+
assert_equal("%064x" % 0, mn2["collateralHash"])
1351+
assert_equal(-1, mn2["collateralIndex"])
13421352

13431353
def check_proreg_payload(self, dmn, json_tx):
13441354
assert "payload" in json_tx
13451355
# null hash if funding collateral
13461356
collateral_hash = 0 if int(json_tx["txid"], 16) == dmn.collateral.hash \
13471357
else dmn.collateral.hash
1358+
collateral_n = dmn.collateral.n
1359+
# null Outpoint if dmn is shielded
1360+
if not dmn.transparent:
1361+
collateral_hash = 0
1362+
collateral_n = -1
13481363
pl = json_tx["payload"]
1349-
assert_equal(pl["version"], 1)
1364+
assert_equal(pl["version"], 2)
13501365
assert_equal(pl["collateralHash"], "%064x" % collateral_hash)
1351-
assert_equal(pl["collateralIndex"], dmn.collateral.n)
1366+
assert_equal(pl["collateralIndex"], collateral_n)
13521367
assert_equal(pl["service"], dmn.ipport)
13531368
assert_equal(pl["ownerAddress"], dmn.owner)
13541369
assert_equal(pl["votingAddress"], dmn.voting)
13551370
assert_equal(pl["operatorPubKey"], dmn.operator_pk)
13561371
assert_equal(pl["payoutAddress"], dmn.payee)
1372+
# fix the nullifier
1373+
dmn.nullifier = pl["nullifier"]
1374+
13571375

13581376
# ------------------------------------------------------
13591377

@@ -1383,17 +1401,18 @@ def __init__(self,
13831401
class PivxDMNTestFramework(PivxTestFramework):
13841402

13851403
def set_base_test_params(self):
1386-
# 1 miner, 1 controller, 6 remote mns
1404+
# 1 miner, 1 controller, 6 remote mns 2 of which shielded
13871405
self.num_nodes = 8
13881406
self.minerPos = 0
13891407
self.controllerPos = 1
13901408
self.setup_clean_chain = True
13911409

1392-
def add_new_dmn(self, strType, op_keys=None, from_out=None):
1410+
def add_new_dmn(self, strType, transparent=True, op_keys=None, from_out=None):
13931411
self.mns.append(self.register_new_dmn(2 + len(self.mns),
13941412
self.minerPos,
13951413
self.controllerPos,
13961414
strType,
1415+
transparent,
13971416
outpoint=from_out,
13981417
op_blskeys=op_keys))
13991418

@@ -1453,10 +1472,14 @@ def setup_test(self):
14531472
# Create 6 DMNs and init the remote nodes
14541473
self.log.info("Initializing masternodes...")
14551474
for _ in range(2):
1456-
self.add_new_dmn("internal")
1475+
self.add_new_dmn("internal", False)
14571476
self.add_new_dmn("external")
14581477
self.add_new_dmn("fund")
14591478
assert_equal(len(self.mns), 6)
1479+
# Sanity check that we have 2 shielded masternodes
1480+
assert_equal(len(self.nodes[self.controllerPos].listlockunspent()["shielded"]), 2)
1481+
assert_equal(self.mns[0].transparent, False)
1482+
assert_equal(self.mns[3].transparent, False)
14601483
for mn in self.mns:
14611484
self.nodes[mn.idx].initmasternode(mn.operator_sk)
14621485
time.sleep(1)

test/functional/test_framework/util.py

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

589589
# Deterministic masternodes
590-
def is_coin_locked_by(node, outpoint):
591-
return outpoint.to_json() in node.listlockunspent()["transparent"]
590+
def is_coin_locked_by(node, outpoint, transparent=True):
591+
returnStr = "transparent" if transparent else "shielded"
592+
return outpoint.to_json() in node.listlockunspent()[returnStr]
592593

593594
def get_collateral_vout(json_tx):
594595
funding_txidn = -1
@@ -601,7 +602,7 @@ def get_collateral_vout(json_tx):
601602

602603
# owner and voting keys are created from controller node.
603604
# operator keys are created, if operator_keys is None.
604-
def create_new_dmn(idx, controller, payout_addr, operator_keys):
605+
def create_new_dmn(idx, controller, payout_addr, operator_keys, transparent):
605606
port = p2p_port(idx) if idx <= MAX_NODES else p2p_port(MAX_NODES) + (idx - MAX_NODES)
606607
ipport = "127.0.0.1:" + str(port)
607608
owner_addr = controller.getnewaddress("mnowner-%d" % idx)
@@ -613,7 +614,7 @@ def create_new_dmn(idx, controller, payout_addr, operator_keys):
613614
else:
614615
operator_pk = operator_keys[0]
615616
operator_sk = operator_keys[1]
616-
return messages.Masternode(idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk)
617+
return messages.Masternode(idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk, transparent)
617618

618619
def spend_mn_collateral(spender, dmn):
619620
inputs = [dmn.collateral.to_json()]

0 commit comments

Comments
 (0)