{"record":{"id":"aa72c599cc7001fc","repo":"hyperledger/fabric","slug":"failed-getting-proposal-context-signed-proposal-i","errorCode":null,"errorMessage":"failed getting proposal context. Signed proposal is nil","messagePattern":"failed getting proposal context\\. Signed proposal is nil","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/chaincode/handler.go","lineNumber":1460,"sourceCode":"\n\tvar ccresp *pb.ChaincodeMessage\n\tselect {\n\tcase ccresp = <-txctx.ResponseNotifier:\n\t\t// response is sent to user or calling chaincode. ChaincodeMessage_ERROR\n\t\t// are typically treated as error\n\tcase <-time.After(timeout):\n\t\terr = errors.New(ErrorExecutionTimeout)\n\t\th.Metrics.ExecuteTimeouts.With(\"chaincode\", h.chaincodeID).Add(1)\n\tcase <-h.streamDone():\n\t\terr = errors.New(ErrorStreamTerminated)\n\t}\n\n\treturn ccresp, err\n}\n\nfunc (h *Handler) setChaincodeProposal(signedProp *pb.SignedProposal, prop *pb.Proposal, msg *pb.ChaincodeMessage) error {\n\tif prop != nil && signedProp == nil {\n\t\treturn errors.New(\"failed getting proposal context. Signed proposal is nil\")\n\t}\n\t// TODO: This doesn't make a lot of sense. Feels like both are required or\n\t// neither should be set. Check with a knowledgeable expert.\n\tif prop != nil {\n\t\tmsg.Proposal = signedProp\n\t}\n\treturn nil\n}\n\nfunc (h *Handler) getCollectionStore(channelID string) privdata.CollectionStore {\n\treturn privdata.NewSimpleCollectionStore(\n\t\th.LedgerGetter.GetLedger(channelID),\n\t\th.DeployedCCInfoProvider,\n\t\th.IDDeserializerFactory,\n\t)\n}\n\nfunc (h *Handler) State() State {","sourceCodeStart":1442,"sourceCodeEnd":1478,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/core/chaincode/handler.go#L1442-L1478","documentation":"Returned by Handler.setChaincodeProposal when a proposal is present (prop != nil) but its signed counterpart is nil. The peer cannot attach proposal context to the chaincode message without the SignedProposal, so the message cannot be validated as coming from a real transaction proposal.","triggerScenarios":"setChaincodeProposal is called with a non-nil prop and nil signedProp while building a ChaincodeMessage — an inconsistent proposal pair passed into the handler's execute path.","commonSituations":"Custom code or tests calling handler execute with only a Proposal (no SignedProposal); mismatched proposal extraction from a client submission; fabric-sdk or caller constructing proposals manually and dropping the signature envelope.","solutions":["Always pass both the SignedProposal and its Proposal together from the caller.","If the call is intentionally proposal-less (system chaincode path), pass nil for BOTH prop and signedProp.","Fix proposal parsing on the caller side so signedProp is extracted alongside prop.","In tests, use the standard helpers to build both proposal and signed proposal."],"exampleFix":"// before\nh.setChaincodeProposal(nil, prop, msg)\n// after\nh.setChaincodeProposal(signedProp, prop, msg) // pass the matching signed proposal","handlingStrategy":"validation","validationCode":"// caller must supply proposal pairs together\nif (prop == nil) != (signedProp == nil) {\n    return errors.New(\"proposal and signed proposal must both be set or both nil\")\n}","typeGuard":"func proposalPairValid(signedProp *pb.SignedProposal, prop *pb.Proposal) bool {\n    return (prop == nil) == (signedProp == nil)\n}","tryCatchPattern":null,"preventionTips":["Always construct Proposals via SDK helpers that yield both prop and signedProp.","Never hand-build proposals for production transactions.","Audit custom/test call sites of handler execute for proposal consistency."],"tags":["hyperledger-fabric","chaincode","proposal","signed-proposal"],"backgroundTag":"missing-signed-proposal","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"}