hyperledger/fabric · info

chain stopped

Error message

chain stopped

What it means

ErrChainStopped is the sentinel error returned by the follower chain's pull loops (pullAfterJoin, pullUntilLatestWithRetry, pullUntilTarget) when the chain's stop channel is closed. It is a normal, expected shutdown signal rather than a fault — the follower was told to stop (e.g. channel removal or chain halt) while a pull/retry loop was waiting.

Source

Thrown at orderer/common/follower/follower_chain.go:27

import (
	"bytes"
	"sync"
	"sync/atomic"
	"time"

	"github.com/hyperledger/fabric-lib-go/bccsp"
	"github.com/hyperledger/fabric-lib-go/common/flogging"
	"github.com/hyperledger/fabric-protos-go-apiv2/common"
	"github.com/hyperledger/fabric/orderer/common/cluster"
	"github.com/hyperledger/fabric/orderer/common/types"
	"github.com/hyperledger/fabric/orderer/consensus"
	"github.com/hyperledger/fabric/protoutil"
	"github.com/pkg/errors"
	"google.golang.org/protobuf/proto"
)

// ErrChainStopped is returned when the chain is stopped during execution.
var ErrChainStopped = errors.New("chain stopped")

//go:generate counterfeiter -o mocks/ledger_resources.go -fake-name LedgerResources . LedgerResources

// LedgerResources defines some interfaces of ledger & config resources needed by the follower.Chain.
type LedgerResources interface {
	// ChannelID The channel ID.
	ChannelID() string

	// Block returns a block with the given number,
	// or nil if such a block doesn't exist.
	Block(number uint64) *common.Block

	// Height returns the number of blocks in the chain this channel is associated with.
	Height() uint64

	// Append appends a new block to the ledger in its raw form.
	Append(block *common.Block) error
}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. No action needed — treat ErrChainStopped as a benign shutdown sentinel; check errors.Is(err, follower.ErrChainStopped) and ignore/log at debug
  2. If it appears unexpectedly, verify who called Halt on the follower chain (channel removal, orderer shutdown)
  3. Ensure caller code doesn't retry pulls after stop; stop loops instead of re-entering them

Example fix

// before
if err := c.pullUntilTarget(puller, target); err != nil {
    c.logger.Panicf("Failed pulling to target: %v", err)
}
// after
if err := c.pullUntilTarget(puller, target); err != nil && !errors.Is(err, ErrChainStopped) {
    c.logger.Errorf("Failed pulling to target: %v", err)
}
Defensive patterns

Strategy: try-catch

Type guard

func isChainStopped(err error) bool { return errors.Is(err, follower.ErrChainStopped) }

Try / catch

if err := chain.pullUntilTarget(puller, target); err != nil {
    if errors.Is(err, follower.ErrChainStopped) {
        logger.Debug("follower stopped during pull; shutting down cleanly")
        return nil
    }
    return fmt.Errorf("pull failed: %w", err)
}

Prevention

When it happens

Trigger: Stopping or removing a follower channel (chain.Halt / channel deactivation) while pullAfterJoin, pullUntilLatestWithRetry, or pullUntilTarget is blocked on timeAfter or height polling; the select's stopChan case wins and returns ErrChainStopped.

Common situations: Orderer shutdown with active followers; removing a follower channel after catching up; admin halting a chain during consensus relation changes; calling Halt concurrently with ongoing block pulls.

Related errors


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