hyperledger/fabric · error

private data APIs are not allowed in chaincode Init()

Error message

private data APIs are not allowed in chaincode Init()

What it means

HandleGetState rejects requests to read private data (getState.Collection is set) while the transaction is the chaincode's Init transaction. Fabric forbids private data APIs during Init because Init runs during chaincode deployment/instantiation where private collection semantics (and the tx simulator setup) are not appropriate for collection reads.

Source

Thrown at core/chaincode/handler.go:693

	return rwPermission, nil
}

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

	var res []byte
	namespaceID := txContext.NamespaceID
	collection := getState.Collection
	chaincodeLogger.Debugf("[%s] getting state for chaincode %s, key %s, channel %s", shorttxid(msg.Txid), namespaceID, getState.Key, txContext.ChannelID)

	if isCollectionSet(collection) {
		if txContext.IsInitTransaction {
			return nil, errors.New("private data APIs are not allowed in chaincode Init()")
		}
		if err = errorIfCreatorHasNoReadPermission(namespaceID, collection, txContext); err != nil {
			return nil, err
		}
		res, err = txContext.TXSimulator.GetPrivateData(namespaceID, collection, getState.Key)
	} else {
		res, err = txContext.TXSimulator.GetState(namespaceID, getState.Key)
	}
	if err != nil {
		return nil, errors.WithStack(err)
	}
	if res == nil {
		chaincodeLogger.Debugf("[%s] No state associated with key: %s. Sending %s with an empty payload", shorttxid(msg.Txid), getState.Key, pb.ChaincodeMessage_RESPONSE)
	}

	// Send response msg back to chaincode. GetState will not trigger event
	return &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE, Payload: res, Txid: msg.Txid, ChannelId: msg.ChannelId}, nil
}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Move all private data reads/writes out of Init into the Invoke function
  2. Initialize dependent state lazily on first Invoke instead of during Init
  3. In Fabric 2.x lifecycle, avoid using --init-required so Init isn't invoked as a transaction; keep Init a no-op
  4. If collection presence must be checked, do it during a normal Invoke where the capability checks and permissions apply

Example fix

// before
func (c *CC) Init(stub shim.ChaincodeStubInterface) pb.Response {
    val, _ := stub.GetPrivateData("coll1", "counter")
    return shim.Success(val)
}
// after
func (c *CC) Init(stub shim.ChaincodeStubInterface) pb.Response { return shim.Success(nil) }
func (c *CC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    val, err := stub.GetPrivateData("coll1", "counter")
    if err != nil { return shim.Error(err.Error()) }
    return shim.Success(val)
}
Defensive patterns

Strategy: validation

Validate before calling

func (c *CC) Init(stub shim.ChaincodeStubInterface) pb.Response {
    // keep Init free of private data APIs entirely
    return shim.Success(nil)
}

Prevention

When it happens

Trigger: Chaincode's Init function (or code invoked with IsInitTransaction=true, e.g., legacy --init-required Invoke) calls GetPrivateData or GetState with a non-empty Collection; developer mistakenly reads private collections to 'warm up' state in Init.

Common situations: Porting Go chaincode where Init still reads private collections; Fabric 1.4→2.0 migrations where init semantics changed (--init-required); developers initializing caches/counters from private data during deployment.

Related errors


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