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..43679e2e1 --- /dev/null +++ b/divi/qa/rpc-tests/custom_address_update.py @@ -0,0 +1,179 @@ +#!/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 time + +OLD_PROTOCOL = 70915 +NEW_PROTOCOL = 71000 + + +def args_for (n): + base = ["-debug", "-nolistenonion", "-activeversion=%d" % OLD_PROTOCOL] + if n == 1: + base.extend (["-protocolversion=%d" % OLD_PROTOCOL]) + return base + + +class CustomAddressUpdateTest (BitcoinTestFramework): + + def setup_chain (self): + for i in range (5): + initialize_datadir (self.options.tmpdir, i) + + def setup_network (self, config_line=None, extra_args=[]): + self.nodes = [ + start_node (i, self.options.tmpdir, extra_args=args_for (i)) + for i in range (5) + ] + + # 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 start_node (self, n): + """Starts node n (0 or 1) with the proper arguments + and masternode config for it.""" + + assert n in [0, 1] + configs = [[c.getLine ()] for c in self.cfg] + + args = args_for (n) + args.append ("-masternode") + args.append ("-masternodeprivkey=%s" % self.cfg[n].privkey) + args.append ("-masternodeaddr=127.0.0.1:%d" % p2p_port (n)) + + self.nodes[n] = start_node (n, self.options.tmpdir, + extra_args=args, mn_config_lines=configs[n]) + self.nodes[n].setmocktime (self.time) + + for i in [2, 3, 4]: + connect_nodes (self.nodes[n], i) + sync_blocks (self.nodes) + + def stop_node (self, n): + """Stops node n.""" + + stop_node (self.nodes[n], n) + self.nodes[n] = None + + 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) + + id1 = self.nodes[0].allocatefunds ("masternode", "mn1", "copper")["txhash"] + id2 = self.nodes[1].allocatefunds ("masternode", "mn2", "copper")["txhash"] + sync_mempools (self.nodes) + self.mine_blocks (15) + set_node_times (self.nodes, self.time) + self.mine_blocks (1) + + self.cfg = [ + fund_masternode (self.nodes[0], "mn1", "copper", id1, "localhost:%d" % p2p_port (0)), + fund_masternode (self.nodes[1], "mn2", "copper", id2, "localhost:%d" % p2p_port (1)), + ] + + def start_masternodes (self): + print ("Starting masternodes...") + + for i in range (2): + self.stop_node (i) + self.start_node (i) + + # Start the masternodes after the nodes are back up and connected + # (so they will receive each other's broadcast). + for i in range (2): + res = self.nodes[i].startmasternode ("mn%d" % (i + 1)) + assert_equal (res, {"status": "success"}) + + # Check status of the masternodes themselves. + for i in [0, 1]: + data = self.nodes[i].getmasternodestatus () + assert_equal (data["status"], 4) + assert_equal (data["txhash"], self.cfg[i].txid) + assert_equal (data["outputidx"], self.cfg[i].vout) + assert_equal (data["message"], "Masternode successfully started") + + # Both masternodes should see each other, independent of the + # protocol version used. + lists = [{}] * 2 + for i in range (2): + cur = self.nodes[i].listmasternodes () + while len (cur) < 2: + time.sleep (0.1) + cur = self.nodes[i].listmasternodes () + for c in cur: + lists[i][c["txhash"]] = c + assert_equal (lists[0], lists[1]) + + lst = lists[0] + assert_equal (len (lst), 2) + for val in lst.values (): + 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 6fc101943..254446043 100644 --- a/divi/qa/rpc-tests/masternode.py +++ b/divi/qa/rpc-tests/masternode.py @@ -12,7 +12,7 @@ class MnConfigLine (object): def __init__ (self, line): parts = line.split (" ") - assert_equal (len (parts), 5) + assert len (parts) in [5, 6] self.line = line @@ -22,6 +22,21 @@ def __init__ (self, line): self.txid = parts[3] self.vout = int (parts[4]) + if len (parts) >= 6: + self.rewardAddr = parts[5] + else: + self.rewardAddr = None + + def getLine (self): + """Returns the config line as string, to put into masternode.conf.""" + + res = "%s %s %s %s %d" % (self.alias, self.ip, self.privkey, + self.txid, self.vout) + if self.rewardAddr is not None: + res += " %s" % self.rewardAddr + + return res + def fund_masternode (node, alias, tier, txid, ip): """Calls fundmasternode with the given data and returns the diff --git a/divi/qa/rpc-tests/mncollateral.py b/divi/qa/rpc-tests/mncollateral.py index 1614c74c5..ce6bb4305 100755 --- a/divi/qa/rpc-tests/mncollateral.py +++ b/divi/qa/rpc-tests/mncollateral.py @@ -59,6 +59,7 @@ def run_test (self): assert_equal (cfg.alias, "spent") assert_equal (cfg.ip, "1.2.3.4:51476") assert_equal (cfg.txid, txid) + assert_equal (cfg.rewardAddr, None) assert_equal (node.gettxout (cfg.txid, cfg.vout)["value"], 300) # It should still be possible to spend the coins, invalidating the diff --git a/divi/qa/rpc-tests/mnoperation.py b/divi/qa/rpc-tests/mnoperation.py index 57ccf0b47..af88f3e0a 100755 --- a/divi/qa/rpc-tests/mnoperation.py +++ b/divi/qa/rpc-tests/mnoperation.py @@ -69,9 +69,9 @@ def start_node (self, n): and masternode config for it.""" configs = [ - [c.line for c in self.cfg], - [self.cfg[0].line], - [self.cfg[1].line], + [c.getLine () for c in self.cfg], + [self.cfg[0].getLine ()], + [self.cfg[1].getLine ()], ] args = self.base_args[:] @@ -141,6 +141,8 @@ def fund_masternodes (self): fund_masternode (self.nodes[0], "mn2", "silver", id2, "localhost:%d" % p2p_port (2)), ] + self.cfg[1].rewardAddr = self.nodes[0].getnewaddress ("reward2") + def start_masternodes (self): print ("Starting masternodes...") @@ -187,8 +189,9 @@ def start_masternodes (self): assert_equal (lst[1]["tier"], "SILVER") for i in range (2): assert_equal (lst[i]["status"], "ENABLED") - assert_equal (lst[i]["addr"], - self.nodes[i + 1].getmasternodestatus ()["addr"]) + n = self.nodes[i + 1] + for key in ["addr", "rewardscript"]: + assert_equal (lst[i][key], n.getmasternodestatus ()[key]) assert_equal (lst[i]["txhash"], self.cfg[i].txid) assert_equal (lst[i]["outidx"], self.cfg[i].vout) @@ -263,7 +266,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.cfg[1].rewardAddr assert_equal (len (winners), 2) assert_greater_than (winners[addr1], 0) assert_greater_than (winners[addr2], 0) @@ -308,8 +311,9 @@ def check_rewards (self): self.start_node (0) sync_blocks (self.nodes) - assert_greater_than (self.nodes[0].getbalance ("alloc->mn1"), 0) - assert_greater_than (self.nodes[0].getbalance ("alloc->mn2"), 0) + 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) if __name__ == '__main__': diff --git a/divi/qa/rpc-tests/test_runner.py b/divi/qa/rpc-tests/test_runner.py index ab807ee38..05c581744 100755 --- a/divi/qa/rpc-tests/test_runner.py +++ b/divi/qa/rpc-tests/test_runner.py @@ -78,6 +78,7 @@ 'StakingVaultStaking.py', 'StakingVaultDeactivation.py', 'StakingVaultSpam.py', + 'custom_address_update.py', 'forknotify.py', 'getchaintips.py', 'httpbasics.py', diff --git a/divi/src/ForkActivation.cpp b/divi/src/ForkActivation.cpp index a05e2dd35..384108346 100644 --- a/divi/src/ForkActivation.cpp +++ b/divi/src/ForkActivation.cpp @@ -25,6 +25,9 @@ const std::unordered_map> ACTIVATION_TIMES = { {Fork::TestByTimestamp, 1000000000}, {Fork::HardenedStakeModifier, unixTimestampForDec31stMidnight}, {Fork::UniformLotteryWinners, unixTimestampForDec31stMidnight}, + + /** FIXME: Set actual time once scheduled. */ + {Fork::CustomRewardAddresses, 2000000000}, }; } // anonymous namespace diff --git a/divi/src/ForkActivation.h b/divi/src/ForkActivation.h index 79aeb7ba6..e1c43dc6d 100644 --- a/divi/src/ForkActivation.h +++ b/divi/src/ForkActivation.h @@ -22,6 +22,13 @@ enum Fork TestByTimestamp, HardenedStakeModifier, UniformLotteryWinners, + + /** + * 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 fac4d3730..4bc84412b 100644 --- a/divi/src/MasternodeBroadcastFactory.cpp +++ b/divi/src/MasternodeBroadcastFactory.cpp @@ -8,6 +8,7 @@ #include #include #include +#include