@@ -61,16 +61,24 @@ def seed_addrman(node):
6161
6262class NetTest (BitcoinTestFramework ):
6363 def set_test_params (self ):
64- self .num_nodes = 2
64+ self .num_nodes = 11
6565 self .extra_args = [
6666 ["-minrelaytxfee=0.00001000" ],
6767 ["-minrelaytxfee=0.00000500" ],
68- ]
69- # Specify a non-working proxy to make sure no actual connections to public IPs are attempted
70- for args in self .extra_args :
71- args .append ("-proxy=127.0.0.1:1" )
68+ ] + [[]] * 9
69+ # Specify a non-working proxy on nodes 0-1 to make sure no actual
70+ # connections to public IPs are attempted; nodes 2-10 need to accept
71+ # real connections for the addnode limit test.
72+ for i in range (2 ):
73+ self .extra_args [i ].append ("-proxy=127.0.0.1:1" )
7274 self .supports_cli = False
7375
76+ def setup_network (self ):
77+ self .setup_nodes ()
78+ # Only connect nodes 0 and 1; nodes 2-10 are addnode targets used
79+ # exclusively by test_addnode_connection_limit.
80+ self .connect_nodes (1 , 0 )
81+
7482 def run_test (self ):
7583 # We need miniwallet to make a transaction
7684 self .wallet = MiniWallet (self .nodes [0 ])
@@ -80,7 +88,7 @@ def run_test(self):
8088 # the two nodes being connected both ways.
8189 # Topology will look like: node0 <--> node1
8290 self .connect_nodes (0 , 1 )
83- self .sync_all ( )
91+ self .sync_blocks ( self . nodes [: 2 ] )
8492
8593 self .test_connection_count ()
8694 self .test_getpeerinfo ()
@@ -93,6 +101,7 @@ def run_test(self):
93101 self .test_sendmsgtopeer ()
94102 self .test_getaddrmaninfo ()
95103 self .test_getrawaddrman ()
104+ self .test_addnode_connection_limit ()
96105
97106 def test_connection_count (self ):
98107 self .log .info ("Test getconnectioncount" )
@@ -103,11 +112,11 @@ def test_getpeerinfo(self):
103112 self .log .info ("Test getpeerinfo" )
104113 # Create a few getpeerinfo last_block/last_transaction values.
105114 self .wallet .send_self_transfer (from_node = self .nodes [0 ]) # Make a transaction so we can see it in the getpeerinfo results
106- self .generate (self .nodes [1 ], 1 )
115+ self .generate (self .nodes [1 ], 1 , sync_fun = lambda : self . sync_blocks ( self . nodes [: 2 ]) )
107116 time_now = int (time .time ())
108- peer_info = [x .getpeerinfo () for x in self .nodes ]
117+ peer_info = [x .getpeerinfo () for x in self .nodes [: 2 ] ]
109118 # Verify last_block and last_transaction keys/values.
110- for node , peer , field in product (range (self . num_nodes ), range (2 ), ['last_block' , 'last_transaction' ]):
119+ for node , peer , field in product (range (2 ), range (2 ), ['last_block' , 'last_transaction' ]):
111120 assert field in peer_info [node ][peer ].keys ()
112121 if peer_info [node ][peer ][field ] != 0 :
113122 assert_approx (peer_info [node ][peer ][field ], time_now , vspan = 60 )
@@ -587,5 +596,50 @@ def check_getrawaddrman_entries(expected):
587596 check_getrawaddrman_entries (expected )
588597
589598
599+ def test_addnode_connection_limit (self ):
600+ self .log .info ("Test addnode connection limit (MAX_ADDNODE_CONNECTIONS=8)" )
601+ MAX_ADDNODE_CONNECTIONS = 8
602+ node = self .nodes [0 ]
603+
604+ def count_manual ():
605+ return sum (1 for p in node .getpeerinfo () if p ['connection_type' ] == 'manual' )
606+
607+ # Restart node 0 without proxy, with -maxconnections=0 to prove
608+ # addnode connections bypass the general connection limit.
609+ self .restart_node (0 , extra_args = ["-maxconnections=0" ])
610+
611+ targets = MAX_ADDNODE_CONNECTIONS + 1 # 9 targets (nodes 2-10), only 8 can connect
612+
613+ self .log .info ("Verify clean initial state" )
614+ assert_equal (node .getaddednodeinfo (), [])
615+ assert_equal (count_manual (), 0 )
616+
617+ self .log .info (f"Add { targets } nodes via addnode RPC" )
618+ for i in range (2 , 2 + targets ):
619+ node .addnode (node = f"127.0.0.1:{ p2p_port (i )} " , command = 'add' )
620+ assert_equal (len (node .getaddednodeinfo ()), targets )
621+
622+ self .log .info (f"Wait for { MAX_ADDNODE_CONNECTIONS } manual connections" )
623+ self .wait_until (lambda : count_manual () >= MAX_ADDNODE_CONNECTIONS , timeout = 120 )
624+ assert_equal (count_manual (), MAX_ADDNODE_CONNECTIONS )
625+
626+ self .log .info ("Verify the 9th (last-added) target was not connected" )
627+ last_target_port = p2p_port (2 + targets - 1 )
628+ connected_ports = {int (p ['addr' ].rsplit (':' , 1 )[1 ]) for p in node .getpeerinfo ()
629+ if p ['connection_type' ] == 'manual' }
630+ assert last_target_port not in connected_ports
631+
632+ self .log .info ("Remove and disconnect one connected peer to free a slot" )
633+ connected = [p for p in node .getpeerinfo () if p ['connection_type' ] == 'manual' ]
634+ assert_greater_than (len (connected ), 0 )
635+ victim_addr = connected [0 ]['addr' ]
636+ node .addnode (node = victim_addr , command = 'remove' )
637+ node .disconnectnode (victim_addr )
638+
639+ self .log .info ("Wait for the previously-blocked node to connect" )
640+ self .wait_until (lambda : count_manual () >= MAX_ADDNODE_CONNECTIONS , timeout = 120 )
641+ assert_equal (count_manual (), MAX_ADDNODE_CONNECTIONS )
642+ assert_equal (len (node .getaddednodeinfo ()), MAX_ADDNODE_CONNECTIONS )
643+
590644if __name__ == '__main__' :
591645 NetTest (__file__ ).main ()
0 commit comments