hyperledger/fabric · error

ProposalResponsePayloads do not match (base64): '%s' vs '%s'

Error message

ProposalResponsePayloads do not match (base64): '%s' vs '%s'

What it means

When multiple proposal responses are supplied, CreateSignedTx requires all ProposalResponsePayloads to be bitwise identical so a deterministic transaction can be built. If any payload differs (different read-write set, different result), the error reports both payloads base64-encoded for comparison.

Source

Thrown at protoutil/txutils.go:191

	if !bytes.Equal(signerBytes, shdr.Creator) {
		return nil, errors.New("signer must be the same as the one referenced in the header")
	}

	// ensure that all actions are bitwise equal and that they are successful
	var a1 []byte
	for n, r := range resps {
		if r.Response.Status < 200 || r.Response.Status >= 400 {
			return nil, errors.Errorf("proposal response was not successful, error code %d, msg %s", r.Response.Status, r.Response.Message)
		}

		if n == 0 {
			a1 = r.Payload
			continue
		}

		if !bytes.Equal(a1, r.Payload) {
			return nil, errors.Errorf("ProposalResponsePayloads do not match (base64): '%s' vs '%s'",
				b64.StdEncoding.EncodeToString(r.Payload), b64.StdEncoding.EncodeToString(a1))
		}
	}

	// fill endorsements according to their uniqueness
	endorsersUsed := make(map[string]struct{})
	var endorsements []*peer.Endorsement
	for _, r := range resps {
		if r.Endorsement == nil {
			continue
		}
		key := string(r.Endorsement.Endorser)
		if _, used := endorsersUsed[key]; used {
			continue
		}
		endorsements = append(endorsements, r.Endorsement)
		endorsersUsed[key] = struct{}{}
	}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Make the chaincode deterministic: no randomness, map-range-order dependence, wall-clock time, or external calls in transactions
  2. Ensure all endorsers are at the same ledger height before endorsement (retry or wait for sync)
  3. Collect responses from a single proposal instance (same txid/nonce) rather than mixing proposals
  4. Diff the base64 payloads in the error to identify the divergent field

Example fix

// before
// chaincode nondeterminism
for k := range stateMap { results[k] = stateMap[k] } // map order varies per peer
// after
keys := make([]string, 0, len(stateMap))
for k := range stateMap { keys = append(keys, k) }
sort.Strings(keys)
for _, k := range keys { results[k] = stateMap[k] }
Defensive patterns

Strategy: validation

Validate before calling

func payloadsIdentical(resps []*peer.ProposalResponse) error {
    if len(resps) < 2 { return nil }
    first := resps[0].Payload
    for _, r := range resps[1:] {
        if !bytes.Equal(first, r.Payload) {
            return fmt.Errorf("divergent endorsement payloads")
        }
    }
    return nil
}

Try / catch

if err := payloadsIdentical(resps); err != nil {
    // endorsers disagree: resync peers or fix chaincode determinism, then retry
    return nil, err
}
tx, err := protoutil.CreateSignedTx(proposal, signer, resps...)

Prevention

When it happens

Trigger: Endorsers returned divergent results for the same proposal — different read/write sets, nondeterministic chaincode (map iteration, timestamps, randomness), or queries executed at different block heights.

Common situations: Nondeterministic chaincode using Go map iteration order or time.Now(); endorsers on different ledger states (one behind); using responses collected at different times or from proposals with differing nonces; CouchDB vs LevelDB divergence.

Related errors


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