hyperledger/fabric · error

proposed update requires that key %s be at version %d, but i

Error message

proposed update requires that key %s be at version %d, but it is currently at version %d

What it means

verifyReadSet also checks version equality: each read-set entry must match the current version of that config element. If the element has been modified since the update was prepared, the read set is stale and the update is rejected with this error.

Source

Thrown at common/configtx/update.go:27

import (
	"maps"
	"strings"

	cb "github.com/hyperledger/fabric-protos-go-apiv2/common"
	"github.com/hyperledger/fabric/common/policies"
	"github.com/hyperledger/fabric/protoutil"
	"github.com/pkg/errors"
)

func (vi *ValidatorImpl) verifyReadSet(readSet map[string]comparable) error {
	for key, value := range readSet {
		existing, ok := vi.configMap[key]
		if !ok {
			return errors.Errorf("existing config does not contain element for %s but was in the read set", key)
		}

		if existing.version() != value.version() {
			return errors.Errorf("proposed update requires that key %s be at version %d, but it is currently at version %d", key, value.version(), existing.version())
		}
	}
	return nil
}

func computeDeltaSet(readSet, writeSet map[string]comparable) map[string]comparable {
	result := make(map[string]comparable)
	for key, value := range writeSet {
		readVal, ok := readSet[key]

		if ok && readVal.version() == value.version() {
			continue
		}

		// If the key in the readset is a different version, we include it
		// Error checking on the sanity of the update is done against the config
		result[key] = value
	}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Fetch the newest config block, recompute read/write sets at the current versions, and resubmit.
  2. Automate config fetch -> update -> submit atomically to avoid racing other updates.
  3. Identify the concurrent update that bumped the version and merge your changes on top of it.

Example fix

// before
readSet[key] = comparable{ConfigValue: &cb.ConfigValue{Version: 3}} // config now at 4
// after
current := vi.configMap[key]; readSet[key] = comparable{ConfigValue: &cb.ConfigValue{Version: current.version(), Value: newVal}}
Defensive patterns

Strategy: retry

Validate before calling

for key, value := range update.GetReadSet() {
  if current[key].Version() != value.Version() {
    return fmt.Errorf("version drift on %s: read %d, current %d", key, value.Version(), current[key].Version())
  }
}

Type guard

func versionsMatch(value comparable, existing comparable) bool { return value.version() == existing.version() }

Try / catch

err := validator.ProposeUpdate(env)
if err != nil && strings.Contains(err.Error(), "currently at version") {
  env = rebuildUpdateFromLatestConfig() // fetch fresh config, rebuild, resubmit
}

Prevention

When it happens

Trigger: Submitting a channel update whose read_set entry has version N while the current config element is at a version > N (it was changed by a prior update).

Common situations: Another admin updated channel config between the client fetching the config and submitting its update; optimistic-concurrency failure on channel configuration.

Related errors


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