Skip to content

Commit

Permalink
[Test] Update tiertwo_mn_compatibility and check winners
Browse files Browse the repository at this point in the history
  • Loading branch information
random-zebra committed May 23, 2021
1 parent ad2cc30 commit 3f16936
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 40 deletions.
22 changes: 22 additions & 0 deletions test/functional/test_framework/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1372,3 +1372,25 @@ def serialize(self):
r += self.block_transactions.serialize(with_witness=True)
return r


# PIVX Classes
class Masternode(object):
def __init__(self, idx, owner_addr, operator_addr, voting_addr, ipport, payout_addr, operator_key):
self.idx = idx
self.owner = owner_addr
self.operator = operator_addr
self.voting = voting_addr
self.ipport = ipport
self.payee = payout_addr
self.operator_key = operator_key
self.proTx = None
self.collateral = None

def __repr__(self):
return "Masternode(idx=%d, owner=%s, operator=%s, voting=%s, ip=%s, payee=%s, opkey=%s, protx=%s, collateral=%s)" % (
self.idx, str(self.owner), str(self.operator), str(self.voting), str(self.ipport),
str(self.payee), str(self.operator_key), str(self.proTx), str(self.collateral)
)

def __str__(self):
return self.__repr__()
159 changes: 152 additions & 7 deletions test/functional/test_framework/test_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,19 @@
connect_nodes,
connect_nodes_clique,
disconnect_nodes,
get_collateral_vout,
lock_utxo,
Decimal,
DEFAULT_FEE,
get_datadir_path,
hex_str_to_bytes,
bytes_to_hex_str,
initialize_datadir,
create_new_dmn,
p2p_port,
set_node_times,
SPORK_ACTIVATION_TIME,
SPORK_DEACTIVATION_TIME,
vZC_DENOMS,
wait_until,
)

class TestStatus(Enum):
Expand Down Expand Up @@ -1048,9 +1049,10 @@ def controller_start_masternode(self, mnOwner, masternodeAlias):

def send_pings(self, mnodes):
for node in mnodes:
sent = node.mnping()["sent"]
if sent != "YES" and "Too early to send Masternode Ping" not in sent:
raise AssertionError("Unable to send ping: \"sent\" = %s" % sent)
try:
node.mnping()["sent"]
except:
pass
time.sleep(1)


Expand All @@ -1068,7 +1070,7 @@ def stake_and_ping(self, node_id, num_blocks, with_ping_mns=[]):
if len(with_ping_mns) > 0:
self.send_pings(with_ping_mns)


# !TODO: remove after obsoleting legacy system
def setupDMN(self,
mnOwner,
miner,
Expand Down Expand Up @@ -1171,6 +1173,149 @@ def setupMasternode(self,
return COutPoint(collateralTxId, collateralTxId_n)


### ----------------------
### ----- DMN setup ------
### ----------------------

def connect_to_all(self, nodePos):
for i in range(self.num_nodes):
if i != nodePos and self.nodes[i] is not None:
connect_nodes(self.nodes[i], nodePos)

def assert_equal_for_all(self, expected, func_name, *args):
def not_found():
raise Exception("function %s not found!" % func_name)

assert_equal([getattr(x, func_name, not_found)(*args) for x in self.nodes],
[expected] * self.num_nodes)

"""
Create a ProReg tx, which has the collateral as one of its outputs
"""
def protx_register_fund(self, miner, controller, dmn, collateral_addr, lock=True):
# send to the owner the collateral tx + some dust for the ProReg and fee
funding_txid = miner.sendtoaddress(collateral_addr, Decimal('101'))
# confirm and verify reception
miner.generate(1)
self.sync_blocks([miner, controller])
assert_greater_than(controller.getrawtransaction(funding_txid, True)["confirmations"], 0)
# create and send the ProRegTx funding the collateral
dmn.proTx = controller.protx_register_fund(collateral_addr, dmn.ipport, dmn.owner,
dmn.operator, dmn.voting, dmn.payee)
dmn.collateral = COutPoint(int(dmn.proTx, 16),
get_collateral_vout(controller.getrawtransaction(dmn.proTx, True)))
if lock:
lock_utxo(controller, dmn.collateral)

"""
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, fLock):
# 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'))
# 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,
dmn.operator, dmn.voting, dmn.payee)
if fLock:
lock_utxo(controller, dmn.collateral)

"""
Create a ProReg tx, referencing a collateral signed externally (eg. HW wallets).
Here the controller node owns the collateral (and signs), but the miner creates the ProReg tx.
"""
def protx_register_ext(self, miner, controller, dmn, outpoint, fSubmit, fLock):
# send to the owner the collateral tx if the outpoint is not specified
if outpoint is None:
funding_txid = miner.sendtoaddress(controller.getnewaddress("collateral"), Decimal('100'))
# 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)
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,
dmn.operator, dmn.voting, dmn.payee)
sig = controller.signmessage(reg_tx["collateralAddress"], reg_tx["signMessage"])
if fSubmit:
if fLock:
lock_utxo(controller, dmn.collateral)
dmn.proTx = miner.protx_register_submit(reg_tx["tx"], sig)
else:
return reg_tx["tx"], sig

""" Create and register new deterministic masternode
:param idx: (int) index of the (remote) node in self.nodes
miner_idx: (int) index of the miner in self.nodes
controller_idx: (int) index of the controller in self.nodes
strType: (string) "fund"|"internal"|"external"
payout_addr: (string) payee address. If not specified, reuse the collateral address.
outpoint: (COutPoint) collateral outpoint to be used with "external".
It must be owned by the controller (proTx is sent from the miner).
If not provided, a new utxo is created, sending it from the miner.
op_addr_and_key: (list of strings) List with two entries, operator address (0) and private key (1).
If not provided, a new address-key pair is generated.
fLock: (boolean) lock the collateral output
:return: dmn: (Masternode) the deterministic masternode object
"""
def register_new_dmn(self, idx, miner_idx, controller_idx, strType,
payout_addr=None, outpoint=None, op_addr_and_key=None, fLock=True):
# Prepare remote node
assert idx != miner_idx
assert idx != controller_idx
miner_node = self.nodes[miner_idx]
controller_node = self.nodes[controller_idx]
mn_node = self.nodes[idx]

# Generate ip and addresses/keys
collateral_addr = controller_node.getnewaddress("mncollateral-%d" % idx)
if payout_addr is None:
payout_addr = collateral_addr
dmn = create_new_dmn(idx, controller_node, payout_addr, op_addr_and_key)

# Create ProRegTx
self.log.info("Creating%s proRegTx for deterministic masternode idx=%d..." % (
" and funding" if strType == "fund" else "", idx))
if strType == "fund":
self.protx_register_fund(miner_node, controller_node, dmn, collateral_addr, fLock)
elif strType == "internal":
self.protx_register(miner_node, controller_node, dmn, collateral_addr, fLock)
elif strType == "external":
self.protx_register_ext(miner_node, controller_node, dmn, outpoint, True, fLock)
else:
raise Exception("Type %s not available" % strType)
time.sleep(1)
self.sync_mempools([miner_node, controller_node])

# confirm and verify inclusion in list
miner_node.generate(1)
self.sync_blocks(self.nodes)
assert_greater_than(mn_node.getrawtransaction(dmn.proTx, 1)["confirmations"], 0)
assert dmn.proTx in mn_node.protx_list(False)
return dmn

def check_mn_list_on_node(self, idx, mns):
mnlist = self.nodes[idx].listmasternodes()
if len(mnlist) != len(mns):
raise Exception("Invalid mn list on node %d:\n%s\nExpected:%s" % (idx, str(mnlist), str(mns)))
protxs = [x["proTxHash"] for x in mnlist]
for mn in mns:
if mn.proTx not in protxs:
raise Exception("ProTx for mn %d (%s) not found in the list of node %d", mn.idx, mn.proTx, idx)


### ------------------------------------------------------

class SkipTest(Exception):
"""This exception is raised to skip a test"""
def __init__(self, message):
Expand All @@ -1180,7 +1325,7 @@ def __init__(self, message):
'''
PivxTestFramework extensions
'''

# !TODO: remove after obsoleting legacy system
class PivxTier2TestFramework(PivxTestFramework):

def set_test_params(self):
Expand Down
37 changes: 36 additions & 1 deletion test/functional/test_framework/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from subprocess import CalledProcessError
import time

from . import coverage
from . import coverage, messages
from .authproxy import AuthServiceProxy, JSONRPCException

logger = logging.getLogger("TestFramework.utils")
Expand Down Expand Up @@ -581,3 +581,38 @@ def get_coinstake_address(node, expected_utxos=None):
addrs = [a for a in set(addrs) if addrs.count(a) == expected_utxos]
assert(len(addrs) > 0)
return addrs[0]

# Deterministic masternodes

def lock_utxo(node, outpoint):
node.lockunspent(False, [{"txid": "%064x" % outpoint.hash, "vout": outpoint.n}])

def get_collateral_vout(json_tx):
funding_txidn = -1
for o in json_tx["vout"]:
if o["value"] == Decimal('100'):
funding_txidn = o["n"]
break
assert_greater_than(funding_txidn, -1)
return funding_txidn

# owner and voting keys are created from controller node.
# operator key and address are created, if operator_addr_and_key is None.
def create_new_dmn(idx, controller, payout_addr, operator_addr_and_key):
ipport = "127.0.0.1:" + str(p2p_port(idx))
owner_addr = controller.getnewaddress("mnowner-%d" % idx)
voting_addr = controller.getnewaddress("mnvoting-%d" % idx)
if operator_addr_and_key is None:
operator_addr = controller.getnewaddress("mnoperator-%d" % idx)
operator_key = controller.dumpprivkey(operator_addr)
else:
operator_addr = operator_addr_and_key[0]
operator_key = operator_addr_and_key[1]
return messages.Masternode(idx, owner_addr, operator_addr, voting_addr, ipport, payout_addr, operator_key)

def spend_mn_collateral(spender, dmn):
inputs = [{"txid": "%064x" % dmn.collateral.hash, "vout": dmn.collateral.n}]
outputs = {spender.getnewaddress(): Decimal('99.99')}
sig_res = spender.signrawtransaction(spender.createrawtransaction(inputs, outputs))
assert_equal(sig_res['complete'], True)
return spender.sendrawtransaction(sig_res['hex'])
Loading

0 comments on commit 3f16936

Please sign in to comment.