hyperledger/fabric · error
proposal payload cannot be nil
Error message
proposal payload cannot be nil
What it means
ProposalToBlock requires the proposal's Payload field, which holds the serialized (payload, metadata) tuple of the block. An empty Payload means the block's data cannot be reconstructed, so conversion fails with this error after the header checks pass.
Source
Thrown at orderer/consensus/smartbft/signature.go:73
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")
}
tuple := &ByteBufferTuple{}
if err := tuple.FromBytes(proposal.Payload); err != nil {
return nil, errors.Wrap(err, "bad payload and metadata tuple")
}
if err := proto.Unmarshal(tuple.A, block.Data); err != nil {
return nil, errors.Wrap(err, "bad payload")
}
if err := proto.Unmarshal(tuple.B, block.Metadata); err != nil {
return nil, errors.Wrap(err, "bad metadata")
}
return block, nil
}
type asn1Header struct {View on GitHub (pinned to 2736b63f8f)
Solutions
- Ensure the code constructing types.Proposal populates both Header and Payload (Payload from the proposal's marshalled block data+metadata tuple).
- Re-fetch the proposal by triggering node synchronization from other consenters.
- Check disk health and ledger/WAL integrity; restore from backup if records are truncated.
- Compare proposal bytes across consenters (signature verification logs) to locate the node with the incomplete proposal.
Example fix
// before
prop := types.Proposal{Header: headerBytes}
block, _ := ProposalToBlock(prop)
// after
prop := types.Proposal{Header: headerBytes, Payload: payloadBytes}
block, err := ProposalToBlock(prop) Defensive patterns
Strategy: validation
Validate before calling
if len(prop.Payload) == 0 {
return fmt.Errorf("proposal has no payload; cannot convert to block")
}
block, err := ProposalToBlock(prop) Type guard
func hasProposalPayload(p *types.Proposal) bool {
return p != nil && len(p.Payload) > 0
} Try / catch
block, err := ProposalToBlock(prop)
if err != nil && err.Error() == "proposal payload cannot be nil" {
// refetch proposal or trigger synchronization
return resync()
} Prevention
- Always populate both Header and Payload when constructing proposals.
- Check storage truncation after unclean shutdowns before processing records.
- Persist proposal Header and Payload atomically.
- Log proposal sizes at creation to detect truncation early.
When it happens
Trigger: VerifyProposal/SignProposal/RequestsFromProposal/Deliver invoked with a types.Proposal that has a valid Header but an empty or nil Payload byte slice — e.g. a proposal constructed only from a header or truncated during persistence.
Common situations: Corrupted proposal entries after unclean shutdown; integration code assembling Proposal structs manually and forgetting to set Payload; truncated records read from storage after disk issues.
Related errors
- proposal header 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/561bc0bbb54a6367.
Report an issue: GitHub.