diff --git a/src/net.cpp b/src/net.cpp index ef787baae989..e02d694c8156 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2052,7 +2052,7 @@ bool CConnman::AddConnection(const std::string& address, ConnectionType conn_typ // Count existing connections int existing_connections = WITH_READ_LOCK(m_nodes_mutex, - return std::count_if(m_nodes.begin(), m_nodes.end(), [conn_type](CNode* node) { return node->m_conn_type == conn_type; });); + return std::count_if(m_nodes.begin(), m_nodes.end(), [conn_type](CNode* node) { return node->m_conn_type == conn_type && !node->fDisconnect; });); // Max connections of specified type already exist if (max_connections != std::nullopt && existing_connections >= max_connections) return false; diff --git a/test/functional/test_framework/crypto/secp256k1.py b/test/functional/test_framework/crypto/secp256k1.py index 2e9e419da5b0..225ed2dc70f5 100644 --- a/test/functional/test_framework/crypto/secp256k1.py +++ b/test/functional/test_framework/crypto/secp256k1.py @@ -15,6 +15,7 @@ * G: the secp256k1 generator point """ +import functools class FE: """Objects of this class represent elements of the field GF(2**256 - 2**32 - 977). @@ -227,7 +228,7 @@ def mul(*aps): def __rmul__(self, a): """Multiply an integer with a group element.""" if self == G: - return FAST_G.mul(a) + return mul_G(a % GE.ORDER) return GE.mul((a, self)) def __neg__(self): @@ -319,28 +320,40 @@ def __repr__(self): class FastGEMul: """Table for fast multiplication with a constant group element. - Speed up scalar multiplication with a fixed point P by using a precomputed lookup table with - its powers of 2: + Speed up scalar multiplication with a fixed point P by using a precomputed lookup table. + The scalar is split into WINDOW-bit chunks, and for every chunk position i the table holds + a row [(0 * 2^(WINDOW*i)) * P, (1 * 2^(WINDOW*i)) * P, ..., ((2^WINDOW-1) * 2^(WINDOW*i)) * P]: - table = [P, 2*P, 4*P, (2^3)*P, (2^4)*P, ..., (2^255)*P] + table[i][j] = j * (2^(WINDOW*i)) * P - During multiplication, the points corresponding to each bit set in the scalar are added up, - i.e. on average ~128 point additions take place. + During multiplication, one table entry per chunk is added up, i.e. 256/WINDOW point + additions take place (instead of ~128 on average for a bit-by-bit approach). """ + WINDOW = 4 + def __init__(self, p): - self.table = [p] # table[i] = (2^i) * p - for _ in range(255): - p = p + p - self.table.append(p) + self.table = [] # table[i][j] = j * (2^(WINDOW*i)) * p + for _ in range((256 + self.WINDOW - 1) // self.WINDOW): + row = [GE()] + for _ in range((1 << self.WINDOW) - 1): + row.append(row[-1] + p) + self.table.append(row) + for _ in range(self.WINDOW): + p = p + p def mul(self, a): result = GE() a = a % GE.ORDER - for bit in range(a.bit_length()): - if a & (1 << bit): - result += self.table[bit] + mask = (1 << self.WINDOW) - 1 + for row in self.table: + result += row[a & mask] + a >>= self.WINDOW return result # Precomputed table with multiples of G for fast multiplication FAST_G = FastGEMul(G) + +@functools.lru_cache(maxsize=1024) +def mul_G(a): + return FAST_G.mul(a) diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py index 7601e6eba5f2..9a257f31a673 100755 --- a/test/functional/test_framework/p2p.py +++ b/test/functional/test_framework/p2p.py @@ -294,6 +294,8 @@ def connection_made(self, transport): if self.p2p_connected_to_node and not self.supports_v2_p2p: self.send_version() self.on_open() + with p2p_lock: + p2p_lock.notify_all() def connection_lost(self, exc): """asyncio callback when a connection is closed.""" @@ -305,6 +307,8 @@ def connection_lost(self, exc): self._transport = None self.recvbuf = b"" self.on_close() + with p2p_lock: + p2p_lock.notify_all() # v2 handshake method def _on_data_v2_handshake(self): @@ -572,6 +576,7 @@ def on_message(self, message): except Exception: print("ERROR delivering %s (%s)" % (repr(message), sys.exc_info()[0])) raise + p2p_lock.notify_all() # Callback methods. Can be overridden by subclasses in individual test # cases to provide custom message handling behaviour. @@ -782,7 +787,9 @@ def test_function(): # P2PConnection acquires this lock whenever delivering a message to a P2PInterface. # This lock should be acquired in the thread running the test logic to synchronize # access to any data shared with the P2PInterface or P2PConnection. -p2p_lock = threading.Lock() +# It is a Condition so that waiters can be woken up as soon as a message is +# delivered or the connection state changes, instead of polling. +p2p_lock = threading.Condition(threading.Lock()) class NetworkThread(threading.Thread): diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 75125f6a9954..c7c722da7ccf 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -826,7 +826,7 @@ def generatetodescriptor(self, generator, *args, sync_fun=None, **kwargs): sync_fun() if sync_fun else self.sync_all() return blocks - def sync_blocks(self, nodes=None, wait=1, timeout=60): + def sync_blocks(self, nodes=None, *, wait=0.05, timeout=60): """ Wait until everybody has the same tip. sync_blocks needs to be called with an rpc_connections set that has least @@ -848,7 +848,7 @@ def sync_blocks(self, nodes=None, wait=1, timeout=60): "".join("\n {!r}".format(b) for b in best_hash), )) - def sync_mempools(self, nodes=None, wait=1, timeout=60, flush_scheduler=True, wait_func=None): + def sync_mempools(self, nodes=None, *, wait=0.05, timeout=60, flush_scheduler=True, wait_func=None): """ Wait until everybody has the same transactions in their memory pools @@ -857,7 +857,8 @@ def sync_mempools(self, nodes=None, wait=1, timeout=60, flush_scheduler=True, wa timeout = int(timeout * self.options.timeout_factor) stop_time = time.time() + timeout if self.mocktime != 0 and wait_func is None: - wait_func = lambda: self.bump_mocktime(3, nodes=nodes) + # Polling is much faster than the mocktime bump was designed for; keep the bump at its original cadence + wait_func = throttled(lambda: self.bump_mocktime(3, nodes=nodes)) while time.time() <= stop_time: pool = [set(r.getrawmempool()) for r in rpc_connections] if pool.count(pool[0]) == len(rpc_connections): diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 7413569ed8cf..90cb83ba782c 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -288,9 +288,10 @@ def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, env=None def wait_for_rpc_connection(self): """Sets up an RPC connection to the dashd process. Returns False if unable to connect.""" - # Poll at a rate of four times per second - poll_per_s = 4 - for _ in range(poll_per_s * self.rpc_timeout): + # Poll at a rate of twenty times per second + poll_per_s = 20 + stop_time = time.monotonic() + self.rpc_timeout + while time.monotonic() <= stop_time: if self.process.poll() is not None: # Attach abrupt shutdown error/s to the exception message self.stderr.seek(0) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 812ebcf089cb..da39edee1a57 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -18,6 +18,7 @@ import shutil import re import sys +import threading import time import urllib.parse @@ -281,6 +282,11 @@ def wait_until_helper(predicate, *, attempts=float('inf'), timeout=float('inf'), with lock: if predicate(): return True + if isinstance(lock, threading.Condition): + # Wake up early when the lock owner signals a state change + lock.wait(sleep) + attempt += 1 + continue else: if predicate(): return True diff --git a/test/functional/wallet_change_address.py b/test/functional/wallet_change_address.py index f71ed1b2e809..40c5bfa1c45d 100755 --- a/test/functional/wallet_change_address.py +++ b/test/functional/wallet_change_address.py @@ -26,6 +26,9 @@ def set_test_params(self): ["-discardfee=1"], ["-avoidpartialspends", "-discardfee=1"] ] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/wallet_conflicts.py b/test/functional/wallet_conflicts.py index 802b718cd5e2..4a5efb08728e 100755 --- a/test/functional/wallet_conflicts.py +++ b/test/functional/wallet_conflicts.py @@ -20,6 +20,8 @@ def add_options(self, parser): def set_test_params(self): self.num_nodes = 3 + # whitelist peers to speed up tx relay / mempool sync + self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py index 42b74e5c7e1a..984ec467bba7 100755 --- a/test/functional/wallet_importprunedfunds.py +++ b/test/functional/wallet_importprunedfunds.py @@ -27,6 +27,8 @@ def add_options(self, parser): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 + # whitelist peers to speed up tx relay / mempool sync + self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/wallet_orphanedreward.py b/test/functional/wallet_orphanedreward.py index c96d67d84451..307ffa4a11ce 100755 --- a/test/functional/wallet_orphanedreward.py +++ b/test/functional/wallet_orphanedreward.py @@ -15,6 +15,8 @@ def add_options(self, parser): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 + # whitelist peers to speed up tx relay / mempool sync + self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/wallet_signer.py b/test/functional/wallet_signer.py index d15d217749ec..907127eb650c 100755 --- a/test/functional/wallet_signer.py +++ b/test/functional/wallet_signer.py @@ -44,11 +44,13 @@ def mock_multi_signers_path(self): def set_test_params(self): self.num_nodes = 2 - self.extra_args = [ [], [f"-signer={self.mock_signer_path()}", '-keypool=10'], ] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") def skip_test_if_missing_module(self): self.skip_if_no_external_signer()