diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseParser.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseParser.kt index e68f45f3..de1891fa 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseParser.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseParser.kt @@ -32,7 +32,15 @@ abstract class ResponseParser { private val log = LoggerFactory.getLogger(ResponseParser::class.java) } - private val jsonFactory = JsonFactory() + // Some upstreams (e.g. Moca's Tendermint EVM) embed raw control characters + // such as LF inside JSON string values (notably in `web3_clientVersion` + // results). Default Jackson rejects those with "Illegal unquoted character" + // before the response ever reaches the upstream-settings detector, so we + // relax just this one rule at the response-parsing layer to keep the + // payload flowing. + private val jsonFactory = JsonFactory().apply { + enable(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS) + } abstract fun build(state: Preparsed): T diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/ResponseRpcParserSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/ResponseRpcParserSpec.groovy index 5d4796c5..04d842b9 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/ResponseRpcParserSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/ResponseRpcParserSpec.groovy @@ -235,4 +235,18 @@ class ResponseRpcParserSpec extends Specification { !act.hasResult() } + // Regression: Moca's Tendermint EVM returns a web3_clientVersion result + // that contains a raw, unescaped LF (CTRL-CHAR, code 10) inside the JSON + // string. Default Jackson rejects that with "Illegal unquoted character", + // which used to break upstream node-type detection. + def "Parse string response with unescaped control chars"() { + setup: + def json = '{"jsonrpc":"2.0","id":1,"result":"Version dev ()\nCompiled at using Go go1.23.11 (amd64)"}' + when: + def act = parser.parse(json.getBytes("UTF-8")) + then: + act.error == null + new String(act.result) == '"Version dev ()\nCompiled at using Go go1.23.11 (amd64)"' + } + }