hyperledger/fabric · error
proposal header cannot be nil
Error message
proposal header cannot be nil
What it means
ProposalToBlock converts a SmartBFT proposal into a common.Block; the proposal's Header field carries the ASN.1-encoded block header. An empty Header means the proposal is malformed and cannot be converted, so the conversion aborts immediately before any unmarshalling.
Source
Thrown at orderer/consensus/smartbft/signature.go:57
return bytes
}
// AsBytes returns the message to sign
func (sig *Signature) AsBytes() []byte {
msg2Sign := util.ConcatenateBytes(sig.OrdererBlockMetadata, sig.IdentifierHeader, sig.BlockHeader)
return msg2Sign
}
// ProposalToBlock marshals the proposal the block
func ProposalToBlock(proposal types.Proposal) (*cb.Block, error) {
// initialize block with empty fields
block := &cb.Block{
Data: &cb.BlockData{},
Metadata: &cb.BlockMetadata{},
}
if len(proposal.Header) == 0 {
return nil, errors.New("proposal header cannot be nil")
}
hdr := &asn1Header{}
if _, err := asn1.Unmarshal(proposal.Header, hdr); err != nil {
return nil, errors.Wrap(err, "bad header")
}
block.Header = &cb.BlockHeader{
Number: hdr.Number.Uint64(),
PreviousHash: hdr.PreviousHash,
DataHash: hdr.DataHash,
}
if len(proposal.Payload) == 0 {
return nil, errors.New("proposal payload cannot be nil")
}
View on GitHub (pinned to 2736b63f8f)
Solutions
- Confirm the channel is configured with the SmartBFT consensus type and the proposal originates from SmartBFT (check consensus_type in channel config).
- Re-pull/re-sync the proposal from correct peers (trigger synchronization) if the local data is corrupted.
- Verify no code path passes a zero-value types.Proposal{} into the chain; initialize it from ViewChainer/Consensus state properly.
- If upgrading from RAFT, ensure the chain was created with SmartBFT genesis settings rather than reusing RAFT artifacts.
Example fix
// before
block, err := signature.ProposalToBlock(types.Proposal{})
// after
if len(prop.Header) == 0 {
return errors.New("refusing to convert empty proposal")
}
block, err := signature.ProposalToBlock(prop) Defensive patterns
Strategy: validation
Validate before calling
if prop == nil || len(prop.Header) == 0 {
return fmt.Errorf("proposal has no header; cannot verify/convert")
}
block, err := ProposalToBlock(*prop) Type guard
func hasProposalHeader(p *types.Proposal) bool {
return p != nil && len(p.Header) > 0
} Try / catch
block, err := ProposalToBlock(prop)
if err != nil {
if err.Error() == "proposal header cannot be nil" {
// proposal source is invalid; re-fetch or resync
}
return err
} Prevention
- Never construct types.Proposal from zero values; always populate from consensus state.
- Only feed SmartBFT-produced proposals into SmartBFT verification paths.
- Validate WAL/backup integrity before replaying proposals.
- Add pre-checks for Header/Payload length in custom consensus integrations.
When it happens
Trigger: Calling VerifyProposal, SignProposal, RequestsFromProposal, or Deliver with a types.Proposal whose Header byte slice is empty/nil — typically a proposal constructed by a different consensus implementation or corrupted during storage/transit.
Common situations: Mixing proposal data between RAFT (etcdraft) and SmartBFT channels; reading a corrupted proposal from the WAL/local ledger; a bug in custom consensus integration passing an uninitialized Proposal struct.
Related errors
- proposal payload cannot be nil
- consenter options type mismatch
- failed getting proposal context. Signed proposal is nil
- Invalid signed proposal during check policy on channel [%s]
- Failing extracting proposal during check policy on channel [
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/6bcb9339fe20cf73.
Report an issue: GitHub.