hyperledger/fabric · warning

form does not contains part key: %s

Error message

form does not contains part key: %s

What it means

After successfully reading the multipart form, the handler requires a file part under the reserved key FormDataConfigBlockKey ("config-block"). If no such file part exists, the join request lacks the required config block and the handler returns 400 with this formatted error naming the expected key.

Source

Thrown at orderer/common/channelparticipation/restapi.go:437

	h.logger.Debugf("Successfully update config channel: %s", info.URL)
	h.sendResponseCreated(resp, info.URL, info)
}

// Expect a multipart/form-data with a single part, of type file, with key FormDataConfigBlockKey.
func (h *HTTPHandler) multipartFormDataBodyToBlock(params map[string]string, req *http.Request, resp http.ResponseWriter) *cb.Block {
	boundary := params["boundary"]
	reader := multipart.NewReader(
		http.MaxBytesReader(resp, req.Body, int64(h.config.MaxRequestBodySize)),
		boundary,
	)
	form, err := reader.ReadForm(2 * int64(h.config.MaxRequestBodySize))
	if err != nil {
		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrap(err, "cannot read form from request body"))
		return nil
	}

	if _, exist := form.File[FormDataConfigBlockKey]; !exist {
		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Errorf("form does not contains part key: %s", FormDataConfigBlockKey))
		return nil
	}

	if len(form.File) != 1 || len(form.Value) != 0 {
		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.New("form contains too many parts"))
		return nil
	}

	fileHeader := form.File[FormDataConfigBlockKey][0]
	file, err := fileHeader.Open()
	if err != nil {
		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrapf(err, "cannot open file part %s from request body", FormDataConfigBlockKey))
		return nil
	}

	blockBytes, err := io.ReadAll(file)
	if err != nil {
		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrapf(err, "cannot read file part %s from request body", FormDataConfigBlockKey))

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Attach the config block as a FILE part named exactly config-block: curl -F 'config-block=@path/to/genesis.block'
  2. Ensure it is uploaded as a file part (binary), not a form value field
  3. Check the client library's form field name matches FormDataConfigBlockKey ("config-block")
  4. Read the %s in the server error message to confirm the expected key name

Example fix

// before
curl -X POST -F 'block=@genesis.block' http://orderer/participation/v2/channels?channelID=mychannel
// after
curl -X POST -F 'config-block=@genesis.block' http://orderer/participation/v2/channels?channelID=mychannel
Defensive patterns

Strategy: validation

Validate before calling

const FormDataConfigBlockKey = "config-block"
// verify before sending:
if _, err := os.Stat(configBlockPath); err != nil {
	return fmt.Errorf("must attach a file part named %q", FormDataConfigBlockKey)
}

Try / catch

if strings.Contains(err.Error(), "form does not contains part key") {
	return fmt.Errorf("attach the block as a file part named exactly %q (curl -F 'config-block=@file')", "config-block")
}

Prevention

When it happens

Trigger: serveJoin posting a multipart form without a file part named config-block — e.g. attaching the block under a different field name, sending it as a plain value field, or omitting the file entirely.

Common situations: Scripts using -F 'block=@...' instead of -F 'config-block=@...'; clients uploading the block as a text field rather than a file part; typos in the form key; sending only JSON metadata with no attachment.

Related errors


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