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
- Verify the key source — only keys from the txID index range (txIDIdxKeyPrefix to prefix+1) should reach this function.
- If production keys lack the prefix, treat the index as corrupted and rebuild the block index.
- 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
- Only feed retrieveTxID keys sourced from the txID index range iterator.
- Construct keys exclusively via constructTxIDKey so the prefix is always present.
- Never hand-edit or manually copy leveldb files while the peer is running.
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
- invalid txIDKey - zero-length slice
- invalid txIDKey {%x}, fewer bytes present
- error decoding the block number
- error decoding the data hash
- error decoding the previous hash
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/910b80095d20d71b.
Report an issue: GitHub.