hyperledger/fabric · error

First message needs to be a register

Error message

First message needs to be a register

What it means

The peer's chaincode access control Authenticator requires that the very first chaincode message received on a newly established stream is a ChaincodeMessage_REGISTER. Any other message type on a fresh stream is rejected with this error, because registration establishes the chaincode's identity (name and certificate) for the connection.

Source

Thrown at core/chaincode/accesscontrol/access.go:63

// Generate returns a pair of certificate and private key,
// and associates the hash of the certificate with the given
// chaincode name
func (ac *Authenticator) Generate(ccName string) (*CertAndPrivKeyPair, error) {
	cert, err := ac.mapper.genCert(ccName)
	if err != nil {
		return nil, err
	}
	return &CertAndPrivKeyPair{
		Key:  cert.Key,
		Cert: cert.Cert,
	}, nil
}

func (ac *Authenticator) authenticate(msg *pb.ChaincodeMessage, stream grpc.ServerStream) error {
	if msg.Type != pb.ChaincodeMessage_REGISTER {
		logger.Warning("Got message", msg, "but expected a ChaincodeMessage_REGISTER message")
		return errors.New("First message needs to be a register")
	}

	chaincodeID := &pb.ChaincodeID{}
	err := proto.Unmarshal(msg.Payload, chaincodeID)
	if err != nil {
		logger.Warning("Failed unmarshalling message:", err)
		return err
	}
	ccName := chaincodeID.Name
	// Obtain certificate from stream
	hash := extractCertificateHashFromContext(stream.Context())
	if len(hash) == 0 {
		errMsg := fmt.Sprintf("TLS is active but chaincode %s didn't send certificate", ccName)
		logger.Warning(errMsg)
		return errors.New(errMsg)
	}
	// Look it up in the mapper
	registeredName := ac.mapper.lookup(certHash(hash))

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Ensure the chaincode shim sends ChaincodeMessage_REGISTER as the first message after stream establishment.
  2. Align the fabric chaincode shim version with the peer's Fabric version (rebuild the chaincode image with a compatible shim).
  3. Check for reconnect logic that reuses a stream without re-registering — recreate the stream and send REGISTER first.
  4. If testing manually, send a REGISTER message with a valid ChaincodeID payload before anything else.

Example fix

// before: sending a transaction before registration
stream.Send(&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION, ...})
// after: register first
payload, _ := proto.Marshal(&pb.ChaincodeID{Name: ccName})
stream.Send(&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_REGISTER, Payload: payload})
Defensive patterns

Strategy: validation

Validate before calling

// shim-side: assert first outbound message is REGISTER
if firstMsg.Type != pb.ChaincodeMessage_REGISTER {
    return errors.New("client bug: first chaincode message must be REGISTER")
}

Type guard

func isFirstMessageRegister(msg *pb.ChaincodeMessage) bool {
    return msg != nil && msg.Type == pb.ChaincodeMessage_REGISTER
}

Try / catch

if err := authenticator.authenticate(msg, stream); err != nil {
    if strings.Contains(err.Error(), "First message needs to be a register") {
        // recreate the stream and send REGISTER first
    }
}

Prevention

When it happens

Trigger: A chaincode process (or any client) connecting to the peer's chaincode support port and sending a message whose Type is not REGISTER (e.g. TRANSACTION, READY, PUT_STATE) before registering.

Common situations: Custom or old chaincode shims speaking an incompatible protocol version; a networking proxy/retry layer replaying a stream mid-conversation so the peer sees a non-first message first; manually testing the ccstream port with a raw gRPC client; mismatched fabric-chaincode shim and peer versions.

Related errors


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