I temporarily commented out WriteCacheControlHeader in RESTERR in rest.cpp, rebuilt, and compared:
GET /rest/block/<hash>.bin → 200 - this unchanged with or without that line: success responses set Cache-Control on their own path, not only via RESTERR.
GET /rest/tx/<64×0>.json → 404 - this without the line: no Cache-Control header. With the line: Cache-Control: no-store.
So a regression that only breaks RESTERR can still look fine if tests only assert 200 responses.
might be worth extending tests in interface_rest.py with append_suffix and assert_rest_error_no_store for several RESTERR outcomes 400/404.
<details>
<summary>click to expand</summary>
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -67,9 +67,11 @@ class RESTTest (BitcoinTestFramework):
status: int = 200,
ret_type: RetType = RetType.JSON,
query_params: typing.Union[dict[str, typing.Any], str, None] = None,
+ *,
+ append_suffix: bool = True,
) -> typing.Union[http.client.HTTPResponse, bytes, str, None]:
rest_uri = '/rest' + uri
- if req_type in ReqType:
+ if append_suffix and req_type in ReqType:
rest_uri += f'.{req_type.name.lower()}'
if query_params:
if isinstance(query_params, str):
:...skipping...
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index ed2aef0f48..62ffdaa92b 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -67,9 +67,11 @@ class RESTTest (BitcoinTestFramework):
status: int = 200,
ret_type: RetType = RetType.JSON,
query_params: typing.Union[dict[str, typing.Any], str, None] = None,
+ *,
+ append_suffix: bool = True,
) -> typing.Union[http.client.HTTPResponse, bytes, str, None]:
rest_uri = '/rest' + uri
- if req_type in ReqType:
+ if append_suffix and req_type in ReqType:
rest_uri += f'.{req_type.name.lower()}'
if query_params:
if isinstance(query_params, str):
@@ -537,6 +539,18 @@ class RESTTest (BitcoinTestFramework):
assert_equal(response.getheader("Cache-Control"), expected)
:...skipping...
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index ed2aef0f48..62ffdaa92b 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -67,9 +67,11 @@ class RESTTest (BitcoinTestFramework):
status: int = 200,
ret_type: RetType = RetType.JSON,
query_params: typing.Union[dict[str, typing.Any], str, None] = None,
+ *,
+ append_suffix: bool = True,
) -> typing.Union[http.client.HTTPResponse, bytes, str, None]:
rest_uri = '/rest' + uri
- if req_type in ReqType:
+ if append_suffix and req_type in ReqType:
rest_uri += f'.{req_type.name.lower()}'
if query_params:
if isinstance(query_params, str):
@@ -537,6 +539,18 @@ class RESTTest (BitcoinTestFramework):
assert_equal(response.getheader("Cache-Control"), expected)
response.read()
:...skipping...
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index ed2aef0f48..62ffdaa92b 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -67,9 +67,11 @@ class RESTTest (BitcoinTestFramework):
status: int = 200,
ret_type: RetType = RetType.JSON,
query_params: typing.Union[dict[str, typing.Any], str, None] = None,
+ *,
+ append_suffix: bool = True,
) -> typing.Union[http.client.HTTPResponse, bytes, str, None]:
rest_uri = '/rest' + uri
- if req_type in ReqType:
+ if append_suffix and req_type in ReqType:
rest_uri += f'.{req_type.name.lower()}'
if query_params:
if isinstance(query_params, str):
@@ -537,6 +539,18 @@ class RESTTest (BitcoinTestFramework):
assert_equal(response.getheader("Cache-Control"), expected)
response.read()
+ def assert_rest_error_no_store(uri, status, *, req_type=ReqType.JSON, query_params=None, append_suffix=True):
+ response = self.test_rest_request(
:...skipping...
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index ed2aef0f48..62ffdaa92b 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -67,9 +67,11 @@ class RESTTest (BitcoinTestFramework):
status: int = 200,
ret_type: RetType = RetType.JSON,
query_params: typing.Union[dict[str, typing.Any], str, None] = None,
+ *,
+ append_suffix: bool = True,
) -> typing.Union[http.client.HTTPResponse, bytes, str, None]:
rest_uri = '/rest' + uri
- if req_type in ReqType:
+ if append_suffix and req_type in ReqType:
rest_uri += f'.{req_type.name.lower()}'
if query_params:
if isinstance(query_params, str):
@@ -537,6 +539,18 @@ class RESTTest (BitcoinTestFramework):
assert_equal(response.getheader("Cache-Control"), expected)
response.read()
+ def assert_rest_error_no_store(uri, status, *, req_type=ReqType.JSON, query_params=None, append_suffix=True):
+ response = self.test_rest_request(
+ uri,
+ req_type=req_type,
:...skipping...
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index ed2aef0f48..62ffdaa92b 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -67,9 +67,11 @@ class RESTTest (BitcoinTestFramework):
status: int = 200,
ret_type: RetType = RetType.JSON,
query_params: typing.Union[dict[str, typing.Any], str, None] = None,
+ *,
+ append_suffix: bool = True,
) -> typing.Union[http.client.HTTPResponse, bytes, str, None]:
rest_uri = '/rest' + uri
- if req_type in ReqType:
+ if append_suffix and req_type in ReqType:
rest_uri += f'.{req_type.name.lower()}'
if query_params:
if isinstance(query_params, str):
@@ -537,6 +539,18 @@ class RESTTest (BitcoinTestFramework):
assert_equal(response.getheader("Cache-Control"), expected)
response.read()
+ def assert_rest_error_no_store(uri, status, *, req_type=ReqType.JSON, query_params=None, append_suffix=True):
+ response = self.test_rest_request(
+ uri,
+ req_type=req_type,
+ status=status,
+ ret_type=RetType.OBJ,
+ query_params=query_params,
:...skipping...
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index ed2aef0f48..62ffdaa92b 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -67,9 +67,11 @@ class RESTTest (BitcoinTestFramework):
status: int = 200,
ret_type: RetType = RetType.JSON,
query_params: typing.Union[dict[str, typing.Any], str, None] = None,
+ *,
+ append_suffix: bool = True,
) -> typing.Union[http.client.HTTPResponse, bytes, str, None]:
rest_uri = '/rest' + uri
- if req_type in ReqType:
+ if append_suffix and req_type in ReqType:
rest_uri += f'.{req_type.name.lower()}'
if query_params:
if isinstance(query_params, str):
@@ -537,6 +539,18 @@ class RESTTest (BitcoinTestFramework):
assert_equal(response.getheader("Cache-Control"), expected)
response.read()
+ def assert_rest_error_no_store(uri, status, *, req_type=ReqType.JSON, query_params=None, append_suffix=True):
+ response = self.test_rest_request(
+ uri,
+ req_type=req_type,
+ status=status,
+ ret_type=RetType.OBJ,
+ query_params=query_params,
+ append_suffix=append_suffix,
+ )
+ assert_equal(response.getheader("Cache-Control"), no_store)
+ response.read()
+
# Immutable endpoints
assert_cache_control(f"/block/{blockhash}", immutable, req_type=ReqType.BIN)
assert_cache_control(f"/spenttxouts/{blockhash}", immutable)
@@ -587,8 +601,16 @@ class RESTTest (BitcoinTestFramework):
assert_cache_control(cache_utxo_path, no_store, req_type=ReqType.BIN)
assert_cache_control(cache_utxo_path, no_store, req_type=ReqType.HEX)
- # Error responses should not be cached
- assert_cache_control(f"/block/{UNKNOWN_PARAM}", no_store, status=404)
+ self.log.info("Cache-Control on REST error responses (no-store)")
+ # Unknown format suffix leaves hashStr unparsed (like "<hex>.invalid") → 400 Invalid hash.
+ assert_rest_error_no_store(f"/block/{blockhash}.invalid", 400, append_suffix=False)
+ assert_rest_error_no_store(f"/tx/{INVALID_PARAM}", 400)
+ assert_rest_error_no_store(f"/deploymentinfo/{INVALID_PARAM}", 400)
+ assert_rest_error_no_store("/blockhashbyheight/999999999", 404)
+ assert_rest_error_no_store(f"/block/{UNKNOWN_PARAM}", 404)
+ assert_rest_error_no_store(f"/tx/{'f' * 64}", 404)
+ assert_rest_error_no_store("/mempool/not-a-valid-path", 400)
+ assert_rest_error_no_store(f"/deploymentinfo/{non_existing_blockhash}", 400)
</details>