hyperledger/fabric · error
empty proposal bytes
Error message
empty proposal bytes
What it means
The SignedProposal's ProposalBytes field is nil, so there is no proposal payload to verify the signature against or to unpack. The endorser requires both ProposalBytes and Signature to be present in a SignedProposal.
Source
Thrown at core/endorser/msgvalidation.go:162
}
// ensure that there is a nonce
if len(up.SignatureHeader.Nonce) == 0 {
return errors.Errorf("nonce is empty")
}
// ensure that there is a creator
if len(up.SignatureHeader.Creator) == 0 {
return errors.New("creator is empty")
}
expectedTxID := protoutil.ComputeTxID(up.SignatureHeader.Nonce, up.SignatureHeader.Creator)
if up.TxID() != expectedTxID {
return errors.Errorf("incorrectly computed txid '%s' -- expected '%s'", up.TxID(), expectedTxID)
}
if up.SignedProposal.ProposalBytes == nil {
return errors.Errorf("empty proposal bytes")
}
if up.SignedProposal.Signature == nil {
return errors.Errorf("empty signature bytes")
}
// get the identity of the creator
creator, err := idDeserializer.DeserializeIdentity(up.SignatureHeader.Creator)
if err != nil {
logger.Warnw("access denied", "error", err, "identity", protoutil.LogMessageForSerializedIdentity(up.SignatureHeader.Creator))
return errors.Errorf("access denied: channel [%s] creator org unknown, creator is malformed", up.ChannelID())
}
genericAuthError := errors.Errorf("access denied: channel [%s] creator org [%s]", up.ChannelID(), creator.GetMSPIdentifier())
// ensure that creator is a valid certificate
err = creator.Validate()
if err != nil {
logger.Warnw("access denied: identity is not valid", "error", err, "identity", protoutil.LogMessageForSerializedIdentity(up.SignatureHeader.Creator))View on GitHub (pinned to 2736b63f8f)
Solutions
- Marshal the fully populated Proposal message with proto.Marshal and assign it to SignedProposal.ProposalBytes before signing.
- Sign exactly the ProposalBytes slice and store both in the SignedProposal sent to ProcessProposal.
- Assert both ProposalBytes and Signature are non-nil/non-empty before sending the gRPC request.
Example fix
// before
signed := &peer.SignedProposal{Signature: sig}
// after
proposalBytes, _ := proto.Marshal(proposal)
signed := &peer.SignedProposal{ProposalBytes: proposalBytes, Signature: sig} Defensive patterns
Strategy: validation
Validate before calling
if sp.ProposalBytes == nil || len(sp.ProposalBytes) == 0 {
return errors.New("ProposalBytes must be the marshaled Proposal before sending")
} Type guard
func hasProposalBytes(sp *peer.SignedProposal) bool {
return sp != nil && len(sp.ProposalBytes) > 0
} Prevention
- Marshal the Proposal and assign ProposalBytes before signing
- Sanity-check the SignedProposal fields before the gRPC call
- Sign exactly the bytes you transmit
When it happens
Trigger: ProcessProposal -> preProcess -> Validate on a SignedProposal struct where ProposalBytes was never set — e.g. constructing SignedProposal{Signature: sig} without proposal marshaling, or a gRPC transmission that dropped the field.
Common situations: Manually assembled SignedProposal protobufs, clients that sign but forget to attach the marshaled Proposal message, serialization bugs where nil marshal produces empty bytes.
Related errors
- chaincode invocation spec did not contain chaincode spec
- chaincode input did not contain any input
- ChaincodeHeaderExtension.ChaincodeId is nil
- ChaincodeHeaderExtension.ChaincodeId.Name is empty
- invalid header type %s
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/f2885b0ff4fc2853.
Report an issue: GitHub.