{"record":{"id":"c733d09dcdd7069b","repo":"linera-io/linera-protocol","slug":"invalid-hex","errorCode":null,"errorMessage":"invalid hex","messagePattern":"invalid hex","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"linera-bridge/contracts/evm-bridge/src/service.rs","lineNumber":195,"sourceCode":"    /// The ERC-20 token address on the source EVM chain (hex-encoded).\n    async fn token_address(&self) -> String {\n        let params: BridgeParameters = self.runtime.application_parameters();\n        format!(\"0x{}\", hex::encode(params.token_address))\n    }\n\n    /// The configured EVM JSON-RPC endpoint, or empty if finality verification\n    /// is disabled.\n    async fn rpc_endpoint(&self) -> String {\n        self.state.rpc_endpoint.get().clone()\n    }\n\n    /// Whether a deposit with the given hash has been processed.\n    ///\n    /// The hash is the hex-encoded keccak-256 of the deposit key\n    /// (see [`evm_bridge::DepositKey::hash`]).\n    async fn is_deposit_processed(&self, hash: String) -> bool {\n        let bytes: [u8; 32] = hex::decode(hash.strip_prefix(\"0x\").unwrap_or(&hash))\n            .expect(\"invalid hex\")\n            .try_into()\n            .expect(\"hash must be 32 bytes\");\n        self.state\n            .processed_deposits\n            .contains(&bytes)\n            .await\n            .expect(\"failed to check processed deposits\")\n    }\n\n    /// Verifies that the given EVM block hash is finalized on the source chain.\n    ///\n    /// Makes the EVM JSON-RPC calls in the service runtime so that the contract\n    /// sees a single deterministic oracle response (the boolean result) instead\n    /// of multiple raw HTTP responses with non-deterministic headers.\n    async fn is_block_hash_finalized(&self, block_hash: String) -> bool {\n        let bytes: [u8; 32] = hex::decode(block_hash.strip_prefix(\"0x\").unwrap_or(&block_hash))\n            .expect(\"invalid hex\")\n            .try_into()","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/contracts/evm-bridge/src/service.rs#L177-L213","documentation":"The is_deposit_processed GraphQL query handler hex-decodes the submitted hash string and expects success. The panic means the query argument was not valid hex — odd-length string, non-hex characters, or a 0x-prefixed string with junk after the prefix (the optional 0x is stripped, everything else must decode). A panic in a query handler surfaces as a GraphQL error response rather than a boolean result.","triggerScenarios":"Querying isDepositProcessed(hash: ...) with a base64 string, an odd-length hex string, a hash with a '0X' uppercase prefix containing invalid chars, or an empty string after prefix stripping; passing the raw DepositKey bytes instead of their hex encoding.","commonSituations":"Frontends that render hashes with mixed case or ellipsis truncation before querying; scripts piping arbitrary identifiers into the query; confusion between keccak hex output and multihash/base58 encodings.","solutions":["Send the hash exactly as hex::encode produced it: 0x + 64 lowercase hex chars","Validate client-side with a regex like /^0x[0-9a-fA-F]{64}$/ before issuing the query","If you control the service, replace the expect with a GraphQL error (Result-based handler) for a friendlier message","Check for stray whitespace, quotes, or truncation in the query template"],"exampleFix":"# before\nquery { isDepositProcessed(hash: \"0x9f2b1c\" ) }  # truncated / odd-length hex -> GraphQL error\n\n# after\n# Always send keccak-256 output verbatim: 0x + 64 hex chars\nquery { isDepositProcessed(hash: \"0x3fa85164e8b7c0c2d4a6b9e0f5d2c8a1b7e6d4f3c2a1908e7d6c5b4a3928f1e0d\") }","handlingStrategy":"validation","validationCode":"// Validate before querying:\nfn is_query_hash(s: &str) -> bool {\n    let h = s.strip_prefix(\"0x\").unwrap_or(s);\n    h.len() == 64 && h.chars().all(|c| c.is_ascii_hexdigit())\n}\nassert!(is_query_hash(&hash));\nlet processed = service.is_deposit_processed(hash).await;","typeGuard":"fn is_deposit_hash_query_arg(s: &str) -> bool {\n    let h = s.strip_prefix(\"0x\").unwrap_or(s);\n    h.len() % 2 == 0 && h.len() == 64 && h.chars().all(|c| c.is_ascii_hexdigit())\n}","tryCatchPattern":"// GraphQL surfaces the panic as an errors[] entry; match on the message:\nmatch resp.errors.first() {\n    Some(e) if e.message.contains(\"invalid hex\") => fix_client_encoding(),\n    _ => use_result(resp.data),\n}","preventionTips":["Always derive the hash via hex::encode of the 32-byte keccak output","Reject user-pasted hashes that fail /^0x[0-9a-fA-F]{64}$/ at the input boundary","Prefer GraphQL variables over string interpolation"],"tags":["linera","bridge","graphql","hex","input-validation","panic"],"backgroundTag":"invalid-hex-input","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}