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

  1. Rebuild the off-chain database so the owned-transactions index is re-encoded by the current version.
  2. Pin all nodes/tooling accessing the DB to one fuel-core version.
  3. 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

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

Related errors


AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16). Data as JSON: /api/errors/0b04c024dba09aa9. Report an issue: GitHub.