@@ -4,9 +4,12 @@ import type { StreamDataRequest, StreamDataResponse } from "../stream";
44import { type ChainTracker , createChainTracker } from "./chain-tracker" ;
55import type { RpcStreamConfig } from "./config" ;
66import { blockInfoToCursor } from "./helpers" ;
7+ import { createTracer } from "./otel" ;
78
89const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000 ;
910
11+ const tracer = createTracer ( ) ;
12+
1013type State < TFilter , TBlock > = {
1114 // The network-specific config.
1215 config : RpcStreamConfig < TFilter , TBlock > ;
@@ -118,53 +121,93 @@ async function* dataStreamLoop<TFilter, TBlock>(
118121 state : State < TFilter , TBlock > ,
119122) : AsyncGenerator < StreamDataResponse < TBlock > > {
120123 while ( shouldContinue ( state ) ) {
121- const { cursor, chainTracker } = state ;
122-
123- // Always check for heartbeats first to ensure we don't miss any.
124- if ( shouldSendHeartbeat ( state ) ) {
125- state . lastHeartbeat = Date . now ( ) ;
126- yield { _tag : "heartbeat" } ;
127- }
128-
129- if ( shouldRefreshFinalized ( state ) ) {
130- const finalizedInfo = await state . config . fetchCursor ( {
131- blockTag : "finalized" ,
132- } ) ;
133-
134- if ( finalizedInfo === null ) {
135- throw new Error ( "Failed to fetch finalized cursor" ) ;
136- }
137-
138- const finalized = blockInfoToCursor ( finalizedInfo ) ;
139- const finalizedChanged =
140- state . chainTracker . updateFinalized ( finalizedInfo ) ;
141-
142- // Only send finalized if it's needed.
143- if ( finalizedChanged && state . cursor . orderKey > finalized . orderKey ) {
144- yield { _tag : "finalize" , finalize : { cursor : finalized } } ;
145- }
146-
147- state . lastFinalizedRefresh = Date . now ( ) ;
148- }
149-
150- const finalized = chainTracker . finalized ( ) ;
124+ const messages = await tracer . startActiveSpan (
125+ "data-stream.loop" ,
126+ async ( span ) => {
127+ try {
128+ const { cursor, chainTracker } = state ;
129+
130+ const attributes : Record < string , string | number | boolean > = {
131+ cursor : cursor . orderKey . toString ( ) ,
132+ head : chainTracker . head ( ) . orderKey . toString ( ) ,
133+ finalized : chainTracker . finalized ( ) . orderKey . toString ( ) ,
134+ actionSendHeartbeat : false ,
135+ actionRefreshFinalized : false ,
136+ actionBackfillFinalized : false ,
137+ actionWaitForHeadChange : false ,
138+ actionProduceLiveBlocks : false ,
139+ } ;
151140
152- // console.log(
153- // `[DS] RpcLoop: c=${cursor.orderKey} f=${finalized.orderKey} h=${chainTracker.head().orderKey}`,
154- // );
141+ const messages : StreamDataResponse < TBlock > [ ] = [ ] ;
142+
143+ if ( shouldSendHeartbeat ( state ) ) {
144+ state . lastHeartbeat = Date . now ( ) ;
145+ attributes . actionSendHeartbeat = true ;
146+ messages . push ( { _tag : "heartbeat" } ) ;
147+ }
148+
149+ if ( shouldRefreshFinalized ( state ) ) {
150+ attributes . actionRefreshFinalized = true ;
151+ const finalizedInfo = await state . config . fetchCursor ( {
152+ blockTag : "finalized" ,
153+ } ) ;
154+
155+ if ( finalizedInfo === null ) {
156+ throw new Error ( "Failed to fetch finalized cursor" ) ;
157+ }
158+
159+ const finalized = blockInfoToCursor ( finalizedInfo ) ;
160+ const finalizedChanged =
161+ state . chainTracker . updateFinalized ( finalizedInfo ) ;
162+
163+ if (
164+ finalizedChanged &&
165+ state . cursor . orderKey > finalized . orderKey
166+ ) {
167+ messages . push ( {
168+ _tag : "finalize" ,
169+ finalize : { cursor : finalized } ,
170+ } ) ;
171+ }
172+
173+ state . lastFinalizedRefresh = Date . now ( ) ;
174+ }
175+
176+ const finalized = chainTracker . finalized ( ) ;
177+
178+ if ( cursor . orderKey < finalized . orderKey ) {
179+ attributes . actionBackfillFinalized = true ;
180+ for await ( const msg of backfillFinalizedBlocks ( state ) ) {
181+ messages . push ( msg ) ;
182+ }
183+ } else {
184+ if ( isAtHead ( state ) ) {
185+ attributes . actionWaitForHeadChange = true ;
186+ for await ( const msg of waitForHeadChange ( state ) ) {
187+ messages . push ( msg ) ;
188+ }
189+ } else {
190+ attributes . actionProduceLiveBlocks = true ;
191+ for await ( const msg of produceLiveBlocks ( state ) ) {
192+ messages . push ( msg ) ;
193+ }
194+ }
195+ }
196+
197+ span . setAttributes ( attributes ) ;
198+
199+ return messages ;
200+ } catch ( error ) {
201+ span . recordException ( error as Error ) ;
202+ throw error ;
203+ } finally {
204+ span . end ( ) ;
205+ }
206+ } ,
207+ ) ;
155208
156- if ( cursor . orderKey < finalized . orderKey ) {
157- yield * backfillFinalizedBlocks ( state ) ;
158- } else {
159- // If we're at the head, wait for a change.
160- //
161- // We don't want to produce a block immediately, but re-run the loop so
162- // that it's like any other iteration.
163- if ( isAtHead ( state ) ) {
164- yield * waitForHeadChange ( state ) ;
165- } else {
166- yield * produceLiveBlocks ( state ) ;
167- }
209+ for ( const message of messages ) {
210+ yield message ;
168211 }
169212 }
170213}
0 commit comments