Skip to content

Commit b18041e

Browse files
committed
Update Concurrency Guide
1 parent c3329b9 commit b18041e

1 file changed

Lines changed: 26 additions & 33 deletions

File tree

website/docs/guides/concurrency.md

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,27 @@ User A ─── tx spending UTXO #1 ──→ ✓ Accepted (first-seen)
1414
User B ─── tx spending UTXO #1 ──→ ✗ Rejected (conflict)
1515
```
1616

17-
This means a single-UTXO contract can only process one interaction per block at worst, or one per few seconds if users build [unconfirmed transaction chains](/docs/guides/lifecycle#unconfirmed-transaction-chains). For any contract with public usage, this creates a serious bottleneck.
17+
This means a single-UTXO contract can only process one interaction every few seconds without running into conflicts. For any contract with public usage, this can degrade the user experience.
1818

1919
:::tip
2020
As covered in the [Transaction Lifecycle](/docs/guides/lifecycle) guide, Bitcoin Cash supports unlimited unconfirmed transaction chains. In theory, users could take turns chaining transactions on the same UTXO. In practice, coordinating this is fragile and doesn't scale.
2121
:::
2222

23-
## The Solution: Multi-Threaded Contracts
23+
## Solution: Peer-to-Peer Contracts
24+
25+
The simplest way to avoid UTXO contention is to avoid shared UTXOs entirely. In a **peer-to-peer contract**, each contract instance is created between specific participants with predetermined terms. Only those participants can spend the UTXO, so there is no public competition for it and no concurrency problem.
26+
27+
Many common contract types are naturally peer-to-peer: escrows, vaults, multisig wallets, and derivatives contracts. These don't need threading — each instance is independent by design. For example, [AnyHedge][anyhedge] is a derivatives protocol where two parties agree on terms, lock BCH into a shared contract UTXO, and settle based on an oracle price at maturity — thousands of these contracts can exist simultaneously without any contention because each is a private UTXO between two specific parties.
28+
29+
Peer-to-peer contracts are the right choice when:
30+
31+
- The contract involves a **fixed set of participants** known at creation time.
32+
- There is **no shared resource** (like a liquidity pool) that many users need to access concurrently.
33+
- The contract's terms are **predetermined** — users agree on parameters before funding.
34+
35+
When a contract *does* need to serve arbitrary public users against shared state, peer-to-peer design won't work and you need the threading patterns described below.
36+
37+
## Solution: Multi-Threaded Contracts
2438

2539
The key insight is to create **multiple identical contract UTXOs**, each acting as an independent "thread". Users interact with different threads in parallel without conflicts.
2640

@@ -36,11 +50,11 @@ Each thread is a separate UTXO locked to the same contract. Because they are ind
3650

3751
### Stateless vs Stateful Threads
3852

39-
How you design threads depends on whether your contract carries state.
53+
How you design threads depends on whether your contract carries state. As is common when working with concurrency, it is easier to leverage it when you have a stateless system. Most of the complexity in concurrent systems comes from the stateful parts.
4054

41-
**Stateless threads** are the simplest case. The contract enforces rules but doesn't track any evolving state. You create multiple identical UTXOs and any of them can service any request. Examples: escrow contracts, multisig wallets, utility functions in a multi-contract system.
55+
**Stateless threads** are the simplest case. The contract enforces rules but doesn't track any evolving state. You create multiple identical UTXOs and any of them can service any request.
4256

43-
**Stateful threads** carry state in the NFT commitment field. Each thread tracks its own state independently. The state across threads may drift and the system must be designed to tolerate this — see [Managing State Drift](#designing-for-state-drift) below.
57+
**Stateful threads** carry state in the NFT commitment field. Each thread tracks its own state independently. The state across threads may drift and the system must be designed to tolerate this — see [Managing State Drift](#managing-state-drift) below.
4458

4559
Thread UTXOs are created during the [genesis transaction](/docs/guides/deployment). Each thread gets its own UTXO (and optionally an NFT with distinct state in its commitment field), all created in one atomic transaction. See the [Contract Deployment](/docs/guides/deployment) guide for details on setting up genesis transactions with multiple outputs.
4660

@@ -69,34 +83,9 @@ Random selection is the simplest approach and works well for client-side applica
6983

7084
### Handling Collisions
7185

72-
Even with random selection, collisions will occasionally happen. When they do, the network provider returns an error indicating the selected UTXO was already spent by another transaction. Your application needs to detect this specific error and distinguish it from other failures like insufficient fees or invalid transactions.
73-
74-
A basic retry strategy is to re-fetch UTXOs and re-select a different thread:
86+
Even with random selection, collisions will occasionally happen. When they do, the network provider returns an error indicating the selected UTXO was already spent by another transaction. Your application needs to detect this specific error and distinguish it from other failures like insufficient fees or invalid transactions, in order to retry the transaction. The key detail is that the entire transaction must be rebuilt on retry — re-fetching UTXOs from the network gives you a fresh set where the conflicting UTXO is no longer available.
7587

76-
```ts
77-
async function sendWithRetry(buildTransaction: () => Promise<TransactionDetails>, maxRetries = 3) {
78-
for (let attempt = 0; attempt < maxRetries; attempt++) {
79-
try {
80-
return await buildTransaction();
81-
} catch (error) {
82-
const isMempoolConflict = error instanceof Error
83-
&& error.message.includes('txn-mempool-conflict');
84-
if (!isMempoolConflict || attempt === maxRetries - 1) throw error;
85-
// Re-fetching UTXOs inside buildTransaction will naturally exclude the spent UTXO
86-
}
87-
}
88-
}
89-
```
90-
91-
:::note
92-
The `txn-mempool-conflict` error string is specific to the `ElectrumNetworkProvider`. Other network providers may return different error messages for the same situation — check your provider's error format when implementing collision detection.
93-
:::
94-
95-
The key detail is that the entire transaction must be rebuilt on retry — re-fetching UTXOs from the network gives you a fresh set where the conflicting UTXO is no longer available.
96-
97-
:::note
9888
This retry pattern applies when the dapp or server broadcasts the transaction directly. In a [WalletConnect](/docs/guides/walletconnect) setup with `broadcast: true`, the user's wallet handles broadcasting and will encounter the mempool conflict error instead. The dapp cannot catch and retry automatically — the user would need to retry the action, at which point the dapp should re-fetch UTXOs and select a new thread.
99-
:::
10089

10190
## Modular Contract Functions
10291

@@ -116,7 +105,9 @@ Consider these factors when deciding:
116105
- **Stateful contracts** benefit from fewer threads, since each thread evolves its state independently and users or the application must keep them in sync.
117106
- **Contracts with shared resources** like a liquidity pool present a trade-off: splitting into persistent threads fragments the resource across them.
118107

119-
For contracts with shared resources, this fragmentation can be significant — for example, a DEX pool split into 4 threads means each thread holds roughly 1/4 of the liquidity, resulting in worse price execution per trade. This is a key motivation for the [accumulate-and-merge](#accumulate-and-merge-threading) model, which avoids permanent fragmentation by periodically merging threads back together. Alternatively, keep shared-state contracts as a single UTXO and offload logic to parallel function contracts.
108+
For contracts with shared resources, this fragmentation can be significant. [Cauldron][cauldron], a BCH DEX, illustrates the tension well: each liquidity provider creates their own independent contract UTXO ("micro-pool"), which looks like natural threading. But to get good price execution, a swap transaction needs to aggregate many of these micro-pools as inputs in a single transaction — so two concurrent swaps will still conflict on shared inputs, and the separate UTXOs don't actually help with concurrency.
109+
110+
This is a fundamental trade-off for DEX designs: combining liquidity for better execution works against splitting UTXOs for concurrency. The [accumulate-and-merge](#accumulate-and-merge-threading) model is one approach that addresses this by periodically merging threads back together for batch settlement.
120111

121112
:::caution
122113
Depending on your contract design, the number of threads may be fixed at deployment and cannot be changed later. Plan your thread count carefully based on realistic usage estimates before deploying.
@@ -180,9 +171,11 @@ Users ── select ──→ ├─── Thread 2 ───┤ independen
180171
└─── Thread 4 ───┘
181172
```
182173

183-
These patterns have been proven in production systems handling thousands of concurrent interactions on Bitcoin Cash. The UTXO model's explicit state makes reasoning about concurrency straightforward: if two transactions don't share any inputs, they cannot conflict.
174+
These patterns have been proven in production systems handling concurrent interactions on Bitcoin Cash. The UTXO model's explicit state makes reasoning about concurrency straightforward: if two transactions don't share any inputs, they cannot conflict.
184175

185176
For adversarial considerations around multi-threaded systems — such as intentional double-spends or targeted contention attacks — see the [Adversarial Analysis](/docs/guides/adversarial) guide.
186177

178+
[anyhedge]: https://anyhedge.com
179+
[cauldron]: https://www.cauldron.quest
187180
[jedex]: https://github.com/bitjson/jedex
188181
[cashninjas-mint]: https://github.com/cashninjas/minting-contract

0 commit comments

Comments
 (0)