hyperledger/fabric · error

proto: Marshal called with nil

Error message

proto: Marshal called with nil

What it means

createProposalFromCDS (used by CreateInstallProposalFromCDS, CreateDeployProposalFromCDS, CreateUpgradeProposalFromCDS) pre-checks msg with ProtoReflect().IsValid() and returns 'proto: Marshal called with nil' before invoking proto.Marshal. It guards against nil or invalid protobuf messages being embedded as chaincode input bytes in the proposal.

Source

Thrown at protoutil/proputils.go:348

	vscc []byte,
	collectionConfig []byte,
) (*peer.Proposal, string, error) {
	if collectionConfig == nil {
		return createProposalFromCDS(channelID, cds, creator, "upgrade", policy, escc, vscc)
	}
	return createProposalFromCDS(channelID, cds, creator, "upgrade", policy, escc, vscc, collectionConfig)
}

// createProposalFromCDS returns a deploy or upgrade proposal given a
// serialized identity and a ChaincodeDeploymentSpec
func createProposalFromCDS(channelID string, msg proto.Message, creator []byte, propType string, args ...[]byte) (*peer.Proposal, string, error) {
	// in the new mode, cds will be nil, "deploy" and "upgrade" are instantiates.
	var ccinp *peer.ChaincodeInput
	var b []byte
	var err error
	if msg != nil {
		if !msg.ProtoReflect().IsValid() {
			return nil, "", errors.New("proto: Marshal called with nil")
		}
		b, err = proto.Marshal(msg)
		if err != nil {
			return nil, "", err
		}
	}
	switch propType {
	case "deploy":
		fallthrough
	case "upgrade":
		cds, ok := msg.(*peer.ChaincodeDeploymentSpec)
		if !ok || cds == nil {
			return nil, "", errors.New("invalid message for creating lifecycle chaincode proposal")
		}
		Args := [][]byte{[]byte(propType), []byte(channelID), b}
		Args = append(Args, args...)

		ccinp = &peer.ChaincodeInput{Args: Args}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Ensure the ChaincodeInput (cds.Input) is non-nil and populated with Args before calling Create*ProposalFromCDS
  2. Use proto.Clone/creation helpers that return valid messages; avoid passing typed nil pointers
  3. Validate with msg.ProtoReflect().IsValid() (or msg != nil for legacy API) at the call site before building the proposal

Example fix

// before
inv, err := protoutil.CreateInstallProposalFromCDS(nil cds, creator) // cds.Input nil
// after
if cds.Input == nil {
	cds.Input = &peer.ChaincodeInput{Args: [][]byte{[]byte("install")}}
}
inv, err := protoutil.CreateInstallProposalFromCDS(cds, creator)
Defensive patterns

Strategy: type-guard

Validate before calling

func validCDS(cds *peer.ChaincodeDeploymentSpec) error {
	if cds == nil || cds.ChaincodeSpec == nil || cds.ChaincodeSpec.ChaincodeId == nil {
		return errors.New("CDS incomplete")
	}
	if cds.Input == nil {
		return errors.New("CDS Input (ChaincodeInput) is nil")
	}
	return nil
}

Type guard

func isMarshalable(m proto.Message) bool {
	return m != nil && m.ProtoReflect().IsValid()
}

Try / catch

prop, _, err := protoutil.CreateInstallProposalFromCDS(cds, creator)
if err != nil {
	if strings.Contains(err.Error(), "Marshal called with nil") {
		return nil, fmt.Errorf("chaincode input message not initialized: %w", err)
	}
	return nil, err
}

Prevention

When it happens

Trigger: Passing a nil msg, or a nil-pointer protobuf message (invalid ProtoReflect state), as the ChaincodeInput/deployment message when building install/deploy/upgrade proposals — e.g. cds.Input never set or a zero-valued pointer typed field.

Common situations: CLI/SDK chaincode install and upgrade flows after a protobuf API migration (github.com/golang/protobuf to google.golang.org/protobuf), where constructing ChaincodeInput changed and left the message nil.

Related errors


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