diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index e2272ad43..3a6412d08 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -51,6 +51,7 @@ testScripts=( 'getblocktemplate.py' 'bip65-cltv-p2p.py' 'bipdersig-p2p.py' + 'overwinter_peer_management.py' ); testScriptsExt=( 'getblocktemplate_longpoll.py' diff --git a/qa/rpc-tests/overwinter_peer_management.py b/qa/rpc-tests/overwinter_peer_management.py new file mode 100755 index 000000000..a7a71e52d --- /dev/null +++ b/qa/rpc-tests/overwinter_peer_management.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python2 +# Copyright (c) 2018 The Zcash 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 NodeConn, NodeConnCB, NetworkThread, \ + EarlyDisconnectError, msg_inv, mininode_lock, msg_ping, \ + MY_VERSION, OVERWINTER_PROTO_VERSION +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import initialize_chain_clean, start_nodes, \ + p2p_port, assert_equal + +import time + +# +# In this test we connect Sprout and Overwinter mininodes to a Zcashd node +# which will activate Overwinter at block 10. +# +# We test: +# 1. the mininodes stay connected to Zcash with Sprout consensus rules +# 2. when Overwinter activates, the Sprout mininodes are dropped +# 3. new Overwinter nodes can connect to Zcash +# 4. new Sprout nodes cannot connect to Zcash +# +# This test *does not* verify that prior to Overwinter activation, the Zcashd +# node will prefer connections with Overwinter nodes, with an eviction process +# that prioritizes Sprout connections. +# + + +class TestManager(NodeConnCB): + def __init__(self): + NodeConnCB.__init__(self) + self.create_callback_map() + + def on_close(self, conn): + pass + + def on_reject(self, conn, message): + conn.rejectMessage = message + + +class OverwinterPeerManagementTest(BitcoinTestFramework): + + def setup_chain(self): + print "Initializing test directory "+self.options.tmpdir + initialize_chain_clean(self.options.tmpdir, 1) + + def setup_network(self): + self.nodes = start_nodes(1, self.options.tmpdir, + extra_args=[['-nuparams=5ba81b19:10', '-debug', '-whitelist=127.0.0.1']]) + + def run_test(self): + test = TestManager() + + # Launch 10 Sprout and 10 Overwinter mininodes + nodes = [] + for x in xrange(10): + nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", False)) + nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", True)) + + # Start up network handling in another thread + NetworkThread().start() + + # Sprout consensus rules apply at block height 9 + self.nodes[0].generate(9) + assert_equal(9, self.nodes[0].getblockcount()) + + # Verify mininodes are still connected to zcashd node + peerinfo = self.nodes[0].getpeerinfo() + versions = [x["version"] for x in peerinfo] + assert_equal(10, versions.count(MY_VERSION)) + assert_equal(10, versions.count(OVERWINTER_PROTO_VERSION)) + + # Overwinter consensus rules activate at block height 10 + self.nodes[0].generate(1) + assert_equal(10, self.nodes[0].getblockcount()) + + # Mininodes send ping message to zcashd node. + pingCounter = 1 + for node in nodes: + node.send_message(msg_ping(pingCounter)) + pingCounter = pingCounter + 1 + + time.sleep(3) + + # Verify Sprout mininodes have been dropped and Overwinter mininodes are still connected. + peerinfo = self.nodes[0].getpeerinfo() + versions = [x["version"] for x in peerinfo] + assert_equal(0, versions.count(MY_VERSION)) + assert_equal(10, versions.count(OVERWINTER_PROTO_VERSION)) + + # Extend the Overwinter chain with another block. + self.nodes[0].generate(1) + + # Connect a new Overwinter mininode to the zcashd node, which is accepted. + nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", True)) + time.sleep(3) + assert_equal(11, len(self.nodes[0].getpeerinfo())) + + # Try to connect a new Sprout mininode to the zcashd node, which is rejected. + sprout = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", False) + nodes.append(sprout) + time.sleep(3) + assert("Version must be 170003 or greater" in str(sprout.rejectMessage)) + + # Verify that only Overwinter mininodes are connected. + peerinfo = self.nodes[0].getpeerinfo() + versions = [x["version"] for x in peerinfo] + assert_equal(0, versions.count(MY_VERSION)) + assert_equal(11, versions.count(OVERWINTER_PROTO_VERSION)) + + for node in nodes: + node.disconnect_node() + +if __name__ == '__main__': + OverwinterPeerManagementTest().main() diff --git a/qa/rpc-tests/test_framework/mininode.py b/qa/rpc-tests/test_framework/mininode.py index c74d3f931..3c9821259 100755 --- a/qa/rpc-tests/test_framework/mininode.py +++ b/qa/rpc-tests/test_framework/mininode.py @@ -39,6 +39,7 @@ from .equihash import ( zcash_person, ) +OVERWINTER_PROTO_VERSION = 170003 BIP0031_VERSION = 60000 MY_VERSION = 170002 # past bip-31 for ping/pong MY_SUBVERSION = "/python-mininode-tester:0.0.1/" @@ -868,8 +869,12 @@ class CAlert(object): class msg_version(object): command = "version" - def __init__(self): - self.nVersion = MY_VERSION + def __init__(self, overwintered=False): + if overwintered: + self.nVersion = OVERWINTER_PROTO_VERSION + else: + self.nVersion = MY_VERSION + self.nServices = 1 self.nTime = time.time() self.addrTo = CAddress() @@ -1326,7 +1331,7 @@ class NodeConn(asyncore.dispatcher): "regtest": "\xaa\xe8\x3f\x5f" # regtest } - def __init__(self, dstaddr, dstport, rpc, callback, net="regtest"): + def __init__(self, dstaddr, dstport, rpc, callback, net="regtest", overwintered=False): asyncore.dispatcher.__init__(self, map=mininode_socket_map) self.log = logging.getLogger("NodeConn(%s:%d)" % (dstaddr, dstport)) self.dstaddr = dstaddr @@ -1343,7 +1348,7 @@ class NodeConn(asyncore.dispatcher): self.disconnect = False # stuff version msg into sendbuf - vt = msg_version() + vt = msg_version(overwintered) vt.addrTo.ip = self.dstaddr vt.addrTo.port = self.dstport vt.addrFrom.ip = "0.0.0.0" diff --git a/src/chainparams.cpp b/src/chainparams.cpp index a0c73dc37..f11d53460 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -95,10 +95,13 @@ public: consensus.nPowMaxAdjustDown = 32; // 32% adjustment down consensus.nPowMaxAdjustUp = 16; // 16% adjustment up consensus.nPowTargetSpacing = 2.5 * 60; + consensus.vUpgrades[Consensus::BASE_SPROUT].nProtocolVersion = 170002; consensus.vUpgrades[Consensus::BASE_SPROUT].nActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE; + consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nProtocolVersion = 170002; consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.vUpgrades[Consensus::UPGRADE_OVERWINTER].nProtocolVersion = 170004; consensus.vUpgrades[Consensus::UPGRADE_OVERWINTER].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; @@ -253,10 +256,13 @@ public: consensus.nPowMaxAdjustDown = 32; // 32% adjustment down consensus.nPowMaxAdjustUp = 16; // 16% adjustment up consensus.nPowTargetSpacing = 2.5 * 60; + consensus.vUpgrades[Consensus::BASE_SPROUT].nProtocolVersion = 170002; consensus.vUpgrades[Consensus::BASE_SPROUT].nActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE; + consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nProtocolVersion = 170002; consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.vUpgrades[Consensus::UPGRADE_OVERWINTER].nProtocolVersion = 170003; consensus.vUpgrades[Consensus::UPGRADE_OVERWINTER].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; @@ -360,10 +366,13 @@ public: consensus.nPowMaxAdjustDown = 0; // Turn off adjustment down consensus.nPowMaxAdjustUp = 0; // Turn off adjustment up consensus.nPowTargetSpacing = 2.5 * 60; + consensus.vUpgrades[Consensus::BASE_SPROUT].nProtocolVersion = 170002; consensus.vUpgrades[Consensus::BASE_SPROUT].nActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE; + consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nProtocolVersion = 170002; consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.vUpgrades[Consensus::UPGRADE_OVERWINTER].nProtocolVersion = 170003; consensus.vUpgrades[Consensus::UPGRADE_OVERWINTER].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; diff --git a/src/consensus/params.h b/src/consensus/params.h index 53f8609d5..11504dbc8 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -28,6 +28,11 @@ enum UpgradeIndex { }; struct NetworkUpgrade { + /** + * The first protocol version which will understand the new consensus rules + */ + int nProtocolVersion; + /** * Height of the first block for which the new consensus rules will be active */ diff --git a/src/main.cpp b/src/main.cpp index 9e41d9c4e..5d45a29ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4721,6 +4721,19 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, return false; } + // When Overwinter is active, reject incoming connections from non-Overwinter nodes + const Consensus::Params& params = Params().GetConsensus(); + if (NetworkUpgradeActive(GetHeight(), params, Consensus::UPGRADE_OVERWINTER) + && pfrom->nVersion < params.vUpgrades[Consensus::UPGRADE_OVERWINTER].nProtocolVersion) + { + LogPrintf("peer=%d using obsolete version %i; disconnecting\n", pfrom->id, pfrom->nVersion); + pfrom->PushMessage("reject", strCommand, REJECT_OBSOLETE, + strprintf("Version must be %d or greater", + params.vUpgrades[Consensus::UPGRADE_OVERWINTER].nProtocolVersion)); + pfrom->fDisconnect = true; + return false; + } + if (pfrom->nVersion == 10300) pfrom->nVersion = 300; if (!vRecv.empty()) @@ -4839,6 +4852,22 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, } + // Disconnect existing peer connection when: + // 1. The version message has been received + // 2. Overwinter is active + // 3. Peer version is pre-Overwinter + else if (NetworkUpgradeActive(GetHeight(), chainparams.GetConsensus(), Consensus::UPGRADE_OVERWINTER) + && (pfrom->nVersion < chainparams.GetConsensus().vUpgrades[Consensus::UPGRADE_OVERWINTER].nProtocolVersion)) + { + LogPrintf("peer=%d using obsolete version %i; disconnecting\n", pfrom->id, pfrom->nVersion); + pfrom->PushMessage("reject", strCommand, REJECT_OBSOLETE, + strprintf("Version must be %d or greater", + chainparams.GetConsensus().vUpgrades[Consensus::UPGRADE_OVERWINTER].nProtocolVersion)); + pfrom->fDisconnect = true; + return false; + } + + else if (strCommand == "addr") { vector vAddr; diff --git a/src/net.cpp b/src/net.cpp index 05f427707..72ef82d5a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -7,6 +7,7 @@ #include "config/bitcoin-config.h" #endif +#include "main.h" #include "net.h" #include "addrman.h" @@ -812,6 +813,34 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) { // Protect connections with certain characteristics + // Check version of eviction candidates and prioritize nodes which do not support network upgrade. + std::vector vTmpEvictionCandidates; + int height; + { + LOCK(cs_main); + height = chainActive.Height(); + } + + const Consensus::Params& params = Params().GetConsensus(); + int nActivationHeight = params.vUpgrades[Consensus::UPGRADE_OVERWINTER].nActivationHeight; + + if (nActivationHeight > 0 && + height < nActivationHeight && + height >= nActivationHeight - NETWORK_UPGRADE_PEER_PREFERENCE_BLOCK_PERIOD) + { + // Find any nodes which don't support Overwinter protocol version + BOOST_FOREACH(const CNodeRef &node, vEvictionCandidates) { + if (node->nVersion < params.vUpgrades[Consensus::UPGRADE_OVERWINTER].nProtocolVersion) { + vTmpEvictionCandidates.push_back(node); + } + } + + // Prioritize these nodes by replacing eviction set with them + if (vTmpEvictionCandidates.size() > 0) { + vEvictionCandidates = vTmpEvictionCandidates; + } + } + // Deterministically select 4 peers to protect by netgroup. // An attacker cannot predict which netgroups will be protected. static CompareNetGroupKeyed comparerNetGroupKeyed; diff --git a/src/net.h b/src/net.h index 6fd49536d..55190baa5 100644 --- a/src/net.h +++ b/src/net.h @@ -57,6 +57,8 @@ static const size_t MAPASKFOR_MAX_SZ = MAX_INV_SZ; static const size_t SETASKFOR_MAX_SZ = 2 * MAX_INV_SZ; /** The maximum number of peer connections to maintain. */ static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 125; +/** The period before a network upgrade activates, where connections to upgrading peers are preferred (in blocks). */ +static const int NETWORK_UPGRADE_PEER_PREFERENCE_BLOCK_PERIOD = 24 * 24 * 3; unsigned int ReceiveFloodSize(); unsigned int SendBufferSize(); diff --git a/src/version.h b/src/version.h index b68bafc87..25527895d 100644 --- a/src/version.h +++ b/src/version.h @@ -9,7 +9,7 @@ * network protocol versioning */ -static const int PROTOCOL_VERSION = 170002; +static const int PROTOCOL_VERSION = 170003; //! initial proto version, to be increased after version/verack negotiation static const int INIT_PROTO_VERSION = 209;