hyperledger/fabric · error

data hash is %s but expected %s

Error message

data hash is %s but expected %s

What it means

verifyHashChainAndDataHash recomputes the block data hash via protoutil.BlockDataHash and compares it to block.Header.DataHash. If the header's declared data hash does not match the actual data, the block content was altered or built incorrectly.

Source

Thrown at orderer/consensus/smartbft/verifier.go:259

func (v *Verifier) VerificationSequence() uint64 {
	return v.VerificationSequencer.Sequence()
}

func verifyHashChainAndDataHash(block *cb.Block, prevHeaderHash string) error {
	thisHdrHashOfPrevHdr := hex.EncodeToString(block.Header.PreviousHash)
	if prevHeaderHash != thisHdrHashOfPrevHdr {
		return errors.Errorf("previous header hash is %s but expected %s", thisHdrHashOfPrevHdr, prevHeaderHash)
	}

	dataHash, err := protoutil.BlockDataHash(block.Data)
	if err != nil {
		return err
	}
	dataHashString := hex.EncodeToString(block.Header.DataHash)

	actualHashOfData := hex.EncodeToString(dataHash)
	if dataHashString != actualHashOfData {
		return errors.Errorf("data hash is %s but expected %s", dataHashString, actualHashOfData)
	}
	return nil
}

func (v *Verifier) verifyBlockDataAndMetadata(block *cb.Block, metadata []byte) ([]types.RequestInfo, error) {
	if block.Data == nil || len(block.Data.Data) == 0 {
		return nil, errors.New("empty block data")
	}

	if block.Metadata == nil || len(block.Metadata.Metadata) < len(cb.BlockMetadataIndex_name) {
		return nil, errors.New("block metadata is either missing or contains too few entries")
	}

	signatureMetadata, err := protoutil.GetMetadataFromBlock(block, cb.BlockMetadataIndex_SIGNATURES)
	if err != nil {
		return nil, err
	}
	ordererMetadataFromSignature := &cb.OrdererBlockMetadata{}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Rebuild the proposal ensuring DataHash = protoutil.BlockDataHash(block.Data) as the final step after data assembly
  2. Audit any code that mutates block.Data between hash computation and proposal
  3. Verify all nodes run the same protoutil version for BlockDataHash
  4. Discard and re-propose the block from the current leader

Example fix

// before: hash computed before adding a transaction
hash := protoutil.BlockDataHash(data)
data.Data = append(data.Data, extraTx) // data changed after hashing
// after: compute hash last
block.Data.Data = append(block.Data.Data, extraTx)
block.Header.DataHash = protoutil.BlockDataHash(block.Data)
Defensive patterns

Strategy: validation

Validate before calling

dataHash, err := protoutil.BlockDataHash(block.Data)
if err != nil { return err }
if !bytes.Equal(dataHash, block.Header.DataHash) {
    return errors.New("block.DataHash does not match block.Data; reject proposal")
}

Type guard

func dataHashMatches(b *cb.Block) bool {
    h, err := protoutil.BlockDataHash(b.Data)
    return err == nil && bytes.Equal(h, b.Header.DataHash)
}

Try / catch

if err := verifyHashChainAndDataHash(block, prevHash); err != nil && strings.Contains(err.Error(), "data hash") {
    logger.Warn("proposal data tampered or incorrectly assembled; discarding")
}

Prevention

When it happens

Trigger: A proposal whose block.Header.DataHash was computed over different data than block.Data contains — e.g. data mutated after hashing, or hash computed with a different algorithm/serialization.

Common situations: A buggy custom block assembler or middleware mutating block.Data after hashing; version mismatch in hashing utilities; corrupted proposal in-flight; adversarial/buggy leader proposing inconsistent blocks.

Related errors


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