hyperledger/fabric · error

duplicate transaction found [%s]. Creator [%x]

Error message

duplicate transaction found [%s]. Creator [%x]

What it means

preProcess performs a uniqueness check: if GetTransactionByID finds the proposal's TxId already committed on the channel, the transaction is a duplicate (possible replay or benign retry) and the proposal is rejected with the txid and creator identity in the message.

Source

Thrown at core/endorser/endorser.go:284

		// ignore uniqueness checks; also, chainless proposals are not validated using the policies
		// of the chain since by definition there is no chain; they are validated against the local
		// MSP of the peer instead by the call to ValidateUnpackProposal above
		return nil
	}

	// labels that provide context for failure metrics
	meterLabels := []string{
		"channel", up.ChannelHeader.ChannelId,
		"chaincode", up.ChaincodeName,
	}

	// Here we handle uniqueness check and ACLs for proposals targeting a chain
	// Notice that ValidateProposalMessage has already verified that TxID is computed properly
	if _, err = e.Support.GetTransactionByID(up.ChannelHeader.ChannelId, up.ChannelHeader.TxId); err == nil {
		// increment failure due to duplicate transactions. Useful for catching replay attacks in
		// addition to benign retries
		e.Metrics.DuplicateTxsFailure.With(meterLabels...).Add(1)
		return errors.Errorf("duplicate transaction found [%s]. Creator [%x]", up.ChannelHeader.TxId, up.SignatureHeader.Creator)
	}

	// check ACL only for application chaincodes; ACLs
	// for system chaincodes are checked elsewhere
	if !e.Support.IsSysCC(up.ChaincodeName) {
		// check that the proposal complies with the Channel's writers
		if err = e.Support.CheckACL(up.ChannelHeader.ChannelId, up.SignedProposal); err != nil {
			e.Metrics.ProposalACLCheckFailed.With(meterLabels...).Add(1)
			return err
		}
	}

	return nil
}

// ProcessProposal process the Proposal
// Errors related to the proposal itself are returned with an error that results in a grpc error.
// Errors related to proposal processing (either infrastructure errors or chaincode errors) are returned with a nil error,

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Generate a fresh nonce and recompute TxId (new signature) before resubmitting
  2. Check the transaction status first; if already committed successfully, do not resubmit
  3. Use idempotent client-side request tracking so retries create new proposals

Example fix

// before
resubmit(sameSignedProposal) // same nonce/txid
// after
nonce := make([]byte, 24)
rand.Read(nonce)
txid := computeTxID(creator, nonce) // re-sign proposal with new nonce
Defensive patterns

Strategy: retry

Validate before calling

if _, err := q.GetTransactionByID(ch, txid); err == nil {
    return errors.New("txid already exists: regenerate nonce before resubmit")
}

Try / catch

resp, err := endorser.ProcessProposal(ctx, prop)
if err != nil && strings.Contains(err.Error(), "duplicate transaction found") {
    // regenerate nonce, rebuild txid, re-sign, then retry once
    prop = newSignedProposal(creator, freshNonce())
    resp, err = endorser.ProcessProposal(ctx, prop)
}

Prevention

When it happens

Trigger: Submitting a proposal whose computed TxId matches an already-validated/committed transaction — e.g. resubmitting the exact same signed proposal with same nonce and creator.

Common situations: Client retry logic resending the same signed proposal after a timeout; replay-attack attempts; SDK reusing a stale proposal/nonce after a failed commit; gateway double-submits.

Related errors


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