{"record":{"id":"bf01ae6c8ceec9d8","repo":"hyperledger/fabric","slug":"no-ledger-context-s-s-v","errorCode":null,"errorMessage":"no ledger context: %s %s\n\n %+v","messagePattern":"no ledger context: (.+?) (.+?)\n\n %\\+v","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/chaincode/handler.go","lineNumber":589,"sourceCode":"}\n\nfunc (h *Handler) Notify(msg *pb.ChaincodeMessage) {\n\ttctx := h.TXContexts.Get(msg.ChannelId, msg.Txid)\n\tif tctx == nil {\n\t\tchaincodeLogger.Debugf(\"notifier Txid:%s, channelID:%s does not exist for handling message %s\", msg.Txid, msg.ChannelId, msg.Type)\n\t\treturn\n\t}\n\n\tchaincodeLogger.Debugf(\"[%s] notifying Txid:%s, channelID:%s\", shorttxid(msg.Txid), msg.Txid, msg.ChannelId)\n\ttctx.ResponseNotifier <- msg\n\ttctx.CloseQueryIterators()\n}\n\n// is this a txid for which there is a valid txsim\nfunc (h *Handler) isValidTxSim(channelID string, txid string, fmtStr string, args ...any) (*TransactionContext, error) {\n\ttxContext := h.TXContexts.Get(channelID, txid)\n\tif txContext == nil || txContext.TXSimulator == nil {\n\t\terr := errors.Errorf(fmtStr, args...)\n\t\tchaincodeLogger.Errorf(\"no ledger context: %s %s\\n\\n %+v\", channelID, txid, err)\n\t\treturn nil, err\n\t}\n\treturn txContext, nil\n}\n\n// register Txid to prevent overlapping handle messages from chaincode\nfunc (h *Handler) registerTxid(msg *pb.ChaincodeMessage) bool {\n\t// Check if this is the unique state request from this chaincode txid\n\tif h.ActiveTransactions.Add(msg.ChannelId, msg.Txid) {\n\t\treturn true\n\t}\n\n\t// Log the issue and drop the request\n\tchaincodeLogger.Errorf(\"[%s] Another request pending for this CC: %s, Txid: %s, ChannelID: %s. Cannot process.\", shorttxid(msg.Txid), h.chaincodeID, msg.Txid, msg.ChannelId)\n\treturn false\n}\n","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/core/chaincode/handler.go#L571-L607","documentation":"isValidTxSim looks up the TransactionContext for a (channelID, txid) pair and requires that a TXSimulator is attached. The error means the chaincode issued a ledger read/write message (GetState, PutState, etc.) for a txid with no registered transaction simulator — i.e., the message arrived outside a valid transaction context, or after the context was deleted (e.g., post-commit or in Init without simulator). The formatted message includes channel, txid, and underlying error details.","triggerScenarios":"Chaincode calls GetState/PutState/DelState after the transaction has completed or been cleaned up; calling state APIs from a goroutine that outlives the transaction; passing a wrong txid through HandleTransaction/getTxContextForInvoke; calling query APIs during Init when no simulator exists.","commonSituations":"Long-running chaincode goroutines or timers that touch the ledger after the tx ends; retrying a request with an expired txid; invoking state APIs inside Init; channel/txid mismatches in custom peer-to-chaincode plumbing.","solutions":["Ensure all ledger reads/writes happen inside the single chaincode Invoke/Init call, before it returns","Do not spawn goroutines that use the tx simulator after the transaction completes; re-invoke instead","Verify the txid/channel passed to HandleTransaction matches an active (not yet deleted) transaction context","Do not call GetState/PutState from Init if a simulator is unavailable; defer state writes to Invoke"],"exampleFix":"// before: goroutine uses simulator after tx ends\ngo func() { stub.PutState(\"k\", v) }()\n// after: keep ledger ops synchronous inside Invoke\nfunc (c *CC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {\n    if err := stub.PutState(\"k\", []byte(\"v\")); err != nil { return shim.Error(err.Error()) }\n    return shim.Success(nil)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"val, err := stub.GetState(key)\nif err != nil {\n    if strings.Contains(err.Error(), \"no ledger context\") {\n        // tx context gone: retry via a NEW transaction invocation, not with same txid\n        return shim.Error(\"transaction context expired; re-invoke\")\n    }\n    return shim.Error(err.Error())\n}","preventionTips":["Never use the tx simulator from goroutines that may outlive the Invoke/Init call","Keep all ledger access synchronous inside the handler returning the response","Don't call state APIs during Init unless a simulator is guaranteed","In long-running chaincode work, return quickly and re-invoke for the rest"],"tags":["hyperledger-fabric","chaincode","ledger","transaction-context"],"backgroundTag":"no-ledger-context","analyzedSha":"2736b63f8fd5932511d56fe68b7039d15977f7f6","analyzedAt":"2026-09-04T08:52:36.465Z","contentChangedAt":"2026-09-04T08:52:36.465Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}