Parallel calls to TransactionBuilderService.buildDepositTransaction() sharing
the same source account fetch the same Horizon sequence number and produce
conflicting transactions. This PR adds SequenceManager — a small per-account
async mutex that serialises sequence allocation so concurrent builds never
collide.
SequenceManager uses a per-account Promise chain as a mutex:
nextSequence(accountId)— acquires the lock, fetches a fresh sequence from Horizon, increments it, releases the lock, returns the allocatedbigint- Lock is released in
finally— a thrown error never leaves the queue stuck - Per-account isolation — one account's Horizon latency does not block another
- No external dependencies — plain Promise chaining, no
async-mutexpackage clearLock(accountId)/hasLock(accountId)— test/utility helpers
46 tests across 8 suites:
| Suite | Tests |
|---|---|
| Basic operation — sequence + 1, bigint parsing | 5 |
Concurrency — no duplicates under Promise.all (2, 5, 10 concurrent) |
4 |
| Ordering — FIFO allocation, serialised loadAccount calls | 2 |
| Lock release on error — first fails, subsequent succeed | 4 |
| Multiple accounts — independent serialisation | 3 |
| Stale Horizon read recovery — fresh fetch per call | 2 |
| Edge cases — near-bigint boundary, sequential calls, special chars | 3 |
| Utility methods — clearLock, hasLock | 5 |
Added Concurrency — Sequence Manager section documenting the problem, solution, usage example, and guarantees.
- No duplicate sequence under parallel calls (
Promise.alltests) - Lock released even on thrown errors (
finallyblock tests) - Tests assert ordering (FIFO suite)
- Docs updated
npm test -- --testPathPattern="sequenceManager.test"All 46 tests pass. No external dependencies required.
closes #416