fix: single-element JSON-RPC batch response shape for UTXO chains#2242
Conversation
…pe for UTXO chains
Single-element batch requests like `[{"id":"1","method":"getblockhash","params":[0]}]`
were incorrectly treated as non-batch requests because `ParseMsg` used `len(msgs) == 1`
to decide batch vs single. This caused two issues:
1. Response returned as a single object instead of an array, with ID replaced by "-1"
(from the cache formatter's default value in `ecosystem/cache/format/jsonrpc.go`).
2. For UTXO chains (BTC, DOGE, LTC, BCH), the relay pipeline reconstructed batch
responses adding `"jsonrpc":"2.0"` and stripping `"error":null`, breaking JSON-RPC 1.0
compatibility.
Changes:
- Add `ParseJsonRPCMsgWithBatchFlag()` that checks for `[` before unmarshaling and
returns an `isBatch` flag so callers can distinguish `{...}` from `[{...}]`.
- Update `JsonRPCChainParser.ParseMsg` and `TendermintChainParser.ParseMsg` to use the
flag: `len(msgs) == 1 && !isBatch` for the batch decision.
- Rename `checkBTCResponseAndFixReply` → `checkUTXOResponseAndFixReply` and extend it
to handle batch (array) responses, stripping injected `jsonrpc` field and preserving
`error:null` for all UTXO-family chains.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review Summary by QodoFix single-element JSON-RPC batch response shape for UTXO chains
WalkthroughsDescription• Fix single-element JSON-RPC batch requests returning wrong response shape • Detect batch requests by checking for [ before unmarshaling, not just message count • Extend UTXO response fix to handle batch arrays, preserving error:null and stripping jsonrpc field • Add comprehensive tests for batch detection and UTXO response formatting Diagramflowchart LR
A["Single-element batch request<br/>[{...}]"] --> B["ParseJsonRPCMsgWithBatchFlag<br/>checks for ["]
B --> C["isBatch flag returned"]
C --> D["ParseMsg uses isBatch<br/>to decide batch vs single"]
D --> E["Batch response wrapped<br/>in array"]
F["UTXO chain response<br/>with jsonrpc:2.0"] --> G["checkUTXOResponseAndFixReply<br/>handles single & batch"]
G --> H["Strip jsonrpc field<br/>preserve error:null"]
H --> I["JSON-RPC 1.0 compatible<br/>response"]
File Changes1. protocol/chainlib/chainproxy/rpcInterfaceMessages/jsonRPCMessage.go
|
Code Review by Qodo
1.
|
Codecov Report❌ Patch coverage is
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 9 files with indirect coverage changes 🚀 New features to boost your workflow:
|
ParseJsonRPCMsgWithBatchFlag now: - Strips UTF-8 BOM (0xEF 0xBB 0xBF) before parsing - Skips JSON whitespace to find the true first byte for batch detection - Falls back to batch unmarshal if single-object unmarshal fails Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For single-element batches, CompareRequestedBlockInBatch was never called (it only runs for idx > 0), leaving earliestRequestedBlock at its init value (LATEST_BLOCK for tendermint, 0 for jsonrpc). This broke downstream routing that relies on RequestedBlock(). With one message in the batch, earliest == latest by definition. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add tests for both JsonRPC and TendermintRPC parsers confirming that a single-element batch requesting a specific block has earliest == latest, not the init value (0 or LATEST_BLOCK). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
User description
Summary
[{"id":"1","method":"getblockhash","params":[0]}]) were treated as non-batch, returning a single object with"id":"-1"instead of an array with the original ID."jsonrpc":"2.0"injected and"error":nullstripped by the relay pipeline, breaking JSON-RPC 1.0 compatibility.Root causes
ParseMsgusedlen(msgs) == 1to decide batch vs single — a single-element batch[{...}]matched this condition. AddedParseJsonRPCMsgWithBatchFlag()that checks for[before unmarshaling and returns anisBatchflag.checkBTCResponseAndFixReplyonly handled single (non-array) responses. Renamed tocheckUTXOResponseAndFixReplyand extended to also process batch responses, stripping the injectedjsonrpcfield and preservingerror:null.Files changed
protocol/chainlib/chainproxy/rpcInterfaceMessages/jsonRPCMessage.goParseJsonRPCMsgWithBatchFlag()protocol/chainlib/jsonRPC.goisBatchflag; rename BTC→UTXOprotocol/chainlib/tendermintRPC.goisBatchflagprotocol/chainlib/common.go*_test.goTest plan
TestParseJsonRPCMsgWithBatchFlag— single object, single-element array, multi-element array, empty array, missing ID, invalid JSONTestCheckUTXOResponseAndFixReply— single/batch/single-element-batch responses, non-UTXO passthrough, error preservationprotocol/chainlib/...test suite passesprotocol/rpcsmartrouter/...test suite passes🤖 Generated with Claude Code
Generated description
Below is a concise technical summary of the changes proposed in this PR:
Ensure
ParseJsonRPCMsgWithBatchFlaginrpcInterfaceMessagesand the JSON-RPC/Tendermint chain parsers detect single-element arrays as batches soJsonRPCChainParserandTendermintChainParsermaintain correct requested-block metadata. Normalize UTXO-family replies by routing responses throughcheckUTXOResponseAndFixReplyso injectedjsonrpcfields are removed whileerror:nullremains, covering both single and batch payloads.ParseJsonRPCMsgWithBatchFlag, returning anisBatchflag, and using it in the JSON-RPC and Tendermint parsers plus new parser tests.Modified files (6)
Latest Contributors(0)
checkUTXOResponseAndFixReplyto strip injectedjsonrpc, preserveerror:null, and handle batch arrays and add tests for each scenario.Modified files (2)
Latest Contributors(0)