You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add timeout margin gate (FULFILLMENT_TIMEOUT_MARGIN_BLOCKS = 3) to prevent new sends when too few blocks remain before swap timeout. Cached sends bypass the margin to allow mark_fulfilled retries.
Write append-only JSONL recovery log for stuck-funds scenarios (terminal failures and timed-out swaps with sent funds).
Design
The timeout margin gates new fund sends only. Once funds are already sent (cached), all downstream retry paths remain open regardless of proximity to timeout so the miner can still finalize on-chain.
Test plan
Timeout margin blocks new sends within 3 blocks of deadline
Cached sends bypass margin and retry mark_fulfilled
Terminal contract errors halt retries and write recovery entry
Transient errors (RPC_FAILURE, INSUFFICIENT_BALANCE) allow continued retry
Stale cache cleanup detects timed-out swaps and logs recovery entry
Completed swaps cleaned up without false-positive recovery alerts
hey, double-send is already prevented at fulfillment.py:206 via the _sent short-circuit, with _save_sent_cache() persisting atomically and _load_sent_cache() restoring on startup. Miner stability is present and sufficient for MVP. Further and further engineering requires further and further maintenance, must choose our work carefully too
near-timeout sends — today it's just the bare current_block >= swap.timeout_block check at fulfillment.py:85. a cushion is a one-line add at the miners choice (could be added with a default, that's fair)
futile retries — retries already happen implicitly and safely. process_swap returning False skips mark_processed, so the swap re-enters new_pending next poll tick; the _sent cache at fulfillment.py:206 short-circuits the re-send and we retry mark_fulfilled with the same payload until it lands or the swap exits Active. that covers transient RPC, CALL_FAILED, and balance blips.
no recovery visibility — bt.logging.error already hits disk and _sent persists atomically across restart
On retries — the implicit loop handles transient errors fine, but SwapNotFound/InvalidStatus/NotAssignedMiner from mark_fulfilled will never land. The miner retries that same doomed call every cycle with no escalation. That's the specific gap: funds left the wallet, contract permanently rejects the update, and the only trace is buried in rotating logs alongside every other error.
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
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.
Closes #19
Summary
FULFILLMENT_TIMEOUT_MARGIN_BLOCKS = 3) to prevent new sends when too few blocks remain before swap timeout. Cached sends bypass the margin to allowmark_fulfilledretries.mark_fulfilledcontract errors (SwapNotFound,InvalidStatus,NotAssignedMiner,MinerNotActive) to stop futile retries.Design
The timeout margin gates new fund sends only. Once funds are already sent (cached), all downstream retry paths remain open regardless of proximity to timeout so the miner can still finalize on-chain.
Test plan
mark_fulfilledRPC_FAILURE,INSUFFICIENT_BALANCE) allow continued retry