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
- Generate a fresh nonce and recompute TxId (new signature) before resubmitting
- Check the transaction status first; if already committed successfully, do not resubmit
- 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
- Generate a fresh nonce and TxId for every submission attempt
- Check transaction status via qscc before retrying after timeouts
- Never reuse a signed proposal for retries
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
- incorrectly computed txid '%s' -- expected '%s'
- attempting to deploy a system chaincode %s/%s
- lscc upgrade/deploy should not include a code packages
- Private data is forbidden to be used in instantiate
- failed to marshal chaincode event
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/fac5dd6732ea20ea.
Report an issue: GitHub.