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
- Treat the "not found" error as an expected cold-start signal: fall back to a known ledger height or start block instead of failing.
- Ensure the header receiver has been started/fed blocks (Start/Initialize path) before querying LastBlockNum.
- 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
- Seed header receivers from ledger height on startup instead of assuming prior state.
- Persist last verified header across restarts if continuity matters.
- Write tests covering first-call (cold) behavior of LastBlockNum/LastBlock.
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
- Ledger not opened [%s]
- no orderer section in config block
- too few arguments
- chaincode type not supported: %s
- incorrect number of arguments
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/4849c2dd302bf393.
Report an issue: GitHub.