hyperledger/fabric · error

unexpected call. Boot KV Hashes are persisted only for the d

Error message

unexpected call. Boot KV Hashes are persisted only for the data imported from snapshot

What it means

FetchBootKVHashes returns boot-time KV hashes only for data that was imported from a snapshot at bootstrap. If the store was not created from a snapshot, or the requested block is past the snapshot's last block, the call is considered an API misuse and this error is returned. Boot KV hashes simply do not exist for such blocks.

Source

Thrown at core/ledger/pvtdatastorage/store.go:760

			return nil, err
		}

		// for each transaction which misses private data, make an entry in missingBlockPvtDataInfo
		for index, isSet := bitmap.NextSet(0); isSet; index, isSet = bitmap.NextSet(index + 1) {
			txNum := uint64(index)
			missingPvtDataInfo.Add(missingDataKey.blkNum, txNum, missingDataKey.ns, missingDataKey.coll)
		}
	}

	return missingPvtDataInfo, nil
}

// FetchBootKVHashes returns the KVHashes from the data that was loaded from a snapshot at the time of
// bootstrapping. This function returns an error if the supplied blkNum is greater than the last block
// number in the booting snapshot
func (s *Store) FetchBootKVHashes(blkNum, txNum uint64, ns, coll string) (map[string][]byte, error) {
	if s.bootsnapshotInfo.createdFromSnapshot && blkNum > s.bootsnapshotInfo.lastBlockInSnapshot {
		return nil, errors.New(
			"unexpected call. Boot KV Hashes are persisted only for the data imported from snapshot",
		)
	}
	encVal, err := s.db.Get(
		encodeBootKVHashesKey(
			&bootKVHashesKey{
				blkNum: blkNum,
				txNum:  txNum,
				ns:     ns,
				coll:   coll,
			},
		),
	)
	if err != nil || encVal == nil {
		return nil, err
	}
	bootKVHashes, err := decodeBootKVHashesVal(encVal)
	if err != nil {

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Check store.BootsnapshotInfo().CreatedFromSnapshot before calling; skip boot-KV-hash logic for genesis-booted stores.
  2. Clamp blkNum to lastBlockInSnapshot before calling FetchBootKVHashes.
  3. Handle the error gracefully — treat 'no boot hashes' as an empty result rather than a fatal condition in reconciliation code.
  4. If snapshot bootstrap was intended, verify the snapshot import completed and the correct ledger config was used at startup.

Example fix

// before
hashes, err := store.FetchBootKVHashes(blkNum, txNum, ns, coll)
// after
info := store.BootsnapshotInfo()
if !info.CreatedFromSnapshot || blkNum > info.LastBlockInSnapshot {
    return nil, nil // no boot KV hashes exist for this range
}
hashes, err := store.FetchBootKVHashes(blkNum, txNum, ns, coll)
Defensive patterns

Strategy: type-guard

Validate before calling

info := store.BootsnapshotInfo()
canFetchBoot := info != nil && info.CreatedFromSnapshot && blkNum <= info.LastBlockInSnapshot

Type guard

func bootKVHashesAvailable(info *BootsnapshotInfo, blkNum uint64) bool {
    return info != nil && info.CreatedFromSnapshot && blkNum <= info.LastBlockInSnapshot
}

Try / catch

if !bootKVHashesAvailable(store.BootsnapshotInfo(), blkNum) {
    return nil, nil // no boot hashes for this block; skip
}
hashes, err := store.FetchBootKVHashes(blkNum, txNum, ns, coll)
if err != nil {
    if strings.Contains(err.Error(), "unexpected call") {
        return nil, nil
    }
    return nil, err
}

Prevention

When it happens

Trigger: Calling FetchBootKVHashes(blkNum, ...) on a store whose bootsnapshotInfo.createdFromSnapshot is false, or with blkNum > bootsnapshotInfo.lastBlockInSnapshot — e.g. reconciling/purging logic probing blocks outside the boot snapshot range.

Common situations: Peers bootstrapped from genesis (not a snapshot) whose pvtdata reconciliation path still probes boot KV hashes; reconciliation jobs scanning past the snapshot boundary; mixing snapshot-booted and genesis-booted peers in the same operational runbook.

Related errors


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