hyperledger/fabric · error

query iterator not found

Error message

query iterator not found

What it means

Raised in HandleQueryStateNext when txContext.GetQueryIterator(id) returns nil, meaning no query iterator is registered in the transaction context under the given iterator ID. The handler cannot continue an iteration that was never created or has already been cleaned up.

Source

Thrown at core/chaincode/handler.go:900

		txContext.CleanupQueryContext(iterID)
		return nil, errors.Wrap(err, "marshal failed")
	}

	chaincodeLogger.Debugf("Got keys and values. Sending %s", pb.ChaincodeMessage_RESPONSE)
	return &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE, Payload: payloadBytes, Txid: msg.Txid, ChannelId: msg.ChannelId}, nil
}

// Handles query to ledger for query state next
func (h *Handler) HandleQueryStateNext(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error) {
	queryStateNext := &pb.QueryStateNext{}
	err := proto.Unmarshal(msg.Payload, queryStateNext)
	if err != nil {
		return nil, errors.Wrap(err, "unmarshal failed")
	}

	queryIter := txContext.GetQueryIterator(queryStateNext.Id)
	if queryIter == nil {
		return nil, errors.New("query iterator not found")
	}

	totalReturnLimit := h.calculateTotalReturnLimit(nil)

	payload, err := h.QueryResponseBuilder.BuildQueryResponse(txContext, queryIter, queryStateNext.Id, false, totalReturnLimit)
	if err != nil {
		txContext.CleanupQueryContext(queryStateNext.Id)
		return nil, errors.WithStack(err)
	}
	if payload == nil {
		txContext.CleanupQueryContext(queryStateNext.Id)
		return nil, errors.New("marshal failed: proto: Marshal called with nil")
	}

	payloadBytes, err := proto.Marshal(payload)
	if err != nil {
		txContext.CleanupQueryContext(queryStateNext.Id)
		return nil, errors.Wrap(err, "marshal failed")

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Complete iterator consumption (Next/Close) within a short window, well inside the peer's queryIterator TTL.
  2. Never call Next after Close; check for closed iterators in chaincode code.
  3. Reduce long-running transactions or increase iterator TTL (ledger state query limit settings) on the peer.
  4. If the ID is genuinely stale, treat as a lifecycle bug: recreate the original query (GetQueryResult) instead of continuing.

Example fix

// chaincode-side before
iter, _ := stub.GetStateByRange(start, end)
// ...long processing...
iter.HasNext() // peer may have expired iterator
// after: consume and close promptly
iter, _ := stub.GetStateByRange(start, end)
for iter.HasNext() {
	kv, _ := iter.Next()
	process(kv)
}
iter.Close()
Defensive patterns

Strategy: validation

Validate before calling

// chaincode-side: ensure the iterator is used before it expires and not after Close
if iter == nil {
	return shim.Error("iterator missing: re-run the original query")
}

Try / catch

iter, err := stub.GetStateByRange(start, end)
if err != nil { return shim.Error(err.Error()) }
defer iter.Close()
for iter.HasNext() {
	kv, err := iter.Next()
	if err != nil {
		if strings.Contains(err.Error(), "query iterator not found") {
			// iterator expired on peer; restart the query
			return shim.Error("iterator expired; re-issue query")
		}
		return shim.Error(err.Error())
	}
}

Prevention

When it happens

Trigger: The chaincode sends a QueryStateNext message with an iterator ID that does not exist in txContext — typically after the iterator's TTL elapsed, after the query context was cleaned up, or when the ID was never created by a prior GetQueryResult/QueryStateNext response.

Common situations: Chaincode holds a state-query iterator across a long transaction while the peer's iterator TTL expires; calling Next after Close; parallel transactions reusing iterator IDs incorrectly; peer restart clearing in-memory iterator state.

Related errors


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