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

  1. Marshal the fully populated Proposal message with proto.Marshal and assign it to SignedProposal.ProposalBytes before signing.
  2. Sign exactly the ProposalBytes slice and store both in the SignedProposal sent to ProcessProposal.
  3. 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

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


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