diff --git a/divi/qa/rpc-tests/custom_address_update.py b/divi/qa/rpc-tests/custom_address_update.py new file mode 100755 index 000000000..a37711305 --- /dev/null +++ b/divi/qa/rpc-tests/custom_address_update.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020-2021 The DIVI developers +# Distributed under the MIT/X11 software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# Tests that nodes with support for custom reward addresses are compatible +# to nodes without it before the change actually activates. For this, +# we run two masternodes (one with and one without support) and check +# that they can both see each other's masternode. +# +# We use five nodes: +# - node 0 is a masternode with default protocol (i.e. "new") +# - node 1 is a masternode with previous protocol (i.e. "old") +# - nodes 2-4 are just used to get above the "three full nodes" threshold + +from test_framework import BitcoinTestFramework +from util import * +from masternode import * + +import collections +import copy +import time + +OLD_PROTOCOL = 70915 +NEW_PROTOCOL = 71000 + + +class CustomAddressUpdateTest (MnTestFramework): + + def __init__ (self): + super ().__init__ () + self.base_args = ["-debug", "-nolistenonion", "-activeversion=%d" % OLD_PROTOCOL] + self.number_of_nodes = 5 + + def args_for (self, n): + res = copy.deepcopy (self.base_args) + if n == 1: + res.extend (["-protocolversion=%d" % OLD_PROTOCOL]) + return res + + def setup_network (self, config_line=None, extra_args=[]): + self.nodes = [ + start_node (i, self.options.tmpdir, extra_args=self.args_for (i)) + for i in range (self.number_of_nodes) + ] + self.setup = [None] * self.number_of_nodes + + # We want to work with mock times that are beyond the genesis + # block timestamp but before current time (so that nodes being + # started up and before they get on mocktime aren't rejecting + # the on-disk blockchain). + self.time = 1580000000 + assert self.time < time.time () + set_node_times (self.nodes, self.time) + + connect_nodes (self.nodes[0], 2) + connect_nodes (self.nodes[1], 2) + connect_nodes (self.nodes[2], 3) + connect_nodes (self.nodes[2], 4) + connect_nodes (self.nodes[3], 4) + + self.is_network_split = False + + def advance_time (self, dt=1): + """Advances mocktime by the given number of seconds.""" + + self.time += dt + set_node_times (self.nodes, self.time) + + def mine_blocks (self, n): + """Mines blocks with node 2.""" + + self.nodes[2].setgenerate(True, n) + sync_blocks (self.nodes) + + def run_test (self): + assert_equal (self.nodes[0].getnetworkinfo ()["protocolversion"], NEW_PROTOCOL) + assert_equal (self.nodes[1].getnetworkinfo ()["protocolversion"], OLD_PROTOCOL) + + self.fund_masternodes () + self.start_masternodes () + + def fund_masternodes (self): + print ("Funding masternodes...") + + # The collateral needs 15 confirmations, and the masternode broadcast + # signature must be later than that block's timestamp. Thus we start + # with a very early timestamp. + genesis = self.nodes[0].getblockhash (0) + genesisTime = self.nodes[0].getblockheader (genesis)["time"] + assert genesisTime < self.time + set_node_times (self.nodes, genesisTime) + + self.nodes[0].setgenerate (True, 1) + sync_blocks (self.nodes) + self.nodes[1].setgenerate (True, 1) + sync_blocks (self.nodes) + self.mine_blocks (25) + assert_equal (self.nodes[0].getbalance (), 1250) + assert_equal (self.nodes[1].getbalance (), 1250) + + self.setup_masternode (0, 0, "mn1", "copper") + self.setup_masternode (1, 1, "mn2", "copper") + self.mine_blocks (15) + set_node_times (self.nodes, self.time) + self.mine_blocks (1) + + def start_masternodes (self): + print ("Starting masternodes...") + + self.stop_masternode_daemons () + self.start_masternode_daemons () + self.connect_masternodes_to_peers ([2, 3, 4], updateMockTime=True) + + # Start the masternodes after the nodes are back up and connected + # (so they will receive each other's broadcast). + assert_equal (self.broadcast_start ("mn1", True)["status"], "success") + assert_equal (self.broadcast_start ("mn2", True)["status"], "success") + + # Check status of the masternodes themselves. + self.wait_for_masternodes_to_be_locally_active (updateMockTime=True) + + # Both masternodes should see each other, independent of the + # protocol version used. + def sortByTxhash (entry): + return entry["txhash"] + lists = [ + sorted (self.wait_for_mn_list_to_sync (self.nodes[i], 2), key=sortByTxhash) + for i in [0, 1] + ] + assert_equal (lists[0], lists[1]) + + lst = lists[0] + assert_equal (len (lst), 2) + for val in lst: + assert_equal (val["tier"], "COPPER") + assert_equal (val["status"], "ENABLED") + + +if __name__ == '__main__': + CustomAddressUpdateTest ().main () diff --git a/divi/qa/rpc-tests/masternode.py b/divi/qa/rpc-tests/masternode.py index 8242cd007..06135dac4 100644 --- a/divi/qa/rpc-tests/masternode.py +++ b/divi/qa/rpc-tests/masternode.py @@ -6,6 +6,7 @@ from test_framework import BitcoinTestFramework from util import * +import copy import time @@ -45,7 +46,7 @@ def __init__(self, setup_data,address=None): if address is not None: self.address = address -def setup_masternode(mempoolSync,controlNode, hostNode,alias,tier,hostIP, utxo = None): +def setup_masternode(mempoolSync,controlNode, hostNode,alias,tier,hostIP, utxo = None, rewardAddr=""): """Calls fundmasternode with the given data and returns the MnConfigLine instance.""" txdata = None @@ -62,7 +63,7 @@ def setup_masternode(mempoolSync,controlNode, hostNode,alias,tier,hostIP, utxo = pubkey = controlNode.validateaddress(address)["pubkey"] if sync_required: mempoolSync() - data = hostNode.setupmasternode(alias,txdata["txhash"],str(txdata["vout"]), pubkey, hostIP) + data = hostNode.setupmasternode(alias,txdata["txhash"],str(txdata["vout"]), pubkey, hostIP, rewardAddr) return MnSetupData (data,address) class MnTestFramework(BitcoinTestFramework): @@ -86,12 +87,12 @@ def for_each_masternode(self, masternode_function): return result - def setup_masternode(self,controlNodeIndex, hostNodeIndex,alias,tier,utxo=None): + def setup_masternode(self,controlNodeIndex, hostNodeIndex,alias,tier,utxo=None, rewardAddr=""): def mempoolSync(): sync_mempools (self.nodes) controlNode = self.nodes[controlNodeIndex] hostNode = self.nodes[hostNodeIndex] - self.setup[hostNodeIndex] = setup_masternode(mempoolSync,controlNode,hostNode,alias, tier,"localhost:%d" % p2p_port (hostNodeIndex),utxo=utxo) + self.setup[hostNodeIndex] = setup_masternode(mempoolSync,controlNode,hostNode,alias, tier,"localhost:%d" % p2p_port (hostNodeIndex),utxo=utxo, rewardAddr=rewardAddr) self.mn_control_node_indices[alias] = controlNodeIndex self.mn_host_node_indices[alias] = hostNodeIndex @@ -171,6 +172,11 @@ def wait_for_mnsync_on_nodes (self, updateMockTime = False): else: break + def args_for(self, n): + """Returns the arguments to use for node n. Defaults to the base args + but can be overridden as needed.""" + return copy.deepcopy(self.base_args) + def stop_masternode_daemons(self): def stop_masternode(nodeIndex): if self.nodes[nodeIndex] is not None: @@ -185,7 +191,7 @@ def start_masternode(nodeIndex): if self.nodes[nodeIndex] is not None: assert_equal("WARNING -- overwriting connection","") else: - args = self.base_args[:] + args = self.args_for(nodeIndex) conf = self.setup[nodeIndex].cfg args.append ("-masternode=%s" % conf.alias) self.nodes[nodeIndex] = start_node (nodeIndex, self.options.tmpdir, extra_args=args, mn_config_lines=[conf.line]) @@ -220,6 +226,7 @@ def find_mn(nodeIndex): assert_equal (local_status["status"], 4) assert_equal(local_status["txhash"],listedMN["txhash"]) assert_equal(local_status["outputidx"],listedMN["outidx"]) + assert_equal(local_status["rewardscript"],listedMN["rewardscript"]) node_found = True assert_equal(node_found,True) self.for_each_masternode(find_mn) @@ -237,4 +244,4 @@ def mock_wait(self,duration=1): for _ in range(duration): self.time +=1 set_node_times(self.nodes,self.time) - time.sleep(0.01) \ No newline at end of file + time.sleep(0.01) diff --git a/divi/qa/rpc-tests/mnoperation.py b/divi/qa/rpc-tests/mnoperation.py index e70732a65..e6dedcca1 100755 --- a/divi/qa/rpc-tests/mnoperation.py +++ b/divi/qa/rpc-tests/mnoperation.py @@ -57,6 +57,13 @@ def stop_node (self, n): stop_node (self.nodes[n], n) self.nodes[n] = None + def start_node (self, n): + """Starts a node.""" + + self.nodes[n] = start_node (n, self.options.tmpdir, extra_args=self.base_args) + for i in [3, 4, 5, 6]: + connect_nodes(self.nodes[n], i) + def advance_time (self, dt=1,delay=None): """Advances mocktime by the given number of seconds.""" @@ -83,8 +90,9 @@ def fund_masternodes (self): self.mine_blocks (25) assert_equal (self.nodes[0].getbalance (), 6250) + self.rewardAddr = self.nodes[0].getnewaddress ("reward2") self.setup_masternode(0,1,"mn1","copper") - self.setup_masternode(0,2,"mn2","silver") + self.setup_masternode(0,2,"mn2","silver",rewardAddr=self.rewardAddr) self.mine_blocks (15) set_node_times (self.nodes, self.time) self.mine_blocks (1) @@ -175,7 +183,7 @@ def payments_both_active (self): winners = self.verify_number_of_votes_exist_and_tally_winners(startHeight,endHeight, 2) addr1 = self.nodes[1].getmasternodestatus ()["addr"] - addr2 = self.nodes[2].getmasternodestatus ()["addr"] + addr2 = self.rewardAddr assert_equal (len (winners), 2) assert_greater_than (winners[addr1], 0) assert_greater_than (winners[addr2], 0) @@ -215,11 +223,22 @@ def payments_one_active (self): addr = self.nodes[1].getmasternodestatus ()["addr"] self.verify_number_of_votes_exist_and_tally_winners(startHeight,endHeight,1,addr) + def check_rewards (self): + print ("Checking rewards in wallet...") + + self.start_node (0) + sync_blocks (self.nodes) + + assert_greater_than (self.nodes[0].getbalance ("alloc->mn1"), 100) + assert_equal (self.nodes[0].getbalance ("alloc->mn2"), 300) + assert_greater_than (self.nodes[0].getbalance ("reward2"), 0) + def run_test (self): self.fund_masternodes () self.start_masternodes () self.payments_both_active () self.payments_one_active () + self.check_rewards () if __name__ == '__main__': MnStatusTest ().main () diff --git a/divi/qa/rpc-tests/mnvaults.py b/divi/qa/rpc-tests/mnvaults.py index 330736cd6..27e2ce4f7 100755 --- a/divi/qa/rpc-tests/mnvaults.py +++ b/divi/qa/rpc-tests/mnvaults.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2020 The DIVI developers +# Copyright (c) 2020-2021 The DIVI developers # Distributed under the MIT/X11 software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -28,7 +28,6 @@ class MnVaultsTest (MnTestFramework): def __init__ (self): super (MnVaultsTest, self).__init__ () self.base_args = ["-debug=masternode", "-debug=mocktime"] - self.cfg = None self.number_of_nodes=7 def connect_to_anchors(self,nodeID): @@ -124,10 +123,10 @@ def fund_vault (self): assert_equal (signed["complete"], True) self.unvaultTx = signed["hex"] - self.setup_masternode(2,1,"mn","copper",{"txhash":txid,"vout":vout}) - self.cfg = self.setup[1].cfg - # FIXME: Use reward address from node 0. - self.cfg.rewardAddr = addr + self.rewardAddr = self.nodes[0].getnewaddress ("reward") + self.setup_masternode(2, 1, "mn", "copper", + utxo={"txhash":txid,"vout":vout}, + rewardAddr=self.rewardAddr) self.stop_masternode_daemons() self.start_masternode_daemons(updateMockTime=True) self.connect_masternodes_to_peers(range(3,7),updateMockTime=True) @@ -204,16 +203,21 @@ def get_payments (self): self.advance_time (10) time.sleep(0.01) + # Mine some blocks normally to make sure the rewards are mature. + self.mine_blocks (20) + # Check that some payments were made. winners = self.nodes[3].getmasternodewinners () found = False for w in winners: - if w["winner"]["address"] == self.cfg.rewardAddr: + if w["winner"]["address"] == self.rewardAddr: found = True break assert_equal (found, True) - # FIXME: Check in wallet when we have a custom reward address. + # The payments should have been received at the reward address + # and in particular not be lost in the "destroyed" vault. + assert_greater_than (self.nodes[0].getbalance ("reward"), 0) def unvault (self): print ("Unvaulting the funds...") diff --git a/divi/qa/rpc-tests/test_runner.py b/divi/qa/rpc-tests/test_runner.py index 45aeec670..b478a2ce1 100755 --- a/divi/qa/rpc-tests/test_runner.py +++ b/divi/qa/rpc-tests/test_runner.py @@ -83,6 +83,7 @@ 'StakingVaultDeactivation.py', 'StakingVaultSpam.py', 'StakingVaultRedundancy.py', + 'custom_address_update.py', 'forknotify.py', 'getchaintips.py', 'httpbasics.py', diff --git a/divi/src/ForkActivation.cpp b/divi/src/ForkActivation.cpp index 66f868f75..8d4aedd64 100644 --- a/divi/src/ForkActivation.cpp +++ b/divi/src/ForkActivation.cpp @@ -28,7 +28,9 @@ const std::unordered_map> ACTIVATION_TIMES = { {Fork::UniformLotteryWinners, unixTimestampForDec31stMidnight}, /* FIXME: Schedule for a real time. */ {Fork::CheckLockTimeVerify, 2000000000}, - {Fork::DeprecateMasternodes,2000000000} + {Fork::DeprecateMasternodes,2000000000}, + /** FIXME: Set actual time once scheduled. */ + {Fork::CustomRewardAddresses, 2000000000}, }; const std::unordered_set> REQUIRE_BLOCK_INDEX_CONTEXT = { diff --git a/divi/src/ForkActivation.h b/divi/src/ForkActivation.h index d1617055c..28c87a709 100644 --- a/divi/src/ForkActivation.h +++ b/divi/src/ForkActivation.h @@ -24,6 +24,13 @@ enum Fork UniformLotteryWinners, CheckLockTimeVerify, DeprecateMasternodes, + + /** + * Custom reward addresses for masternodes. This is activated like other + * forks based on block time; but it only affects the network protocol + * we require from peers, not the actual consensus logic. + */ + CustomRewardAddresses, }; /** diff --git a/divi/src/MasternodeBroadcastFactory.cpp b/divi/src/MasternodeBroadcastFactory.cpp index f4cd33351..7fe7ba777 100644 --- a/divi/src/MasternodeBroadcastFactory.cpp +++ b/divi/src/MasternodeBroadcastFactory.cpp @@ -7,6 +7,7 @@ #include #include #include +#include