hyperledger/fabric · error

proposal cannot be nil

Error message

proposal cannot be nil

What it means

signProposal in internal/peer/lifecycle/chaincode/common.go is a shared helper that serializes and signs a proposal before it is sent to the endorser. It refuses to run when the *pb.Proposal argument is nil, returning this error immediately before any marshaling or signing occurs. This indicates an internal construction bug: the caller built no proposal at all.

Source

Thrown at internal/peer/lifecycle/chaincode/common.go:50

	Deliver(ctx context.Context, opts ...grpc.CallOption) (pb.Deliver_DeliverClient, error)
	DeliverFiltered(ctx context.Context, opts ...grpc.CallOption) (pb.Deliver_DeliverClient, error)
}

// Signer defines the interface needed for signing messages
type Signer interface {
	Sign(msg []byte) ([]byte, error)
	Serialize() ([]byte, error)
}

// Writer defines the interface needed for writing a file
type Writer interface {
	WriteFile(string, string, []byte) error
}

func signProposal(proposal *pb.Proposal, signer Signer) (*pb.SignedProposal, error) {
	// check for nil argument
	if proposal == nil {
		return nil, errors.New("proposal cannot be nil")
	}

	if signer == nil {
		return nil, errors.New("signer cannot be nil")
	}

	proposalBytes, err := proto.Marshal(proposal)
	if err != nil {
		return nil, errors.Wrap(err, "error marshaling proposal")
	}

	signature, err := signer.Sign(proposalBytes)
	if err != nil {
		return nil, err
	}

	return &pb.SignedProposal{
		ProposalBytes: proposalBytes,

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Check upstream proposal construction (createInput/createCommand) for an ignored error that left the proposal nil
  2. If calling signProposal directly (tests/tools), ensure the pb.Proposal is non-nil before invoking
  3. Update to a stock fabric release where all callers validate the proposal before signing

Example fix

// before
sp, err := signProposal(nil, signer)
// after
if proposal == nil {
    return errors.New("no proposal to sign")
}
sp, err := signProposal(proposal, signer)
Defensive patterns

Strategy: validation

Validate before calling

if proposal == nil {
    return errors.New("proposal must be constructed before signing")
}
sp, err := signProposal(proposal, signer)

Type guard

func hasProposal(p *pb.Proposal) bool { return p != nil }

Try / catch

if err := run(); err != nil {
    if strings.Contains(err.Error(), "proposal cannot be nil") {
        // fix proposal construction upstream
    }
}

Prevention

When it happens

Trigger: Any of the lifecycle CLI commands (Approve, ReadinessCheck, Commit, Get, Install, Query) calls signProposal with a nil proposal, which happens only when the command's createInput/proposal-construction step failed to produce a proposal yet execution continued.

Common situations: Practically only seen from modified/forked code or test harnesses that invoke signProposal directly with nil; stock CLI commands always build a proposal first.

Related errors


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