Repository navigation
fix: sequence invalidation, batch timing metadata, weak notification listeners - #881
Merged
Just-Bamford merged 3 commits intoOct 21, 2026
Merged
Conversation
…ers (Sorokit#842) Convert the payment notification channel registry from a strong Set to a WeakSet + WeakRef-tracked list, so a subscriber becomes eligible for garbage collection as soon as no other reference to it remains, instead of being pinned alive indefinitely by the module-level registry.
…orokit#839) Add submittedAtMs, confirmedAtMs, and attemptCount to BatchTransactionStatus so callers can measure per-transaction latency, detect unusual delays, and inspect retry/attempt counts after a submitBatch() call, in both sequential and parallel mode.
… failure (Sorokit#836) A failed submission with a sequence-related error (e.g. tx_bad_seq) left the build-time autoFetchSequence cache holding the stale, now-incorrect sequence number, causing every subsequent submission for that account to fail with tx_bad_seq until the cache's TTL expired. Add isSequenceError() to detect sequence-related Horizon failures (via the structured extras.result_codes, with a message-based fallback), and invalidateSequenceCacheEntry() to clear a single account's cached sequence. submitTransaction's failure path now invalidates the cache entry for every source account in the transaction when the failure is sequence-related, forcing the next build to fetch a fresh sequence.
|
@Speedable2 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This batch fixes three stale-state and observability gaps in the transaction/notification layer: a stale sequence cache after a failed submission, missing timing metadata on batch submission results, and strong-referenced notification channel listeners that prevented garbage collection.
closes #836
closes #839
closes #842
Changes
AccountSequenceTrackerdoes not invalidate sequence on failed submissions — stale sequence is reused #836 - AccountSequenceTracker reuses stale sequence after failed submission:src/transaction/buildTransaction.ts'sautoFetchSequencecache (used by payment/offer/trustline builders) was never invalidated when a submission failed with a sequence-related error, so atx_bad_seqfailure cascaded into further failures until the cache's 5s TTL expired. AddedisSequenceError()insrc/shared/horizonErrorMapper.ts, which detects sequence errors from Horizon's structuredextras.result_codes(both transaction- and operation-level codes) with a message-based fallback. AddedinvalidateSequenceCacheEntry(publicKey)inbuildTransaction.ts.submitTransaction.ts's failure path now calls this for every source account (including muxed-account resolution) in the transaction whenever the failure is sequence-related, forcing the next build to fetch a fresh sequence from Horizon instead of reusing the stale one.BatchTransactionResultdoes not include timing metadata — cannot measure per-transaction latency or order #839 - BatchTransactionResult missing timing metadata: AddedsubmittedAtMs,confirmedAtMs, andattemptCounttoBatchTransactionStatusinsrc/transaction/batchSubmitter.ts. A newsubmitEntry()helper records the submit timestamp and increments the attempt count immediately before callingsubmitTransaction, andapplyResult()records the confirmation timestamp once the result settles, on both success and failure. Wired into both sequential and parallel (Promise.allSettled) submission modes, including the rejected-promise branch in parallel mode.NotificationChannellisteners are not weak references — event listeners prevent garbage collection of subscribers #842 - NotificationChannel listeners prevent garbage collection:src/transaction/paymentNotifications.ts'schannelRegistrywas a strongSet<PaymentNotificationChannel>, so a subscriber was kept alive forever once registered. Converted to aWeakSet(for dedup checks) plus aWeakRef-backed list (channelRefs) for enumeration, sinceWeakSethas no iteration protocol. Delivery now goes throughliveChannels(), which dereferences eachWeakRefand lazily prunes dead ones. AddedgetRegisteredChannelCount()as an observability/test helper. Required addingES2021.WeakReftotsconfig.json'slibarray (target staysES2020; this only adds the ambientWeakReftype declaration, available natively in the Node versions this package already targets).#841 (ManageOfferManager allows identical buying/selling assets) was found already implemented on
main-buildManageOfferTransactioninsrc/transaction/buildTransaction.ts(lines ~1906-1910) already rejects offers wheresellingResult.data.equals(buyingResult.data), returning aTX_BUILD_FAILEDerror. No change needed; left out of this batch. Flagging so the issue can be closed/confirmed by a maintainer independent of this PR.Test plan
src/shared/horizonErrorMapper.test.ts: unit tests forisSequenceError()against structured Horizon result codes (transaction- and operation-level), message fallback, and negative cases; confirmsmapHorizonErrorstill mapstx_bad_seqtoINVALID_TRANSACTION.src/tests/submit-sequence-invalidation.test.ts: integration-style test mockingHorizon.Serverend-to-end with a real signed testnet XDR, proving atx_bad_seqsubmission failure forces the nextautoFetchSequencebuild for that account to re-fetch from Horizon, while an unrelated failure (e.g.tx_insufficient_fee) leaves the cache untouched.src/tests/transaction.test.ts's "sequence number auto-fetch cache" suite with a test forinvalidateSequenceCacheEntry(), confirming it clears only the targeted account's entry and leaves others cached.src/transaction/batchSubmitter.test.tswith four new tests covering timing metadata on success, failure, sequential ordering, and parallel mode (including a rejected promise).src/tests/paymentNotifications.test.tswith a dedup test (registering the same channel instance twice only counts once) and a GC test guarded behindtypeof global.gc === "function"so it never flakes the suite under a different Node/vitest flag configuration.npm run typecheck,npm run lint, andnpm run testall pass (192 test files, 3846 tests passed, 0 failed, 80 pre-existing skips).Not run against a live Horizon network; the sequence-invalidation test mocks
Horizon.Serverwith a real signed XDR fixture rather than hitting testnet.