/ test / functional / p2p_disconnect_ban.py
p2p_disconnect_ban.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2014-present The Bitcoin Core developers
  3  # Distributed under the MIT software license, see the accompanying
  4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
  5  """Test node disconnect and ban behavior"""
  6  import time
  7  from pathlib import Path
  8  
  9  from test_framework.test_framework import BitcoinTestFramework
 10  from test_framework.util import (
 11      assert_equal,
 12      assert_raises_rpc_error,
 13  )
 14  
 15  class DisconnectBanTest(BitcoinTestFramework):
 16      def set_test_params(self):
 17          self.num_nodes = 2
 18  
 19      def run_test(self):
 20          self.log.info("Connect nodes both ways")
 21          # By default, the test framework sets up an addnode connection from
 22          # node 1 --> node0. By connecting node0 --> node 1, we're left with
 23          # the two nodes being connected both ways.
 24          # Topology will look like: node0 <--> node1
 25          self.connect_nodes(0, 1)
 26  
 27          self.log.info("Test setban and listbanned RPCs")
 28  
 29          self.log.info("setban: successfully ban single IP address")
 30          assert_equal(len(self.nodes[1].getpeerinfo()), 2)  # node1 should have 2 connections to node0 at this point
 31          self.nodes[1].setban(subnet="127.0.0.1", command="add")
 32          self.wait_until(lambda: len(self.nodes[1].getpeerinfo()) == 0, timeout=10)
 33          assert_equal(len(self.nodes[1].getpeerinfo()), 0)  # all nodes must be disconnected at this point
 34          assert_equal(len(self.nodes[1].listbanned()), 1)
 35  
 36          self.log.info("clearbanned: successfully clear ban list")
 37          self.nodes[1].clearbanned()
 38          assert_equal(len(self.nodes[1].listbanned()), 0)
 39  
 40          self.log.info('Test banlist database recreation')
 41          self.stop_node(1)
 42          target_file = self.nodes[1].chain_path / "banlist.json"
 43          Path.unlink(target_file)
 44          with self.nodes[1].assert_debug_log(["Recreating the banlist database"]):
 45              self.start_node(1)
 46  
 47          assert Path.exists(target_file)
 48          assert_equal(self.nodes[1].listbanned(), [])
 49  
 50          self.nodes[1].setban("127.0.0.0/24", "add")
 51  
 52          self.log.info("setban: fail to ban an already banned subnet")
 53          assert_equal(len(self.nodes[1].listbanned()), 1)
 54          assert_raises_rpc_error(-23, "IP/Subnet already banned", self.nodes[1].setban, "127.0.0.1", "add")
 55  
 56          self.log.info("setban: fail to ban an invalid subnet")
 57          assert_raises_rpc_error(-30, "Error: Invalid IP/Subnet", self.nodes[1].setban, "127.0.0.1/42", "add")
 58          assert_equal(len(self.nodes[1].listbanned()), 1)  # still only one banned ip because 127.0.0.1 is within the range of 127.0.0.0/24
 59  
 60          self.log.info("setban: fail to ban with past absolute timestamp")
 61          assert_raises_rpc_error(-8, "Error: Absolute timestamp is in the past", self.nodes[1].setban, "127.27.0.1", "add", 123, True)
 62  
 63          self.log.info("setban remove: fail to unban a non-banned subnet")
 64          assert_raises_rpc_error(-30, "Error: Unban failed", self.nodes[1].setban, "127.0.0.1", "remove")
 65          assert_equal(len(self.nodes[1].listbanned()), 1)
 66  
 67          self.log.info("setban remove: successfully unban subnet")
 68          self.nodes[1].setban("127.0.0.0/24", "remove")
 69          assert_equal(len(self.nodes[1].listbanned()), 0)
 70          self.nodes[1].clearbanned()
 71          assert_equal(len(self.nodes[1].listbanned()), 0)
 72  
 73          self.log.info("setban: test persistence across node restart")
 74          # Set the mocktime so we can control when bans expire
 75          old_time = int(time.time())
 76          self.nodes[1].setmocktime(old_time)
 77          self.nodes[1].setban("127.0.0.0/32", "add")
 78          self.nodes[1].setban("127.0.0.0/24", "add")
 79          self.nodes[1].setban("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion", "add")
 80          self.nodes[1].setban("192.168.0.1", "add", 1)  # ban for 1 seconds
 81          self.nodes[1].setban("2001:4d48:ac57:400:cacf:e9ff:fe1d:9c63/19", "add", 1000)  # ban for 1000 seconds
 82          listBeforeShutdown = self.nodes[1].listbanned()
 83          assert_equal("192.168.0.1/32", listBeforeShutdown[2]['address'])
 84  
 85          self.log.info("setban: test banning with absolute timestamp")
 86          self.nodes[1].setban("192.168.0.2", "add", old_time + 120, absolute=True)
 87  
 88          # Move time forward by 3 seconds so the fourth ban has expired
 89          self.nodes[1].setmocktime(old_time + 3)
 90          assert_equal(len(self.nodes[1].listbanned()), 5)
 91  
 92          self.log.info("Test ban_duration and time_remaining")
 93          for ban in self.nodes[1].listbanned():
 94              if ban["address"] in ["127.0.0.0/32", "127.0.0.0/24", "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"]:
 95                  assert_equal(ban["ban_duration"], 86400)
 96                  assert_equal(ban["time_remaining"], 86397)
 97              elif ban["address"] == "2001:4d48:ac57:400:cacf:e9ff:fe1d:9c63/19":
 98                  assert_equal(ban["ban_duration"], 1000)
 99                  assert_equal(ban["time_remaining"], 997)
100              elif ban["address"] == "192.168.0.2/32":
101                  assert_equal(ban["ban_duration"], 120)
102                  assert_equal(ban["time_remaining"], 117)
103  
104          # Keep mocktime, to avoid ban expiry when restart takes longer than
105          # time_remaining
106          self.restart_node(1, extra_args=[f"-mocktime={old_time+4}"])
107  
108          listAfterShutdown = self.nodes[1].listbanned()
109          assert_equal("127.0.0.0/24", listAfterShutdown[0]['address'])
110          assert_equal("127.0.0.0/32", listAfterShutdown[1]['address'])
111          assert_equal("192.168.0.2/32", listAfterShutdown[2]['address'])
112          assert_equal("/19" in listAfterShutdown[3]['address'], True)
113          assert_equal("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion", listAfterShutdown[4]['address'])
114  
115          # Clear ban lists
116          self.nodes[1].clearbanned()
117          self.log.info("Connect nodes both ways")
118          self.connect_nodes(0, 1)
119          self.connect_nodes(1, 0)
120  
121          self.log.info("Test disconnectnode RPCs")
122  
123          self.log.info("disconnectnode: fail to disconnect when calling with address and nodeid")
124          address1 = self.nodes[0].getpeerinfo()[0]['addr']
125          node1 = self.nodes[0].getpeerinfo()[0]["id"]
126          assert_raises_rpc_error(-32602, "Only one of address and nodeid should be provided.", self.nodes[0].disconnectnode, address=address1, nodeid=node1)
127  
128          self.log.info("disconnectnode: fail to disconnect when calling with junk address")
129          assert_raises_rpc_error(-29, "Node not found in connected nodes", self.nodes[0].disconnectnode, address="221B Baker Street")
130  
131          self.log.info("disconnectnode: successfully disconnect node by address")
132          address1 = self.nodes[0].getpeerinfo()[0]['addr']
133          self.nodes[0].disconnectnode(address=address1)
134          self.wait_until(lambda: len(self.nodes[1].getpeerinfo()) == 1, timeout=10)
135          assert not [node for node in self.nodes[0].getpeerinfo() if node['addr'] == address1]
136  
137          self.log.info("disconnectnode: successfully reconnect node")
138          self.connect_nodes(0, 1)  # reconnect the node
139          assert_equal(len(self.nodes[0].getpeerinfo()), 2)
140          assert [node for node in self.nodes[0].getpeerinfo() if node['addr'] == address1]
141  
142          self.log.info("disconnectnode: successfully disconnect node by node id")
143          id1 = self.nodes[0].getpeerinfo()[0]['id']
144          self.nodes[0].disconnectnode(nodeid=id1)
145          self.wait_until(lambda: len(self.nodes[1].getpeerinfo()) == 1, timeout=10)
146          assert not [node for node in self.nodes[0].getpeerinfo() if node['id'] == id1]
147  
148  if __name__ == '__main__':
149      DisconnectBanTest(__file__).main()