hyperledger/fabric · warning
bad normal message: %s
Error message
bad normal message: %s
What it means
Returned by BFTChain.Order when the normal (transaction) message fails re-validation against the current config. If configSeq has fallen behind the chain's actual config sequence, ProcessNormalMsg is re-run and its error is surfaced as 'bad normal message'. The message is rejected and never enters consensus.
Source
Thrown at orderer/consensus/smartbft/chain.go:337
}
c.Logger.Debugf("Consensus.SubmitRequest, node id %d", c.Config.SelfID)
if err = c.consensus.SubmitRequest(reqBytes); err != nil {
return errors.Wrapf(err, "failed to submit request")
}
return nil
}
// Order accepts a message which has been processed at a given configSeq.
// If the configSeq advances, it is the responsibility of the consenter
// to revalidate and potentially discard the message
// The consenter may return an error, indicating the message was not accepted
func (c *BFTChain) Order(env *cb.Envelope, configSeq uint64) error {
seq := c.support.Sequence()
if configSeq < seq {
c.Logger.Warnf("Normal message was validated against %d, although current config seq has advanced (%d)", configSeq, seq)
if _, err := c.support.ProcessNormalMsg(env); err != nil {
return errors.Errorf("bad normal message: %s", err)
}
}
return c.submit(env)
}
// Configure accepts a message which reconfigures the channel and will
// trigger an update to the configSeq if committed. The configuration must have
// been triggered by a ConfigUpdate message. If the config sequence advances,
// it is the responsibility of the consenter to recompute the resulting config,
// discarding the message if the reconfiguration is no longer valid.
// The consenter may return an error, indicating the message was not accepted
func (c *BFTChain) Configure(config *cb.Envelope, configSeq uint64) error {
if err := c.verifier.ConfigValidator.ValidateConfig(config); err != nil {
return err
}
seq := c.support.Sequence()
if configSeq < seq {View on GitHub (pinned to 2736b63f8f)
Solutions
- Have the client fetch the latest config seq (re-deliver/reconnect) and resubmit with an up-to-date configSeq
- Fix the client so it does not send config transactions via the normal Order path (use Configure)
- Read the inner %s text from ProcessNormalMsg (e.g. invalid tx, mvcc conflict) and correct the transaction accordingly
- Retry with a fresh envelope after re-validating against current channel config
Example fix
// before
chain.Order(staleEnv, oldConfigSeq) // -> bad normal message: ...
// after
if err := chain.Order(env, currentSeq); err != nil && strings.Contains(err.Error(), "bad normal message") {
env = rebuildEnvelope(tx) // re-create tx against current channel config
if err := chain.Order(env, support.Sequence()); err != nil { return err }
} Defensive patterns
Strategy: validation
Validate before calling
seq := chain.Sequence() // via support/metadata
if clientConfigSeq < seq {
// re-fetch/re-validate the tx against current config before submitting
env = revalidateNormalMsg(env)
clientConfigSeq = seq
}
return chain.Order(env, clientConfigSeq) Try / catch
if err := chain.Order(env, configSeq); err != nil {
if strings.Contains(err.Error(), "bad normal message") {
// refresh config seq and resubmit a freshly validated envelope
return retryWithFreshConfig(env)
}
return err
} Prevention
- Track the channel's current config sequence client-side and refresh it on config-update events
- Never send config transactions through the normal Order path
- Handle 'bad normal message' by rebuilding the transaction against current config, not blind-retrying the same bytes
When it happens
Trigger: SendTx calls Order(env, configSeq) with a configSeq older than c.support.Sequence() (config has advanced since last delivery), AND the re-processed envelope fails ProcessNormalMsg — e.g. it is malformed, is actually a config tx, or fails mvcc/version verification.
Common situations: A config update committed while a client's transaction was in flight, invalidating the seq the tx was validated against; clients resubmitting stale transactions after a channel reconfiguration; sending config-update envelopes through the normal tx path.
Related errors
- %s is mandatory and cannot be empty
- enrollment certificate isn't a valid PEM block
- failed to decode PEM block from %s
- '%s' not equal <newest|oldest|config|(number)>
- unmarshalling block: %s
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/603b7675aad79d30.
Report an issue: GitHub.