hyperledger/fabric · error

block deliverer for channel `%s` already exists

Error message

block deliverer for channel `%s` already exists

What it means

StartDeliverForChannel returns this when a block deliverer is already registered on the DeliveryClient (d.blockDeliverer != nil), since Fabric's delivery client supports exactly one active deliverer per client instance. A second start request is rejected.

Source

Thrown at core/deliverservice/deliveryclient.go:115

// StartDeliverForChannel starts blocks delivery for channel
// initializes the grpc stream for given chainID, creates blocks provider instance
// that spawns in go routine to read new blocks starting from the position provided by ledger
// info instance.
func (d *deliverServiceImpl) StartDeliverForChannel(chainID string, ledgerInfo blocksprovider.LedgerInfo, finalizer func()) error {
	d.lock.Lock()
	defer d.lock.Unlock()

	if d.stopping {
		errMsg := fmt.Sprintf("block deliverer for channel `%s` is stopping", chainID)
		logger.Errorf("Delivery service: %s", errMsg)
		return errors.New(errMsg)
	}

	if d.blockDeliverer != nil {
		errMsg := fmt.Sprintf("block deliverer for channel `%s` already exists", chainID)
		logger.Errorf("Delivery service: %s", errMsg)
		return errors.New(errMsg)
	}

	// TODO save the initial bundle in the block deliverer in order to maintain a stand alone BlockVerifier that gets updated
	// immediately after a config block is pulled and verified.
	bundle, err := channelconfig.NewBundle(chainID, d.conf.ChannelConfig, d.conf.CryptoProvider)
	if err != nil {
		return errors.WithMessagef(err, "failed to create block deliverer for channel `%s`", chainID)
	}
	oc, ok := bundle.OrdererConfig()
	if !ok {
		// This should never happen because it is checked in peer.createChannel()
		return errors.Errorf("failed to create block deliverer for channel `%s`, missing OrdererConfig", chainID)
	}

	switch ct := oc.ConsensusType(); ct {
	case "etcdraft":
		d.blockDeliverer, err = d.createBlockDelivererCFT(chainID, ledgerInfo)
	case "BFT":

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Call StopDeliverForChannel before attempting to start again on the same client.
  2. Track started state in your code and make StartDeliverForChannel idempotent at the call site.
  3. Create a separate DeliveryClient instance if you need concurrent block delivery consumers.
  4. If caused by leader election retries, add a guard so only the elected leader starts delivery once.

Example fix

// before
client.StartDeliverForChannel(chainID, f, stop)
client.StartDeliverForChannel(chainID, f2, stop2) // error
// after
client.StartDeliverForChannel(chainID, f, stop)
client.StopDeliverForChannel(chainID)
client.StartDeliverForChannel(chainID, f2, stop2)
Defensive patterns

Strategy: validation

Validate before calling

var delivering atomic.Bool
if !delivering.CompareAndSwap(false, true) {
    return errors.New("delivery already running")
}
err := client.StartDeliverForChannel(chainID, finalize, stopCh)
if err != nil {
    delivering.Store(false)
}

Try / catch

if err := client.StartDeliverForChannel(chainID, f, stopCh); err != nil {
    if strings.Contains(err.Error(), "already exists") {
        logger.Infof("deliverer already active for %s", chainID)
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Calling StartDeliverForChannel twice for the same client without StopDeliverForChannel in between; two components (e.g. gossip leader and custom consumer) both starting delivery on one shared client.

Common situations: Leader-election flapping causing repeated start calls; application code retrying start after a transient failure without stopping first; sharing a single DeliveryClient across channel services.

Related errors


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