#36385 rest: fix getutxos POST body decoding

ReviewabilityReviewsAgreementSize
#36385 l0rinc rest: fix getutxos POST body decodingWaiting on author1Strong7 + 17 tests
  • Ensure POST requests to /rest/getutxos find valid outpoints instead of incorrectly reporting no hit
  • Align POST behavior with GET queries so REST clients get accurate UTXO results
  • Cap input vector allocation and reject trailing or malformed request bytes
  • Ready to review
  • Substantive support with no open objections
  • Verified locally by reproducing the issue and confirming matching responses (Zeegaths)
  • Tested 14 POST bodies in binary and hex on regtest to confirm expected responses (fametrano)
  • Open suggestion to return HTTP 400 when a POST body contains no outpoints (fametrano)
  • 7 lines added or modified outside tests
  • 17 lines added or modified in tests
  • 11 lines removed in total
  • 2 files, 4 commits
RPC / REST / ZMQ#3 of 55
P1 · bug fix
  • P1 after comparing with the other PRs here (assessed alone as P2): Raised to P1: like #35837 it silently returns wrong data to existing callers (POST getutxos always misses), plus an unbounded allocation; same consequence class as the other P1 correctness bugs.
  • P2 because it fixes a correctness bug causing false negative UTXO results for REST clients
  • It prevents unbounded memory allocation on large declared outpoint counts
Reviewability: ReadyStalePaused
Agreement: StrongPositivePositive w/ caveatsNeutralMildDisputedBlockedCrickets
Reviews: current code-review ACKs, then (+stale ACKs) and -NACKs; greener = more ACKs.
Size: lines added or modified outside tests, then in tests; greener = smaller.

Goal

Fixes request body deserialization for the `/rest/getutxos` POST endpoint. Rather than writing the request string into a `DataStream` with `operator<<`, which prepended a `CompactSize` length prefix, the endpoint parses body bytes in place using `SpanReader`. The change also caps vector deserialization at `MAX_GETUTXOS_OUTPOINTS`, rejects trailing unparsed bytes with an HTTP 400 error, and fails malformed hex inputs using `TryParseHex`.

Problem: Binary and hex POST requests to `/rest/getutxos` incorrectly report that valid outpoints do not exist. Serializing the body string with `operator<<` inserts a length prefix, causing the parser to interpret the length byte as the `checkmempool` flag and misalign all subsequent outpoints. In addition, an unbounded vector read could allocate memory for large declared counts before reaching the 15-outpoint validation check, and trailing or malformed data was silently accepted.

Evidence of importance:

  • PR description: 'Binary GET and POST requests to /rest/getutxos for the same outpoints should agree. GET finds the unspent output, but POST incorrectly reports no hit.'
  • Commit 57dc676b59: 'Writing the POST body to DataStream with operator<< serializes it as a string and prepends a CompactSize length, which the parser incorrectly reads as request fields.'

Risk of a mistake: medium (rpc_api, tests). A flaw in REST request parsing could cause node endpoint failures or return incorrect UTXO query results to client applications.

Changed since the previous assessment: No functional changes to the patch logic; the PR remains split into four commits addressing test characterization, decoder correction, vector size limits, and in-place `SpanReader` usage.

Reviewability

Paused · Waiting on author

Open objection from fametrano with no author reply for 7 days; review would repeat what has been asked.

Waiting on (from the thread read):

  • author: response to fametrano's suggestion to return HTTP 400 for empty outpoints 2026-10-25

Agreement

Strong

substantive support, no open objection (Zeegaths, fametrano)

Thread notes: Added Tested ACK and suggestion from fametrano to reject empty-outpoint POST bodies with HTTP 400.

Objections, suggestions, and questions:

ReviewerTypeHarm, request, or questionStatusMust beAuthor repliedQuote
b-l-u-e (contributor)objection, correctnessPOST decoder silently accepts trailing data instead of rejecting requests with unconsumed bytesfixed · fix pushed · author added trailing-byte rejection and testschangedyes2026-10-02: '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'
Status from: 2026-10-04: l0rinc: 'added trailing-byte rejection and coverage for binary and hex POST bodies'
b-l-u-e (contributor)objection, correctnessPOST /getutxos accepts odd-length hex bodies with 200 OK instead of rejecting with HTTP 400fixed · fix pushed · author pushed a fix for the remaining issues and added b-l-u-e as co-authorchangedyes2026-10-04: 'AssertionError: Expected: 400, Got: 200 (OK)'
Status from: 2026-10-04: l0rinc: 'we might as well fix the remaining issues. Added @b-l-u-e as co-author.'
fametrano (contributor)suggestion, interfacereturn HTTP 400 'Error: empty request' when a POST body contains no outpointsopenansweredno2026-10-25: 'Adding `if (vOutPoints.empty()) return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");` before the max-outpoints check makes these a 400 too.'

Support:

  • Zeegaths (none) (2026-10-01) [tested]: reproduced the issue locally and verified response match on the PR branch
  • fametrano (contributor) (Tested ACK, 2026-10-25) [both: interface_rest.py]: tested 14 POST bodies in binary and hex against regtest nodes at base and PR head, verifying that all 28 match GET / 400 expectations on PR head versus 10 on base

Participants:

  • Zeegaths (none, support, 2 statements 2026-10-01 to 2026-10-02): reproduced the issue and verified the fix on the PR branch
  • b-l-u-e (contributor, objection, 2 statements 2026-10-02 to 2026-10-04): pointed out unconsumed trailing POST bytes and found unhandled odd-length hex bodies
  • fametrano (contributor, support, 1 statements 2026-10-25 to 2026-10-25): tACK; suggested returning HTTP 400 when a POST body contains no outpoints

Files

22 lines under test/bench/ci.

  • test/functional/interface_rest.py +17/-5
  • src/rest.cpp +7/-6

Card

The short text the ranking pass compares across PRs.

Fixes request body deserialization in the /rest/getutxos endpoint for binary and hex POST queries. Serializing the raw body into a DataStream with operator<< prepended a CompactSize length, misaligning parsed fields and causing valid POST queries to find no UTXOs. The change deserializes the payload in place using SpanReader, bounds vector deserialization before allocation, rejects trailing bytes, and errors on malformed hex. Functional tests are updated to ensure GET and POST requests return matching binary output.

Data

dossier JSON · extract JSON · model ledger, generated 2026-10-26T01:21, confidence high, input hash 594a1f443762875b
thread read through 2026-10-25 (8 statements), head b7622767c6
labels: RPC/REST/ZMQ

Categories

RPC / REST / ZMQ

P1 · bug fix (assessed alone as P2; Raised to P1: like #35837 it silently returns wrong data to existing callers (POST getutxos always misses), plus an unbounded allocation; same consequence class as the other P1 correctness bugs.)

Fixes a data correctness bug where POST requests to `/rest/getutxos` fail to find valid unspent outputs due to improper length-prefixed stream deserialization. As noted in the PR description, 'GET finds the unspent output, but POST incorrectly reports no hit', causing false negatives for consumers of the REST endpoint while also patching an unbounded allocation risk on input outpoints.

Membership: Matches the RPC/REST/ZMQ label and directly modifies src/rest.cpp endpoint deserialization.

Factors: security/stability 1, bug 2, performance 0, user value 2, leverage 0