seems like trailing data is silently being accepted... the decoder doesnt check that the body was consumed fully..so any bytes after the outpoint vector are ignored
i reproduced the issue by adding this line in interface_rest.py
git diff test/functional/interface_rest.py
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index eb18569478..9a32c49e3b 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -213,6 +213,7 @@ class RESTTest (BitcoinTestFramework):
self.test_rest_request("/getutxos", http_method='POST', req_type=ReqType.JSON, body='{"checkmempool', status=400, ret_type=RetType.OBJ)
self.test_rest_request("/getutxos", http_method='POST', req_type=ReqType.BIN, body='{"checkmempool', status=400, ret_type=RetType.OBJ)
self.test_rest_request("/getutxos/checkmempool", http_method='POST', req_type=ReqType.JSON, status=400, ret_type=RetType.OBJ)
+ self.test_rest_request("/getutxos", http_method='POST', req_type=ReqType.BIN, body=bin_request + b'\x00', status=400, ret_type=RetType.OBJ)
self.test_rest_request(f"/getutxos/{spending[0]}_+1", ret_type=RetType.OBJ, status=400)
here below shows the node returns success response
<details>
<summary>output results</summary>
build/test/functional/interface_rest.py
2026-10-02T16:09:34.675935Z TestFramework (INFO): PRNG seed is: 6815011314856331724
2026-10-02T16:09:34.726606Z TestFramework (INFO): Initializing test directory /tmp/bitcoin_func_test__mdx5r6s
2026-10-02T16:09:35.188130Z TestFramework (INFO): Broadcast test transaction and sync nodes
2026-10-02T16:09:36.216533Z TestFramework (INFO): Test the /tx URI
2026-10-02T16:09:36.222070Z TestFramework (INFO): Query an unspent TXO using the /getutxos URI
2026-10-02T16:09:36.240350Z TestFramework (INFO): Query a spent TXO using the /getutxos URI
2026-10-02T16:09:36.241426Z TestFramework (INFO): Query two TXOs using the /getutxos URI
2026-10-02T16:09:36.242814Z TestFramework (INFO): Compare binary GET and POST /getutxos responses for the same TXOs
2026-10-02T16:09:36.244860Z TestFramework (INFO): Test the /getutxos URI with and without /checkmempool
2026-10-02T16:09:36.277380Z TestFramework (INFO): Check some invalid requests
2026-10-02T16:09:36.280077Z TestFramework (ERROR): Unexpected exception:
Traceback (most recent call last):
File "/home/user/projects/bitcoin/test/functional/test_framework/test_framework.py", line 145, in main
self.run_test()
File "/home/user/projects/bitcoin/build/test/functional/interface_rest.py", line 216, in run_test
self.test_rest_request("/getutxos", http_method='POST', req_type=ReqType.BIN, body=bin_request + b'\x00', status=400, ret_type=RetType.OBJ)
File "/home/user/projects/bitcoin/build/test/functional/interface_rest.py", line 90, in test_rest_request
assert resp.status == status, f"Expected: {status}, Got: {resp.status} ({resp.reason}) - Response: {str(resp.read())}"
^^^^^^^^^^^^^^^^^^^^^
AssertionError: Expected: 400, Got: 200 (OK) - Response: b'\xca\x00\x00\x00>\xac\xfa1\xe1J\xe7nm\x8cp\xe8\x8e\x05~\x050Xx\xe1\x14\xb9\x0e\xa8\x86d\xab\xbed\xd3\x920\x01\x01\x01\x00\x00\x00\x00\xc9\x00\x00\x00\x80\x96\x98\x00\x00\x00\x00\x00"Q h\xe0|\xdaU\xb4<\x01q\xa8\x19\xeaT\xb91O=\x17\x05\x821\x04\xba\xc6f\xa6\x8eE\xee\x0cX\xd6'
2026-10-02T16:09:36.333952Z TestFramework (INFO): Not stopping nodes as test failed. The dangling processes will be cleaned up later.
2026-10-02T16:09:36.334372Z TestFramework (WARNING): Not cleaning up dir /tmp/bitcoin_func_test__mdx5r6s
2026-10-02T16:09:36.334541Z TestFramework (ERROR): Test failed. Test logging available at /tmp/bitcoin_func_test__mdx5r6s/test_framework.log
2026-10-02T16:09:36.334838Z TestFramework (ERROR):
2026-10-02T16:09:36.335156Z TestFramework (ERROR): Hint: Call /home/user/projects/bitcoin/test/functional/combine_logs.py '/tmp/bitcoin_func_test__mdx5r6s' to consolidate all logs
2026-10-02T16:09:36.335312Z TestFramework (ERROR):
2026-10-02T16:09:36.335445Z TestFramework (ERROR): If this failure happened unexpectedly or intermittently, please file a bug and provide a link or upload of the combined log.
2026-10-02T16:09:36.335608Z TestFramework (ERROR): https://github.com/bitcoin/bitcoin/issues
2026-10-02T16:09:36.335720Z TestFramework (ERROR):
[node 1] Cleaning up leftover process
[node 0] Cleaning up leftover process
</details>
so perhaps we could keep reader named and reject leftover bytes like how transaction decoding being handled here: https://github.com/bitcoin/bitcoin/blob/65896ac58ea63e29ad19d41b492e37f8722ac715/src/core_io.cpp#L174-L182
SpanReader reader{MakeByteSpan(strRequestMutable)};
reader >> fCheckMemPool >> LIMITED_VECTOR(vOutPoints, MAX_GETUTXOS_OUTPOINTS);
if (!reader.empty()) return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");