hyperledger/fabric · info

not found

Error message

not found

What it means

BFTHeaderReceiver.LastBlockNum returns errors.New("not found") when hr.lastHeader is nil, i.e. no block header has been received/verified yet. It is a sentinel state error, not a transport failure: the receiver simply has no cached last block number. Callers like newHeaderClient use it to seed the starting block number.

Source

Thrown at common/deliverclient/blocksprovider/bft_header_receiver.go:204

		hr.logger.Infof("[%s][%s] Already stopped", hr.chainID, hr.endpoint)
		return nil
	}

	hr.logger.Infof("[%s][%s] Stopping", hr.chainID, hr.endpoint)
	hr.stop = true
	hr.clientCloserFunc()
	close(hr.stopChan)

	return nil
}

// LastBlockNum returns the last block number which was verified
func (hr *BFTHeaderReceiver) LastBlockNum() (uint64, time.Time, error) {
	hr.mutex.Lock()
	defer hr.mutex.Unlock()

	if hr.lastHeader == nil {
		return 0, time.Time{}, errors.New("not found")
	}

	return hr.lastHeader.Header.Number, hr.lastHeaderTime, nil
}

// LastBlock returns the last block which was verified
func (hr *BFTHeaderReceiver) LastBlock() (*common.Block, time.Time, error) {
	hr.mutex.Lock()
	defer hr.mutex.Unlock()

	if hr.lastHeader == nil {
		return nil, time.Time{}, errors.New("not found")
	}

	return hr.lastHeader, hr.lastHeaderTime, nil
}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Treat the "not found" error as an expected cold-start signal: fall back to a known ledger height or start block instead of failing.
  2. Ensure the header receiver has been started/fed blocks (Start/Initialize path) before querying LastBlockNum.
  3. If persistence is expected, verify the monitor was not recreated without restoring lastHeader from the ledger.

Example fix

// before
lastNum, _, err := hr.LastBlockNum()
if err != nil {
    return err
}
// after
lastNum, _, err := hr.LastBlockNum()
if err != nil {
    if err.Error() == "not found" {
        lastNum = ledgerHeight - 1 // cold start fallback
    } else {
        return err
    }
}
Defensive patterns

Strategy: fallback

Validate before calling

// check whether the receiver has state before querying
func hasLastHeader(hr *BFTHeaderReceiver) bool {
    _, _, err := hr.LastBlockNum()
    return err == nil // false => uninitialized, use ledger height instead
}

Type guard

func lastBlockNumKnown(err error) bool {
    return err == nil || !strings.Contains(err.Error(), "not found")
}

Try / catch

num, ts, err := hr.LastBlockNum()
if err != nil {
    if strings.Contains(err.Error(), "not found") {
        num, ts = seedFromLedgerHeight(), time.Time{} // cold-start fallback
    } else {
        return err
    }
}

Prevention

When it happens

Trigger: newHeaderClient calls LastBlockNum on a freshly created BFTHeaderReceiver before any header has been verified, or after a state where lastHeader was never set; the method returns (0, zero time, "not found").

Common situations: Cold start of the censorship monitor on a channel where this receiver has not yet processed any blocks; initializing a new header receiver after channel join; callers not treating 'not found' as an expected first-call condition.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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