fix(subscriptions): bound chain evaluator tip by per-trigger decoder progress - #50
Closed
Rapha-btc wants to merge 1 commit into
Closed
fix(subscriptions): bound chain evaluator tip by per-trigger decoder progress#50Rapha-btc wants to merge 1 commit into
Rapha-btc wants to merge 1 commit into
Conversation
…progress Chain print subscriptions silently dropped events. The evaluator's block-source tip tracks block INGESTION, which runs ahead of decode; each event type is decoded by an independent, differently-paced decoder (print is heavier and trails ft_transfer). When the evaluator processed a height before the print decoder committed it, the print wasn't in decoded_events yet, the match was missed, and the forward-only cursor never revisited it. Bound the evaluator tip by the MIN decoder_checkpoints height over ONLY the decoders feeding the event types its active subscriptions read (derived from referencedEventTypes: decode.<event_type>.v1). A stalled decoder no subscription reads (e.g. a defunct pox4) is excluded and can never gate deliveries. Null floor -> fall back to the raw tip rather than stall. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YMvYzMv1qpiUMUeGA81bXB
|
@Rapha-btc is attempting to deploy a commit to the root Team on Vercel. A member of the Team first needs to authorize it. |
Owner
|
thanks for this @Rapha-btc - i didn't take the patch as is (a missing decoder checkpoint fell back to the raw tip, which is the original miss), but the chain evaluator now bounds its tip by the slowest referenced decoder checkpoint, so a lagging print decoder cannot skip those heights. what you can try to do now if you haven't repaired:
it caps at 100k blocks so you can chunk if the window is larger |
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.
Symptom
Chain
print_eventsubscriptions silently skipped blocks: no outbox/delivery row was ever created (not a webhook failure). Example on our OSS deployment: a sub on*fakfun-market-registry*got the print in block 8864250 but nothing for 8864267 / 8864295, where the same contract emitted the same kind of print. A sweep over 3 days found 283 missed prints across 13 subscriptions.Root cause
The chain evaluator's block-source tip tracks block ingestion, which runs ahead of decode. Each event type is decoded by an independent, differently paced decoder;
print(arbitrary Clarity payloads) routinely trailsft_transferby 1-15 s. When the evaluator processed a height before the print decoder had committed it, the print was not yet indecoded_events, the match was missed, and the forward-only cursor never revisited that height. Blocks with no ft_transfer were unaffected, which made the misses look random.Observed on the missed heights (decoded_events timestamps): 8864267 ft_transfer 02:19:40.74, print 02:19:41.66; 8864295 ft_transfer 02:25:31.48, print 02:25:46.96.
Fix
Bound the evaluator tip by the minimum
decoder_checkpointsheight over only the decoders feeding the event types its active subscriptions read (derived fromreferencedEventTypes, decoder namedecode.<event_type>.v1). A stalled decoder that no subscription reads (e.g. an idlepox4) is excluded and can never gate deliveries. A null floor (no referenced decoders / none checkpointed) falls back to the raw tip rather than stalling.We first tried a global
minover all decoders ingetIndexTip; that pinned the whole tip on a stalledpox4decoder within minutes, hence the per-trigger design.trigger-evaluator.ts:referencedDecoderNames,lowestDecoderHeight,decoderFloorHeighttrigger-evaluator-loop.ts:tip = min(rawTip, floor)trigger-evaluator-decoder-floor.test.ts: pure unit test reproducing the race (lagging print lowers the floor below ft_transfer; a far-behind unreferenced decoder is ignored)Verified live on our multi-service OSS deployment: after deploying, the evaluator cursor tracks the print decoder checkpoint exactly, and a cursor rewind replayed all 283 misses (idempotent via
(subscription_id, dedup_key)), post-sweep misses = 0.Follow-up (not blocking)
A cursor rewind re-emits for every active subscription regardless of
created_at, so a replay can back-deliver pre-creation events to recently created subs (58 in our case; harmless for idempotent receivers). Bounding emits bysubscription.created_at, or storing a per-sub start block, would make rewinds exact.Context: follows up on #47 (safe observer defaults). Deployed and validated on the fak.fun / STX Juice node.