{"record":{"id":"2520f7bc6dc1fe7d","repo":"hyperledger/fabric","slug":"no-endorsements","errorCode":null,"errorMessage":"no endorsements","messagePattern":"no endorsements","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"protoutil/txutils.go","lineNumber":212,"sourceCode":"\t}\n\n\t// fill endorsements according to their uniqueness\n\tendorsersUsed := make(map[string]struct{})\n\tvar endorsements []*peer.Endorsement\n\tfor _, r := range resps {\n\t\tif r.Endorsement == nil {\n\t\t\tcontinue\n\t\t}\n\t\tkey := string(r.Endorsement.Endorser)\n\t\tif _, used := endorsersUsed[key]; used {\n\t\t\tcontinue\n\t\t}\n\t\tendorsements = append(endorsements, r.Endorsement)\n\t\tendorsersUsed[key] = struct{}{}\n\t}\n\n\tif len(endorsements) == 0 {\n\t\treturn nil, errors.Errorf(\"no endorsements\")\n\t}\n\n\t// create ChaincodeEndorsedAction\n\tcea := &peer.ChaincodeEndorsedAction{ProposalResponsePayload: resps[0].Payload, Endorsements: endorsements}\n\n\t// obtain the bytes of the proposal payload that will go to the transaction\n\tpropPayloadBytes, err := GetBytesProposalPayloadForTx(pPayl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// serialize the chaincode action payload\n\tcap := &peer.ChaincodeActionPayload{ChaincodeProposalPayload: propPayloadBytes, Action: cea}\n\tcapBytes, err := GetBytesChaincodeActionPayload(cap)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/protoutil/txutils.go#L194-L230","documentation":"After deduplicating endorsers, CreateSignedTx requires at least one endorsement to place in the ChaincodeEndorsedAction. Since responses were already required non-empty, this fires in the degenerate case where the collected endorsement set ends up empty.","triggerScenarios":"Calling CreateSignedTx with responses whose Endorsement fields are all nil/empty so that no endorsements accumulate, or an effectively empty deduplicated endorsement set.","commonSituations":"Manually constructed or partially deserialized ProposalResponse objects missing Endorsement; responses built in tests without running real endorsement; upstream code stripping endorsements.","solutions":["Ensure each ProposalResponse passed in carries a valid Endorsement (signed by an endorser)","Use responses returned directly by the endorser service instead of hand-built ones","In tests, generate responses via a helper that populates Endorsement","If no endorsements can be obtained, fail before attempting to build the tx"],"exampleFix":"// before\nresp := &peer.ProposalResponse{Response: &peer.Response{Status: 200}} // no Endorsement\nenv, err := protoutil.CreateSignedTx(proposal, signer, resp)\n// after\nresp, err := endorser.ProcessProposal(ctx, signedProp)\nif err != nil || resp == nil || resp.Endorsement == nil {\n    return nil, fmt.Errorf(\"no endorsement: %w\", err)\n}\nenv, err := protoutil.CreateSignedTx(proposal, signer, resp)","handlingStrategy":"validation","validationCode":"func hasEndorsement(r *peer.ProposalResponse) error {\n    if r == nil || r.Endorsement == nil {\n        return fmt.Errorf(\"response missing endorsement\")\n    }\n    return nil\n}","typeGuard":"func isEndorsed(r *peer.ProposalResponse) bool {\n    return r != nil && r.Endorsement != nil\n}","tryCatchPattern":"for _, r := range resps {\n    if err := hasEndorsement(r); err != nil {\n        return nil, err\n    }\n}\ntx, err := protoutil.CreateSignedTx(proposal, signer, resps...)","preventionTips":["Use responses returned by the endorser service, not hand-built structs","Verify Endorsement is populated when constructing test fixtures","Drop responses with nil endorsements before assembly and report why"],"tags":["endorsement","transaction","hyperledger-fabric","missing-data"],"backgroundTag":"missing-endorsement-response","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"}