hyperledger/fabric · error

error validating ReadSet

Error message

error validating ReadSet

What it means

verifyReadSet rejected the update because an entry in the ReadSet does not match the current channel config — the read item's version differs from the current version, or the item does not exist. This is optimistic-concurrency validation: the update was built against a stale base config.

Source

Thrown at common/configtx/update.go:136

		return nil, errors.Errorf("cannot process nil ConfigUpdateEnvelope")
	}

	configUpdate, err := UnmarshalConfigUpdate(configUpdateEnv.ConfigUpdate)
	if err != nil {
		return nil, err
	}

	if configUpdate.ChannelId != vi.channelID {
		return nil, errors.Errorf("ConfigUpdate for channel '%s' but envelope for channel '%s'", configUpdate.ChannelId, vi.channelID)
	}

	readSet, err := mapConfig(configUpdate.ReadSet, vi.namespace)
	if err != nil {
		return nil, errors.Wrapf(err, "error mapping ReadSet")
	}
	err = vi.verifyReadSet(readSet)
	if err != nil {
		return nil, errors.Wrapf(err, "error validating ReadSet")
	}

	writeSet, err := mapConfig(configUpdate.WriteSet, vi.namespace)
	if err != nil {
		return nil, errors.Wrapf(err, "error mapping WriteSet")
	}

	deltaSet := computeDeltaSet(readSet, writeSet)
	signedData, err := protoutil.ConfigUpdateEnvelopeAsSignedData(configUpdateEnv)
	if err != nil {
		return nil, err
	}

	if err = vi.verifyDeltaSet(deltaSet, signedData); err != nil {
		return nil, errors.Wrapf(err, "error validating DeltaSet")
	}

	fullProposedConfig := vi.computeUpdateResult(deltaSet)

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Re-fetch the latest channel config, regenerate the update against it, and resubmit.
  2. Coordinate config updates so only one is in flight at a time (or merge pending changes before regenerating).
  3. If the read item was removed, remove it from the ReadSet or restructure the update.
  4. Check the wrapped error to identify the exact key whose version diverged.

Example fix

// before: base config version 3 (stale)
readSet["Application/Org2"].version = 3
// after: re-fetch current config and rebuild
latest := fetchLatestConfigBlock(channelID)
update := computeDiff(latest, desiredConfig)
Defensive patterns

Strategy: validation

Validate before calling

// Compare the ReadSet versions against the live config before submitting
func readSetCurrent(update *cb.ConfigUpdate, live *cb.Config) error {
    versions := map[string]uint64{}
    traverseConfigGroups(live.GetChannelGroup(), func(path string, g *cb.ConfigGroup) error {
        for k, v := range g.GetValues() { versions[path+"/"+k] = v.GetVersion() }
        return nil
    })
    // any read item whose recorded version != live version means a stale update
    return checkReadVersions(update.GetReadSet(), versions)
}

Try / catch

if _, err := validator.ProposeConfigUpdate(env, seq); err != nil {
    if strings.Contains(err.Error(), "error validating ReadSet") {
        // refetch latest config block, regenerate diff, resubmit
    }
    return err
}

Prevention

When it happens

Trigger: Calling proposeConfigUpdate/Validate with a ReadSet whose key versions don't match the validator's current config sequence, typically because another config update was committed since the base config was fetched.

Common situations: Two orgs preparing updates concurrently; using a config dump from hours/days ago; rebuilding a channel (new genesis block) while reusing old update files; joining orgs in sequence where each commit bumps versions.

Related errors


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