{"record":{"id":"1ae600ae9b8a0d20","repo":"FuelLabs/fuel-core","slug":"invalid-coin-type","errorCode":null,"errorMessage":"Invalid coin type {:?}","messagePattern":"Invalid coin type (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/fuel-core/src/graphql_api/storage/coins/codecs.rs","lineNumber":138,"sourceCode":"                serialized_coin[start..end].copy_from_slice(&amount.to_be_bytes());\n                start = end;\n                end = end.saturating_add(Nonce::LEN);\n                serialized_coin[start..end].copy_from_slice(nonce.as_ref());\n                start = end;\n                serialized_coin[start] = CoinType::Message as u8;\n\n                SerializedCoinsToSpendIndexKey::Message(serialized_coin)\n            }\n        }\n    }\n}\n\nimpl Decode<CoinsToSpendIndexKey> for Manual<CoinsToSpendIndexKey> {\n    fn decode(bytes: &[u8]) -> anyhow::Result<CoinsToSpendIndexKey> {\n        let coin_type = match bytes.last() {\n            Some(0) => CoinType::Coin,\n            Some(1) => CoinType::Message,\n            _ => return Err(anyhow::anyhow!(\"Invalid coin type {:?}\", bytes.last())),\n        };\n\n        let result = match coin_type {\n            CoinType::Coin => {\n                let bytes: [u8; COIN_VARIANT_SIZE] = bytes.try_into()?;\n                let mut start;\n                let mut end = RETRYABLE_FLAG_SIZE;\n                start = end;\n                end = end.saturating_add(Address::LEN);\n                let owner = Address::try_from(&bytes[start..end])?;\n                start = end;\n                end = end.saturating_add(AssetId::LEN);\n                let asset_id = AssetId::try_from(&bytes[start..end])?;\n                start = end;\n                end = end.saturating_add(AMOUNT_SIZE);\n                let amount = u64::from_be_bytes(bytes[start..end].try_into()?);\n                start = end;\n                end = end.saturating_add(UTXO_ID_SIZE);","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/FuelLabs/fuel-core/blob/b9d4d170da3a31c9ace5f963d633b326348e0d42/crates/fuel-core/src/graphql_api/storage/coins/codecs.rs#L120-L156","documentation":"The off-chain coins-to-spend index encodes each key with a trailing discriminant byte: 0 for Coin, 1 for Message. The Manual Decode impl inspects bytes.last() and rejects anything other than 0 or 1 with this error. Hitting it means the stored index bytes are not in the expected format — corruption or data written by an incompatible fuel-core version/codec.","triggerScenarios":"Iterating the CoinsToSpendIndexKey column and encountering a key whose last byte is neither 0 nor 1 — corrupted storage, a truncated/mangled key, or an on-disk format written by a different codec version.","commonSituations":"Upgrading fuel-core across off-chain index format changes without re-syncing; bit rot or partial writes; pointing a newer node at an old database directory.","solutions":["Rebuild the off-chain database (delete it and let the node re-index from on-chain data).","Ensure all nodes reading/writing the database run the same fuel-core version.","If it recurs on fresh data, inspect the writer of that column for a codec mismatch and file/report the version pair involved."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Guard before decoding an index key: discriminant byte must be 0 (Coin) or 1 (Message).\nfn valid_coin_index_key(bytes: &[u8]) -> bool {\n    matches!(bytes.last(), Some(0) | Some(1))\n}\nif !valid_coin_index_key(&raw_key) {\n    anyhow::bail!(\"foreign/corrupt coins-to-spend index entry; rebuild off-chain DB\");\n}","typeGuard":"fn coin_type_of(bytes: &[u8]) -> Option<CoinType> {\n    match bytes.last() {\n        Some(0) => Some(CoinType::Coin),\n        Some(1) => Some(CoinType::Message),\n        _ => None,\n    }\n}","tryCatchPattern":"match Manual::<CoinsToSpendIndexKey>::decode(&raw) {\n    Ok(k) => Ok(k),\n    Err(e) if e.to_string().contains(\"Invalid coin type\") => {\n        // storage-format mismatch or corruption: rebuild the off-chain index\n        anyhow::bail!(\"coins index corrupt ({}); wipe and re-index off-chain DB\", e)\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Never mix fuel-core versions on one database directory.","Rebuild the off-chain DB (it is derivable) after upgrades that touch index codecs.","Report the key bytes and node version when filing issues about this error."],"tags":["storage","codec","corruption","off-chain","rust"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}