diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 7690ee551..3d2916d0d 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -116,6 +116,7 @@ testScripts = [ 'fundrawtransaction.py', 'fundrawtransaction-hd.py', # vv Tests less than 2m vv + 'p2p-instantsend.py', 'wallet.py', 'wallet-accounts.py', 'wallet-dump.py', diff --git a/qa/rpc-tests/p2p-instantsend.py b/qa/rpc-tests/p2p-instantsend.py new file mode 100755 index 000000000..33aaa755b --- /dev/null +++ b/qa/rpc-tests/p2p-instantsend.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.mininode import * +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from time import * + +''' +InstantSendTest -- test InstantSend functionality (prevent doublespend for unconfirmed transactions) +''' + +MASTERNODE_COLLATERAL = 1000 + + +class MasternodeInfo: + def __init__(self, key, collateral_id, collateral_out): + self.key = key + self.collateral_id = collateral_id + self.collateral_out = collateral_out + + +class InstantSendTest(BitcoinTestFramework): + def __init__(self): + super().__init__() + self.mn_count = 10 + self.num_nodes = self.mn_count + 4 + self.mninfo = [] + self.setup_clean_chain = True + self.is_network_split = False + # set sender, receiver, isolated nodes + self.isolated_idx = self.num_nodes - 1 + self.receiver_idx = self.num_nodes - 2 + self.sender_idx = self.num_nodes - 3 + + def create_simple_node(self): + idx = len(self.nodes) + self.nodes.append(start_node(idx, self.options.tmpdir, + ["-debug"])) + for i in range(0, idx): + connect_nodes(self.nodes[i], idx) + + def get_mnconf_file(self): + return os.path.join(self.options.tmpdir, "node0/regtest/masternode.conf") + + def prepare_masternodes(self): + for idx in range(0, self.mn_count): + key = self.nodes[0].masternode("genkey") + address = self.nodes[0].getnewaddress() + txid = self.nodes[0].sendtoaddress(address, MASTERNODE_COLLATERAL) + txrow = self.nodes[0].getrawtransaction(txid, True) + collateral_vout = 0 + for vout_idx in range(0, len(txrow["vout"])): + vout = txrow["vout"][vout_idx] + if vout["value"] == MASTERNODE_COLLATERAL: + collateral_vout = vout_idx + self.nodes[0].lockunspent(False, + [{"txid": txid, "vout": collateral_vout}]) + self.mninfo.append(MasternodeInfo(key, txid, collateral_vout)) + + def write_mn_config(self): + conf = self.get_mnconf_file() + f = open(conf, 'a') + for idx in range(0, self.mn_count): + f.write("mn%d 127.0.0.1:%d %s %s %d\n" % (idx + 1, p2p_port(idx + 1), + self.mninfo[idx].key, + self.mninfo[idx].collateral_id, + self.mninfo[idx].collateral_out)) + f.close() + + def create_masternodes(self): + for idx in range(0, self.mn_count): + self.nodes.append(start_node(idx + 1, self.options.tmpdir, + ['-debug=masternode', '-externalip=127.0.0.1', + '-masternode=1', + '-masternodeprivkey=%s' % self.mninfo[idx].key + ])) + for i in range(0, idx + 1): + connect_nodes(self.nodes[i], idx + 1) + + def sentinel(self): + for i in range(1, self.mn_count + 1): + self.nodes[i].sentinelping("1.1.0") + + def setup_network(self): + self.nodes = [] + # create faucet node for collateral and transactions + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"])) + required_balance = MASTERNODE_COLLATERAL * self.mn_count + 1 + while self.nodes[0].getbalance() < required_balance: + set_mocktime(get_mocktime() + 1) + self.nodes[0].generate(1) + # create masternodes + self.prepare_masternodes() + self.write_mn_config() + stop_node(self.nodes[0], 0) + self.nodes[0] = start_node(0, self.options.tmpdir, ["-debug"]) + self.create_masternodes() + # create connected simple nodes + for i in range(0, self.num_nodes - self.mn_count - 1): + self.create_simple_node() + # feed the sender with some balance + sender_addr = self.nodes[self.sender_idx].getnewaddress() + self.nodes[0].sendtoaddress(sender_addr, 1) + # make sender funds mature for InstantSend + for i in range(0, 2): + set_mocktime(get_mocktime() + 1) + self.nodes[0].generate(1) + # sync nodes + self.sync_all() + set_mocktime(get_mocktime() + 1) + sync_masternodes(self.nodes) + for i in range(1, self.mn_count + 1): + res = self.nodes[0].masternode("start-alias", "mn%d" % i) + assert(res["result"] == 'successful') + sync_masternodes(self.nodes) + #self.sentinel() + mn_info = self.nodes[0].masternodelist("status") + assert(len(mn_info) == self.mn_count) + for status in mn_info.values(): + assert(status == 'ENABLED') + + def create_raw_trx(self, node_from, node_to, amount): + # fill inputs + inputs=[] + balances = node_from.listunspent() + for tx in balances: + if tx['amount'] > amount: + input = {} + input["txid"] = tx['txid'] + input['vout'] = tx['vout'] + in_amount = float(tx['amount']) + inputs.append(input) + break + assert(len(inputs) > 0) + # fill outputs + receiver_address = node_to.getnewaddress() + change_address = node_from.getnewaddress() + fee = 0.001 + outputs={} + outputs[receiver_address] = amount + outputs[change_address] = in_amount - amount - fee + rawtx = node_from.createrawtransaction(inputs, outputs) + return node_from.signrawtransaction(rawtx) + + def run_test(self): + # create doublepending transaction, but don't relay it + dblspnd_tx = self.create_raw_trx(self.nodes[self.sender_idx], + self.nodes[self.isolated_idx], + 0.5) + # stop one node to isolate it from network + stop_node(self.nodes[self.isolated_idx], self.isolated_idx) + # instantsend to receiver + receiver_addr = self.nodes[self.receiver_idx].getnewaddress() + is_id = self.nodes[self.sender_idx].instantsendtoaddress(receiver_addr, 0.9) + # wait for instantsend locks + start = time() + locked = False + while True: + is_trx = self.nodes[self.sender_idx].gettransaction(is_id) + if is_trx['instantlock']: + locked = True + break + if time() > start + 10: + break + assert(locked) + # start last node + self.nodes[self.isolated_idx] = start_node(self.isolated_idx, + self.options.tmpdir, + ["-debug"]) + # send doublespend transaction to isolated node + self.nodes[self.isolated_idx].sendrawtransaction(dblspnd_tx['hex']) + # generate block on isolated node with doublespend transaction + set_mocktime(get_mocktime() + 1) + self.nodes[self.isolated_idx].generate(1) + wrong_block = self.nodes[self.isolated_idx].getbestblockhash() + # connect isolated block to network + for i in range(0, self.isolated_idx): + connect_nodes(self.nodes[i], self.isolated_idx) + # check doublespend block is rejected by other nodes + timeout = 10 + for i in range(0, self.isolated_idx): + res = self.nodes[i].waitforblock(wrong_block, timeout) + assert (res['hash'] != wrong_block) + # wait for long time only for first node + timeout = 1 + + +if __name__ == '__main__': + InstantSendTest().main() diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index 86b074243..6cb94be59 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -30,7 +30,7 @@ from .authproxy import AuthServiceProxy, JSONRPCException COVERAGE_DIR = None # The maximum number of nodes a single test can spawn -MAX_NODES = 8 +MAX_NODES = 15 # Don't assign rpc or p2p ports lower than this PORT_MIN = 11000 # The number of ports to "reserve" for p2p and rpc, each diff --git a/src/chainparams.cpp b/src/chainparams.cpp index e01c9c8c0..23054a381 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -223,6 +223,7 @@ public: fMiningRequiresPeers = true; fDefaultConsistencyChecks = false; fRequireStandard = true; + fRequireRoutableExternalIP = true; fMineBlocksOnDemand = false; fAllowMultipleAddressesFromGroup = false; fAllowMultiplePorts = false; @@ -369,6 +370,7 @@ public: fMiningRequiresPeers = true; fDefaultConsistencyChecks = false; fRequireStandard = false; + fRequireRoutableExternalIP = true; fMineBlocksOnDemand = false; fAllowMultipleAddressesFromGroup = false; fAllowMultiplePorts = false; @@ -597,6 +599,7 @@ public: fMiningRequiresPeers = false; fDefaultConsistencyChecks = true; fRequireStandard = false; + fRequireRoutableExternalIP = false; fMineBlocksOnDemand = true; fAllowMultipleAddressesFromGroup = true; fAllowMultiplePorts = true; diff --git a/src/chainparams.h b/src/chainparams.h index 034e186bc..a0e0b07b3 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -69,6 +69,8 @@ public: bool DefaultConsistencyChecks() const { return fDefaultConsistencyChecks; } /** Policy: Filter transactions that do not match well-defined patterns */ bool RequireStandard() const { return fRequireStandard; } + /** Require addresses specified with "-externalip" parameter to be routable */ + bool RequireRoutableExternalIP() const { return fRequireRoutableExternalIP; } uint64_t PruneAfterHeight() const { return nPruneAfterHeight; } /** Make miner stop after a block is found. In RPC, don't return until nGenProcLimit blocks are generated */ bool MineBlocksOnDemand() const { return fMineBlocksOnDemand; } @@ -106,6 +108,7 @@ protected: bool fMiningRequiresPeers; bool fDefaultConsistencyChecks; bool fRequireStandard; + bool fRequireRoutableExternalIP; bool fMineBlocksOnDemand; bool fAllowMultipleAddressesFromGroup; bool fAllowMultiplePorts; diff --git a/src/masternodeman.cpp b/src/masternodeman.cpp index bec58a45d..815cd09f3 100644 --- a/src/masternodeman.cpp +++ b/src/masternodeman.cpp @@ -968,7 +968,9 @@ void CMasternodeMan::SyncAll(CNode* pnode, CConnman& connman) LOCK(cs); for (const auto& mnpair : mapMasternodes) { - if (mnpair.second.addr.IsRFC1918() || mnpair.second.addr.IsLocal()) continue; // do not send local network masternode + if (Params().RequireRoutableExternalIP() && + (mnpair.second.addr.IsRFC1918() || mnpair.second.addr.IsLocal())) + continue; // do not send local network masternode // NOTE: send masternode regardless of its current state, the other node will need it to verify old votes. LogPrint("masternode", "CMasternodeMan::%s -- Sending Masternode entry: masternode=%s addr=%s\n", __func__, mnpair.first.ToStringShort(), mnpair.second.addr.ToString()); PushDsegInvs(pnode, mnpair.second); diff --git a/src/net.cpp b/src/net.cpp index f0bd58abb..988230de8 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -205,7 +205,7 @@ void AdvertiseLocal(CNode *pnode) // learn a new local address bool AddLocal(const CService& addr, int nScore) { - if (!addr.IsRoutable()) + if (!addr.IsRoutable() && Params().RequireRoutableExternalIP()) return false; if (!fDiscover && nScore < LOCAL_MANUAL)