Skip to content
Merged
105 changes: 51 additions & 54 deletions test/functional/feature_dip3_deterministicmns.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@
#

from decimal import Decimal
from typing import List

from test_framework.blocktools import create_block_with_mnpayments
from test_framework.messages import tx_from_hex
from test_framework.test_framework import BitcoinTestFramework
from test_framework.test_framework import (
BitcoinTestFramework,
MasternodeInfo,
)
from test_framework.util import assert_equal, force_finish_mnsync, p2p_port, softfork_active

class Masternode(object):
pass

class DIP3Test(BitcoinTestFramework):
def set_test_params(self):
self.num_initial_mn = 11 # Should be >= 11 to make sure quorums are not always the same MNs
Expand Down Expand Up @@ -56,11 +57,11 @@ def run_test(self):
self.log.info("testing rejection of ProTx before dip3 activation")
assert self.nodes[0].getblockchaininfo()['blocks'] < 135

mns = []
mns: List[MasternodeInfo] = []

# prepare mn which should still be accepted later when dip3 activates
self.log.info("creating collateral for mn-before-dip3")
before_dip3_mn = self.prepare_mn(self.nodes[0], 1, 'mn-before-dip3')
before_dip3_mn: MasternodeInfo = self.prepare_mn(self.nodes[0], 1, 'mn-before-dip3')
self.create_mn_collateral(self.nodes[0], before_dip3_mn)
mns.append(before_dip3_mn)

Expand All @@ -82,7 +83,7 @@ def run_test(self):

self.log.info("registering MNs")
for i in range(self.num_initial_mn):
mn = self.prepare_mn(self.nodes[0], i + 2, "mn-%d" % i)
mn: MasternodeInfo = self.prepare_mn(self.nodes[0], i + 2, "mn-%d" % i)
mns.append(mn)

# start a few MNs before they are registered and a few after they are registered
Expand All @@ -93,12 +94,12 @@ def run_test(self):
# let a few of the protx MNs refer to the existing collaterals
fund = (i % 2) == 0
if fund:
self.log.info("register_fund %s" % mn.alias)
self.log.info(f"register_fund {mn.friendlyName}")
self.register_fund_mn(self.nodes[0], mn)
else:
self.log.info("create_collateral %s" % mn.alias)
self.log.info(f"create_collateral {mn.friendlyName}")
self.create_mn_collateral(self.nodes[0], mn)
self.log.info("register %s" % mn.alias)
self.log.info(f"register {mn.friendlyName}")
self.register_mn(self.nodes[0], mn)

self.generate(self.nodes[0], 1, sync_fun=self.no_op)
Expand All @@ -117,7 +118,7 @@ def run_test(self):
old_tip = self.nodes[0].getblockcount()
old_listdiff = self.nodes[0].protx("listdiff", 1, old_tip)
for i in range(spend_mns_count):
old_protx_hash = mns[i].protx_hash
old_protx_hash = mns[i].proTxHash
old_collateral_address = mns[i].collateral_address
old_blockhash = self.nodes[0].getbestblockhash()
old_rpc_info = self.nodes[0].protx("info", old_protx_hash)
Expand Down Expand Up @@ -187,7 +188,7 @@ def run_test(self):
mn = mns[i]
# a few of these will actually refer to old ProRegTx internal collaterals,
# which should work the same as external collaterals
new_mn = self.prepare_mn(self.nodes[0], mn.idx, mn.alias)
new_mn: MasternodeInfo = self.prepare_mn(self.nodes[0], mn.nodeIdx, mn.friendlyName)
new_mn.collateral_address = mn.collateral_address
new_mn.collateral_txid = mn.collateral_txid
new_mn.collateral_vout = mn.collateral_vout
Expand All @@ -196,46 +197,42 @@ def run_test(self):
mns[i] = new_mn
self.generate(self.nodes[0], 1)
self.assert_mnlists(mns)
self.log.info("restarting MN %s" % new_mn.alias)
self.stop_node(new_mn.idx)
self.log.info(f"restarting MN {mn.friendlyName}")
self.stop_node(new_mn.nodeIdx)
self.start_mn(new_mn)
self.sync_all()

self.log.info("testing masternode status updates")
# change voting address and see if changes are reflected in `masternode status` rpc output
mn = mns[0]
node = self.nodes[0]
old_dmnState = mn.node.masternode("status")["dmnState"]
old_dmnState = mn.get_node(self).masternode("status")["dmnState"]
old_voting_address = old_dmnState["votingAddress"]
new_voting_address = node.getnewaddress()
assert old_voting_address != new_voting_address
# also check if funds from payout address are used when no fee source address is specified
node.sendtoaddress(mn.rewards_address, 0.001)
node.protx('update_registrar' if softfork_active(node, 'v19') else 'update_registrar_legacy', mn.protx_hash, "", new_voting_address, "")
node.protx('update_registrar' if softfork_active(node, 'v19') else 'update_registrar_legacy', mn.proTxHash, "", new_voting_address, "")
self.generate(node, 1)
new_dmnState = mn.node.masternode("status")["dmnState"]
new_dmnState = mn.get_node(self).masternode("status")["dmnState"]
new_voting_address_from_rpc = new_dmnState["votingAddress"]
assert new_voting_address_from_rpc == new_voting_address
# make sure payoutAddress is the same as before
assert old_dmnState["payoutAddress"] == new_dmnState["payoutAddress"]

def prepare_mn(self, node, idx, alias):
mn = Masternode()
mn.idx = idx
mn.alias = alias
mn.p2p_port = p2p_port(mn.idx)
mn.operator_reward = (mn.idx % self.num_initial_mn)

def prepare_mn(self, node, idx, alias) -> MasternodeInfo:
blsKey = node.bls('generate') if softfork_active(node, 'v19') else node.bls('generate', True)
mn.fundsAddr = node.getnewaddress()
mn.ownerAddr = node.getnewaddress()
mn.operatorAddr = blsKey['public']
mn.votingAddr = mn.ownerAddr
mn.blsMnkey = blsKey['secret']

fundsAddr = node.getnewaddress()
ownerAddr = node.getnewaddress()
operator_reward = (idx % self.num_initial_mn)

# proTxHash, rewards_address, collateral_address, collateral_address, collateral_txid, collateral_vout are set later
mn = MasternodeInfo("", fundsAddr, ownerAddr, ownerAddr, "", operator_reward, blsKey['public'], blsKey['secret'], "", "", None,
p2p_port(idx), False)
mn.set_node(idx, alias)
return mn

def create_mn_collateral(self, node, mn):
def create_mn_collateral(self, node, mn: MasternodeInfo):
mn.collateral_address = node.getnewaddress()
mn.collateral_txid = node.sendtoaddress(mn.collateral_address, 1000)
mn.collateral_vout = None
Expand All @@ -249,13 +246,13 @@ def create_mn_collateral(self, node, mn):
assert mn.collateral_vout is not None

# register a protx MN and also fund it (using collateral inside ProRegTx)
def register_fund_mn(self, node, mn):
def register_fund_mn(self, node, mn: MasternodeInfo):
node.sendtoaddress(mn.fundsAddr, 1000.001)
mn.collateral_address = node.getnewaddress()
mn.rewards_address = node.getnewaddress()

mn.protx_hash = node.protx('register_fund' if softfork_active(node, 'v19') else 'register_fund_legacy', mn.collateral_address, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, mn.operator_reward, mn.rewards_address, mn.fundsAddr)
mn.collateral_txid = mn.protx_hash
mn.proTxHash = node.protx('register_fund' if softfork_active(node, 'v19') else 'register_fund_legacy', mn.collateral_address, '127.0.0.1:%d' % mn.nodePort, mn.ownerAddr, mn.pubKeyOperator, mn.votingAddr, mn.operator_reward, mn.rewards_address, mn.fundsAddr)
mn.collateral_txid = mn.proTxHash
mn.collateral_vout = None

rawtx = node.getrawtransaction(mn.collateral_txid, 1)
Expand All @@ -266,45 +263,45 @@ def register_fund_mn(self, node, mn):
assert mn.collateral_vout is not None

# create a protx MN which refers to an existing collateral
def register_mn(self, node, mn):
def register_mn(self, node, mn: MasternodeInfo):
node.sendtoaddress(mn.fundsAddr, 0.001)
mn.rewards_address = node.getnewaddress()

mn.protx_hash = node.protx('register' if softfork_active(node, 'v19') else 'register_legacy', mn.collateral_txid, mn.collateral_vout, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, mn.operator_reward, mn.rewards_address, mn.fundsAddr)
mn.proTxHash = node.protx('register' if softfork_active(node, 'v19') else 'register_legacy', mn.collateral_txid, mn.collateral_vout, '127.0.0.1:%d' % mn.nodePort, mn.ownerAddr, mn.pubKeyOperator, mn.votingAddr, mn.operator_reward, mn.rewards_address, mn.fundsAddr)
self.generate(node, 1, sync_fun=self.no_op)

def start_mn(self, mn):
if len(self.nodes) <= mn.idx:
self.add_nodes(mn.idx - len(self.nodes) + 1)
assert len(self.nodes) == mn.idx + 1
self.start_node(mn.idx, extra_args = self.extra_args + ['-masternodeblsprivkey=%s' % mn.blsMnkey])
force_finish_mnsync(self.nodes[mn.idx])
mn.node = self.nodes[mn.idx]
self.connect_nodes(mn.idx, 0)
def start_mn(self, mn: MasternodeInfo):
assert mn.nodeIdx is not None, "nodeIdx must be set before starting masternode"
if len(self.nodes) <= mn.nodeIdx:
self.add_nodes(mn.nodeIdx - len(self.nodes) + 1)
assert len(self.nodes) == mn.nodeIdx + 1
self.start_node(mn.nodeIdx, extra_args = self.extra_args + ['-masternodeblsprivkey=%s' % mn.keyOperator])
force_finish_mnsync(mn.get_node(self))
self.connect_nodes(mn.nodeIdx, 0)
self.sync_all()

def spend_mn_collateral(self, mn, with_dummy_input_output=False):
def spend_mn_collateral(self, mn: MasternodeInfo, with_dummy_input_output=False):
return self.spend_input(mn.collateral_txid, mn.collateral_vout, 1000, with_dummy_input_output)

def update_mn_payee(self, mn, payee):
def update_mn_payee(self, mn: MasternodeInfo, payee):
self.nodes[0].sendtoaddress(mn.fundsAddr, 0.001)
self.nodes[0].protx('update_registrar' if softfork_active(self.nodes[0], 'v19') else 'update_registrar_legacy', mn.protx_hash, '', '', payee, mn.fundsAddr)
self.nodes[0].protx('update_registrar' if softfork_active(self.nodes[0], 'v19') else 'update_registrar_legacy', mn.proTxHash, '', '', payee, mn.fundsAddr)
self.generate(self.nodes[0], 1)
info = self.nodes[0].protx('info', mn.protx_hash)
info = self.nodes[0].protx('info', mn.proTxHash)
assert info['state']['payoutAddress'] == payee

def test_protx_update_service(self, mn):
def test_protx_update_service(self, mn: MasternodeInfo):
self.nodes[0].sendtoaddress(mn.fundsAddr, 0.001)
self.nodes[0].protx('update_service', mn.protx_hash, '127.0.0.2:%d' % mn.p2p_port, mn.blsMnkey, "", mn.fundsAddr)
self.nodes[0].protx('update_service', mn.proTxHash, '127.0.0.2:%d' % mn.nodePort, mn.keyOperator, "", mn.fundsAddr)
self.generate(self.nodes[0], 1)
for node in self.nodes:
protx_info = node.protx('info', mn.protx_hash)
protx_info = node.protx('info', mn.proTxHash)
mn_list = node.masternode('list')
assert_equal(protx_info['state']['service'], '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)
assert_equal(protx_info['state']['service'], '127.0.0.2:%d' % mn.nodePort)
assert_equal(mn_list['%s-%d' % (mn.collateral_txid, mn.collateral_vout)]['address'], '127.0.0.2:%d' % mn.nodePort)

# undo
self.nodes[0].protx('update_service', mn.protx_hash, '127.0.0.1:%d' % mn.p2p_port, mn.blsMnkey, "", mn.fundsAddr)
self.nodes[0].protx('update_service', mn.proTxHash, '127.0.0.1:%d' % mn.nodePort, mn.keyOperator, "", mn.fundsAddr)
self.generate(self.nodes[0], 1, sync_fun=self.no_op)

def assert_mnlists(self, mns):
Expand Down
4 changes: 3 additions & 1 deletion test/functional/test_framework/test_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -1151,9 +1151,11 @@ def __init__(self, proTxHash, fundsAddr, ownerAddr, votingAddr, rewards_address,
self.nodePort = nodePort
self.evo = evo
self.nodeIdx = None
self.friendlyName=None

def set_node(self, nodeIdx):
def set_node(self, nodeIdx, friendlyName=None):
self.nodeIdx = nodeIdx
self.friendlyName = friendlyName or f"mn-{'evo' if self.evo else 'reg'}-{self.nodeIdx}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: why mn-reg and mn-evo instead mn and evo? they both are reg.


def get_node(self, test: BitcoinTestFramework) -> TestNode:
if self.nodeIdx is None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why exactly it's impossible to set .node after adding node to the list of test.nodes? looks a bit complex

Expand Down