FuelLabs/fuel-core · error · anyhow::Error
Unable to decode bytes
Error message
Unable to decode bytes
What it means
OwnedTransactionIndexKey is a fixed 38-byte record: 32-byte owner Address, 4-byte big-endian block height, 2-byte big-endian tx index. The Decode impl converts bytes via TryFrom<&[u8]>, which fails with TryFromSliceError when the slice length differs from INDEX_SIZE; that error is flattened into this opaque message.
Source
Thrown at crates/fuel-core/src/graphql_api/storage/transactions.rs:178
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let bytes: [u8; INDEX_SIZE] = bytes.try_into()?;
Ok(Self::from(bytes))
}
}
impl Encode<OwnedTransactionIndexKey> for Manual<OwnedTransactionIndexKey> {
type Encoder<'a> = [u8; INDEX_SIZE];
fn encode(t: &OwnedTransactionIndexKey) -> Self::Encoder<'_> {
owned_tx_index_key(&t.owner, t.block_height, t.tx_idx)
}
}
impl Decode<OwnedTransactionIndexKey> for Manual<OwnedTransactionIndexKey> {
fn decode(bytes: &[u8]) -> anyhow::Result<OwnedTransactionIndexKey> {
OwnedTransactionIndexKey::try_from(bytes)
.map_err(|_| anyhow::anyhow!("Unable to decode bytes"))
}
}
#[derive(Clone, Debug, PartialOrd, Eq, PartialEq)]
pub struct OwnedTransactionIndexCursor {
pub block_height: BlockHeight,
pub tx_idx: TransactionIndex,
}
impl From<OwnedTransactionIndexKey> for OwnedTransactionIndexCursor {
fn from(key: OwnedTransactionIndexKey) -> Self {
OwnedTransactionIndexCursor {
block_height: key.block_height,
tx_idx: key.tx_idx,
}
}
}
View on GitHub (pinned to b9d4d170da)
Solutions
- Rebuild the off-chain database so the owned-transactions index is re-encoded by the current version.
- Pin all nodes/tooling accessing the DB to one fuel-core version.
- When writing custom tooling, use the OwnedTransactionIndexKey Encode impl rather than hand-built byte arrays.
Defensive patterns
Strategy: validation
Validate before calling
const OWNED_TX_INDEX_SIZE: usize = 38; // 32 owner + 4 height + 2 tx idx
if bytes.len() != OWNED_TX_INDEX_SIZE {
anyhow::bail!(
"owned-transaction index key has {} bytes, expected {}",
bytes.len(), OWNED_TX_INDEX_SIZE
);
} Type guard
fn is_owned_tx_index_key(bytes: &[u8]) -> bool {
bytes.len() == 38 // 32-byte owner + 4-byte height + 2-byte tx index
} Try / catch
match Manual::<OwnedTransactionIndexKey>::decode(&raw) {
Ok(k) => Ok(k),
Err(e) if e.to_string().contains("Unable to decode bytes") => {
// length mismatch: corrupt or foreign key; rebuild the off-chain DB
anyhow::bail!("owned-tx index corrupt ({} bytes); rebuild off-chain DB", raw.len())
}
Err(e) => Err(e),
} Prevention
- Keep every process touching the DB on one fuel-core version.
- Use the provided Encode/Decode impls for index keys; never hand-pack bytes.
- Rebuild the off-chain database after format-breaking upgrades.
When it happens
Trigger: Decoding a key from the owned-transactions index whose byte length is not exactly 38 — corrupted keys, foreign data in the column, or a codec/format change between fuel-core versions.
Common situations: Reading an off-chain database written by an incompatible fuel-core version; partially written/corrupted storage after a crash; tooling that writes raw keys into the column with a different layout.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Invalid coin type {:?}
- Failed to get registry root: {}
- Failed to get registry root: {}
- The block has more than `u16::MAX` transactions, {}
- Transaction status already exists for tx {}
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/0b04c024dba09aa9.
Report an issue: GitHub.