Merge bitcoin/bitcoin#35458: qa: Avoid extra tracebacks when exception is raised

472b950b7f qa: Use custom assert_greater_than() over naked assert (Hodlinator)
f42226d526 qa: Silence socket.timeout exception when substituting it for a JSONRPCException (Hodlinator)
659671ac3d qa: Avoid cleanup when exception is raised (Hodlinator)

Pull request description:

  Clean up some cases in which we would trigger multiple tracebacks, which makes it unclear what issue is occuring (https://github.com/bitcoin/bitcoin/issues/31894#issuecomment-4616130031).

  CI log of this occurring: https://github.com/bitcoin/bitcoin/actions/runs/26907239657/job/79375388058?pr=35179

  <details><summary>Relevant log excerpt</summary>

  ```
   test  2026-06-03T19:28:28.119804Z TestFramework.node0 (DEBUG): TestNode.generate() dispatches `generate` call to `generatetoaddress`
   test  2026-06-03T19:28:28.120483Z TestFramework (ERROR): Unexpected exception:
                                     Traceback (most recent call last):
                                       File "/home/runner/work/bitcoin/bitcoin/test/functional/test_framework/authproxy.py", line 165, in _get_response
                                         http_response = self.__conn.getresponse()
                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^
                                       File "/usr/lib/python3.12/http/client.py", line 1448, in getresponse
                                         response.begin()
                                       File "/usr/lib/python3.12/http/client.py", line 336, in begin
                                         version, status, reason = self._read_status()
                                                                   ^^^^^^^^^^^^^^^^^^^
                                       File "/usr/lib/python3.12/http/client.py", line 297, in _read_status
                                         line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
                                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                       File "/usr/lib/python3.12/socket.py", line 707, in readinto
                                         return self._sock.recv_into(b)
                                                ^^^^^^^^^^^^^^^^^^^^^^^
                                     TimeoutError: timed out
                                     During handling of the above exception, another exception occurred:
                                     Traceback (most recent call last):
                                       File "/home/runner/work/bitcoin/bitcoin/ci_build/test/functional/p2p_orphan_handling.py", line 54, in wrapper
                                         func(self)
                                       File "/home/runner/work/bitcoin/bitcoin/ci_build/test/functional/p2p_orphan_handling.py", line 633, in test_maximal_package_protected
                                         testres = node.testmempoolaccept([large_orphan.serialize().hex()])
                                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                       File "/home/runner/work/bitcoin/bitcoin/test/functional/test_framework/coverage.py", line 50, in __call__
                                         return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                       File "/home/runner/work/bitcoin/bitcoin/test/functional/test_framework/authproxy.py", line 128, in __call__
                                         response, status = self._request('POST', self.__url.path, postdata.encode('utf-8'))
                                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                       File "/home/runner/work/bitcoin/bitcoin/test/functional/test_framework/authproxy.py", line 102, in _request
                                         return self._get_response()
                                                ^^^^^^^^^^^^^^^^^^^^
                                       File "/home/runner/work/bitcoin/bitcoin/test/functional/test_framework/authproxy.py", line 167, in _get_response
                                         raise JSONRPCException({
                                     test_framework.util.JSONRPCException: 'testmempoolaccept' RPC took longer than 30.000000 seconds. Consider using larger timeout for calls that take longer to return. (-344)  [http_status=None]
                                     During handling of the above exception, another exception occurred:
                                     Traceback (most recent call last):
                                       File "/home/runner/work/bitcoin/bitcoin/test/functional/test_framework/test_framework.py", line 143, in main
                                         self.run_test()
                                       File "/home/runner/work/bitcoin/bitcoin/ci_build/test/functional/p2p_orphan_handling.py", line 837, in run_test
                                         self.test_maximal_package_protected()
                                       File "/home/runner/work/bitcoin/bitcoin/ci_build/test/functional/p2p_orphan_handling.py", line 57, in wrapper
                                         self.generate(self.nodes[0], 1)
                                       File "/home/runner/work/bitcoin/bitcoin/test/functional/test_framework/test_framework.py", line 666, in generate
                                         blocks = generator.generate(*args, called_by_framework=True, **kwargs)
                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                       File "/home/runner/work/bitcoin/bitcoin/test/functional/test_framework/test_node.py", line 445, in generate
                                         return self.generatetoaddress(nblocks=nblocks, address=self.get_deterministic_priv_key().address, maxtries=maxtries, **kwargs)
                                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                       File "/home/runner/work/bitcoin/bitcoin/test/functional/test_framework/test_node.py", line 453, in generatetoaddress
                                         return self.__getattr__('generatetoaddress')(*args, **kwargs)
                                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                       File "/home/runner/work/bitcoin/bitcoin/test/functional/test_framework/coverage.py", line 50, in __call__
                                         return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                       File "/home/runner/work/bitcoin/bitcoin/test/functional/test_framework/authproxy.py", line 128, in __call__
                                         response, status = self._request('POST', self.__url.path, postdata.encode('utf-8'))
                                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                       File "/home/runner/work/bitcoin/bitcoin/test/functional/test_framework/authproxy.py", line 101, in _request
                                         self.__conn.request(method, path, postdata, headers)
                                       File "/usr/lib/python3.12/http/client.py", line 1356, in request
                                         self._send_request(method, url, body, headers, encode_chunked)
                                       File "/usr/lib/python3.12/http/client.py", line 1367, in _send_request
                                         self.putrequest(method, url, **skips)
                                       File "/usr/lib/python3.12/http/client.py", line 1193, in putrequest
                                         raise CannotSendRequest(self.__state)
                                     http.client.CannotSendRequest: Request-sent
   test  2026-06-03T19:28:28.124908Z TestFramework (DEBUG): Closing down network thread
  ```

  </details>

  Fix in authproxy.py can be verified through applying the below diff with/without PR changes and running `./build/test/functional/p2p_orphan_handling.py`:

  ```diff
  --- a/test/functional/test_framework/authproxy.py
  +++ b/test/functional/test_framework/authproxy.py
  @@ -162,6 +162,8 @@ class AuthServiceProxy():
       def _get_response(self):
           req_start_time = time.time()
           try:
  +            if AuthServiceProxy.__id_count > 55:
  +                raise socket.timeout()
               http_response = self.__conn.getresponse()
           except socket.timeout:
               raise JSONRPCException({
  ```

ACKs for top commit:
  maflcko:
    review ACK 472b950b7f 🔼
  polespinasa:
    reACK 472b950b7f

Tree-SHA512: ec1ede0340da9d1338643980e7f2e4646f0aed2e64f339f8b41a447cbbfb4da8158c1cbcb22f2d0cc383c2dd945075602d02b07a47b33b44cc6215f3292c1d46
This commit is contained in:
merge-script
2026-06-12 10:51:47 +02:00
7 changed files with 61 additions and 67 deletions

View File

@@ -30,13 +30,12 @@ def weight_to_vsize(weight):
def cleanup(func):
def wrapper(self, *args, **kwargs):
try:
func(self, *args, **kwargs)
finally:
# Mine blocks to clear the mempool and replenish the wallet's confirmed UTXOs.
while (len(self.nodes[0].getrawmempool()) > 0):
self.generate(self.nodes[0], 1)
self.wallet.rescan_utxos(include_mempool=True)
func(self, *args, **kwargs)
# Mine blocks to clear the mempool and replenish the wallet's confirmed UTXOs.
while (len(self.nodes[0].getrawmempool()) > 0):
self.generate(self.nodes[0], 1)
self.wallet.rescan_utxos(include_mempool=True)
return wrapper
class MempoolClusterTest(BitcoinTestFramework):

View File

@@ -29,15 +29,14 @@ TRUC_CHILD_MAX_VSIZE = 1000
def cleanup(extra_args=None):
def decorator(func):
def wrapper(self):
try:
if extra_args is not None:
self.restart_node(0, extra_args=extra_args)
func(self)
finally:
# Clear mempool again after test
self.generate(self.nodes[0], 1)
if extra_args is not None:
self.restart_node(0)
if extra_args is not None:
self.restart_node(0, extra_args=extra_args)
func(self)
# Clear mempool again after test
self.generate(self.nodes[0], 1)
if extra_args is not None:
self.restart_node(0)
return wrapper
return decorator

View File

@@ -58,18 +58,17 @@ GETDATA_WAIT = 60
def cleanup(func):
def wrapper(self, *args, **kwargs):
try:
func(self, *args, **kwargs)
finally:
self.nodes[0].disconnect_p2ps()
# Do not clear the node's mempool, as each test requires mempool min feerate > min
# relay feerate. However, do check that this is the case.
assert self.nodes[0].getmempoolinfo()["mempoolminfee"] > self.nodes[0].getnetworkinfo()["relayfee"]
# Ensure we do not try to spend the same UTXOs in subsequent tests, as they will look like RBF attempts.
self.wallet.rescan_utxos(include_mempool=True)
func(self, *args, **kwargs)
# Resets if mocktime was used
self.nodes[0].setmocktime(0)
self.nodes[0].disconnect_p2ps()
# Do not clear the node's mempool, as each test requires mempool min feerate > min
# relay feerate. However, do check that this is the case.
assert_greater_than(self.nodes[0].getmempoolinfo()["mempoolminfee"], self.nodes[0].getnetworkinfo()["relayfee"])
# Ensure we do not try to spend the same UTXOs in subsequent tests, as they will look like RBF attempts.
self.wallet.rescan_utxos(include_mempool=True)
# Resets if mocktime was used
self.nodes[0].setmocktime(0)
return wrapper
class PackageRelayTest(BitcoinTestFramework):

View File

@@ -50,20 +50,19 @@ TXREQUEST_TIME_SKIP = NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY + OVERLOADED_PEER
def cleanup(func):
def wrapper(self):
try:
func(self)
finally:
# Clear mempool
self.generate(self.nodes[0], 1)
self.nodes[0].disconnect_p2ps()
# Check that mempool and orphanage have been cleared
self.wait_until(lambda: len(self.nodes[0].getorphantxs()) == 0)
assert_equal(0, len(self.nodes[0].getrawmempool()))
func(self)
self.restart_node(0, extra_args=["-persistmempool=0"])
# Allow use of bumpmocktime again
self.nodes[0].setmocktime(int(time.time()))
self.wallet.rescan_utxos(include_mempool=True)
# Clear mempool
self.generate(self.nodes[0], 1)
self.nodes[0].disconnect_p2ps()
# Check that mempool and orphanage have been cleared
self.wait_until(lambda: len(self.nodes[0].getorphantxs()) == 0)
assert_equal(0, len(self.nodes[0].getrawmempool()))
self.restart_node(0, extra_args=["-persistmempool=0"])
# Allow use of bumpmocktime again
self.nodes[0].setmocktime(int(time.time()))
self.wallet.rescan_utxos(include_mempool=True)
return wrapper
class PeerTxRelayer(P2PTxInvStore):

View File

@@ -169,7 +169,7 @@ class AuthServiceProxy():
'message': '%r RPC took longer than %f seconds. Consider '
'using larger timeout for calls that take '
'longer to return.' % (self._service_name,
self.__conn.timeout)})
self.__conn.timeout)}) from None
if http_response is None:
raise JSONRPCException({
'code': -342, 'message': 'missing HTTP response from server'})

View File

@@ -18,12 +18,11 @@ from test_framework.util import (
# Decorator to reset activewallet to zero utxos
def cleanup(func):
def wrapper(self):
try:
func(self)
finally:
if 0 < self.wallet.getbalances()["mine"]["trusted"]:
self.wallet.sendall([self.remainder_target])
assert_equal(0, self.wallet.getbalances()["mine"]["trusted"]) # wallet is empty
func(self)
if 0 < self.wallet.getbalances()["mine"]["trusted"]:
self.wallet.sendall([self.remainder_target])
assert_equal(0, self.wallet.getbalances()["mine"]["trusted"]) # wallet is empty
return wrapper
class SendallTest(BitcoinTestFramework):

View File

@@ -38,28 +38,27 @@ from test_framework.mempool_util import (
# sweep alice and bob's wallets and clear the mempool
def cleanup(func):
def wrapper(self, *args):
try:
self.generate(self.nodes[0], 1)
func(self, *args)
finally:
self.generate(self.nodes[0], 1)
for wallet in [self.alice, self.bob]:
txs = set(tx["txid"] for tx in wallet.listtransactions("*", 1000) if tx["confirmations"] == 0 and not tx["abandoned"])
for tx in txs:
wallet.abandontransaction(tx)
try:
wallet.sendall([self.charlie.getnewaddress()])
except JSONRPCException as e:
assert "Total value of UTXO pool too low to pay for transaction" in e.error['message']
self.generate(self.nodes[0], 1)
self.generate(self.nodes[0], 1)
func(self, *args)
for wallet in [self.alice, self.bob]:
balance = wallet.getbalances()["mine"]
for balance_type in ["untrusted_pending", "trusted", "immature", "nonmempool"]:
assert_equal(balance[balance_type], 0)
self.generate(self.nodes[0], 1)
for wallet in [self.alice, self.bob]:
txs = set(tx["txid"] for tx in wallet.listtransactions("*", 1000) if tx["confirmations"] == 0 and not tx["abandoned"])
for tx in txs:
wallet.abandontransaction(tx)
try:
wallet.sendall([self.charlie.getnewaddress()])
except JSONRPCException as e:
assert "Total value of UTXO pool too low to pay for transaction" in e.error['message']
self.generate(self.nodes[0], 1)
assert_equal(self.alice.getrawmempool(), [])
assert_equal(self.bob.getrawmempool(), [])
for wallet in [self.alice, self.bob]:
balance = wallet.getbalances()["mine"]
for balance_type in ["untrusted_pending", "trusted", "immature", "nonmempool"]:
assert_equal(balance[balance_type], 0)
assert_equal(self.alice.getrawmempool(), [])
assert_equal(self.bob.getrawmempool(), [])
return wrapper