hyperledger/fabric · error

invalid txIDKey {%x} - unexpected prefix

Error message

invalid txIDKey {%x} - unexpected prefix

What it means

retrieveTxID requires encoded keys to start with the txID index prefix byte. If the first byte does not match txIDIdxKeyPrefix, the key is not a txID key and this error reports the raw bytes in hex. It protects the decoder from reading foreign or corrupted index entries.

Source

Thrown at common/ledger/blkstorage/blockindex.go:432

func constructTxIDKey(txID string, blkNum, txNum uint64) []byte {
	k := append(
		[]byte{txIDIdxKeyPrefix},
		util.EncodeOrderPreservingVarUint64(uint64(len(txID)))...,
	)
	k = append(k, txID...)
	k = append(k, util.EncodeOrderPreservingVarUint64(blkNum)...)
	return append(k, util.EncodeOrderPreservingVarUint64(txNum)...)
}

// retrieveTxID takes input an encoded txid key of the format `prefix:len(TxID):TxID:BlkNum:TxNum`
// and returns the TxID from this
func retrieveTxID(encodedTxIDKey []byte) (string, error) {
	if len(encodedTxIDKey) == 0 {
		return "", errors.New("invalid txIDKey - zero-length slice")
	}
	if encodedTxIDKey[0] != txIDIdxKeyPrefix {
		return "", errors.Errorf("invalid txIDKey {%x} - unexpected prefix", encodedTxIDKey)
	}
	remainingBytes := encodedTxIDKey[utf8.RuneLen(txIDIdxKeyPrefix):]

	txIDLen, n, err := util.DecodeOrderPreservingVarUint64(remainingBytes)
	if err != nil {
		return "", errors.WithMessagef(err, "invalid txIDKey {%x}", encodedTxIDKey)
	}
	remainingBytes = remainingBytes[n:]
	if len(remainingBytes) <= int(txIDLen) {
		return "", errors.Errorf("invalid txIDKey {%x}, fewer bytes present", encodedTxIDKey)
	}
	return string(remainingBytes[:int(txIDLen)]), nil
}

func retrieveBlockNum(encodedTxIDKey []byte, BlkNumStartingIndex int) (uint64, error) {
	n, _, err := util.DecodeOrderPreservingVarUint64(encodedTxIDKey[BlkNumStartingIndex:])
	return n, err
}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Verify the key source — only keys from the txID index range (txIDIdxKeyPrefix to prefix+1) should reach this function.
  2. If production keys lack the prefix, treat the index as corrupted and rebuild the block index.
  3. Fix the calling code to construct keys with constructTxIDKey so the prefix is always present.
Defensive patterns

Strategy: validation

Validate before calling

if len(key) == 0 || key[0] != txIDIdxKeyPrefix {
    return "", errors.Errorf("not a txID key: %x", key)
}

Type guard

func hasTxIDKeyPrefix(k []byte) bool {
    return len(k) > 0 && k[0] == txIDIdxKeyPrefix
}

Try / catch

txID, err := retrieveTxID(key)
if err != nil && strings.Contains(err.Error(), "unexpected prefix") {
    return fmt.Errorf("index key %x is not a txID key — suspect index corruption: %w", key, err)
}
if err != nil { return err }

Prevention

When it happens

Trigger: An iterator range spanning unintended keys, a corrupted index DB whose keys lost their prefix byte, or direct calls (including tests) with wrongly-prefixed byte slices.

Common situations: Index DB corruption after unclean shutdown or partial file copy; someone manually manipulating the leveldb files; decoder tests exercising invalid input.

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/910b80095d20d71b. Report an issue: GitHub.