Double spending, avoiding double spending and avoiding replay attacks work differently in the different blockchain and distributed ledger systems, especially if we consider the ledger structure.
- Bitcoin: in Bitcoin, there are only unspent transaction outputs that behave as coins. At proof of work, the winner miner defines an order of the transactions that are applied to the ledger, to the unspent coins. The rule is that every unspent output can be spent only once, so considering the transaction order of the winning miner, the first valid transaction spending an unspent transaction output will really spend it, the next such a transaction will be considered as double spending. Similarly, old transactions can not be replayed, because the output is already spent.
- Corda: in some sense Corda uses a similar UTXO, unspent transaction based system as Bitcoin. However, the unspent outputs are not "coins", but complex state information of a contract. Similarly to Bitcoin, one output can be spent only once, realizing an efficient way of avoiding both double spending and replay attacks. As opposed to Bitcoin, there is no proof of work or mining, instead a special dedicated node, called the notary service is responsible for the ordering of the transactions.
- Ethereum: Ethereum is not an UTXO but an account based system. Practically, every account has some kind of a value field with a nonce that is incremented at every transaction. So a transaction not simply referring to an account but to an account with a certain nonce. At proof of work or proof of stake, the winning miner or validator creates a block that contain an ordering of the transactions. If there are two transactions referring to the same account with the same nonce, than the first will be executed and the second one will be recognized as double spending.
- Hyperledger Fabric: Fabric has a little bit similar mechanism than Ethereum. Each transaction is simulated by the smart-contracts at the endorsement peers and read write sets are defined. A read operator is not only defined to a variable but to a version of a variable. As a consequence, two transactions referring to the same input variable in a way that one reads and one writes that variable can be executed only in one specific order. If in one round a transaction already wrote into a variable the version of that variable will be increased, so in the same round, the variable can not be the read input of another transaction. In Fabric, the is no proof of work, but a specific service, called the ordering service is responsible for creating a valid order of the transactions.