@@ -242,6 +242,62 @@ async fn batch_insert_produced_blocks(pool: &SqlitePool, blocks: &[ProducedBlock
242
242
Ok ( ( ) )
243
243
}
244
244
245
+ /// Marks heartbeat presence entries as outdated (disabled) based on global slot comparisons.
246
+ ///
247
+ /// This function performs the following steps:
248
+ /// 1. Finds the maximum global slot for each window (considering only non-disabled entries).
249
+ /// 2. Identifies the previous window's maximum global slot for each window.
250
+ /// 3. Marks a presence entry as disabled if its global slot is less than:
251
+ /// - The maximum global slot of the previous window (if it exists).
252
+ ///
253
+ /// This approach allows for a full window of tolerance in synchronization:
254
+ /// - Entries matching or exceeding the previous window's max slot are considered up-to-date.
255
+ /// - This allows for slight delays in propagation between windows.
256
+ ///
257
+ /// Note: The first window in the sequence will not have any entries marked as disabled,
258
+ /// as there is no previous window to compare against.
259
+ ///
260
+ /// Returns the number of presence entries marked as disabled.
261
+ async fn mark_outdated_presence ( pool : & SqlitePool ) -> Result < usize > {
262
+ let affected = sqlx:: query!(
263
+ r#"
264
+ WITH MaxSlots AS (
265
+ SELECT
266
+ window_id,
267
+ MAX(best_tip_global_slot) as max_slot
268
+ FROM heartbeat_presence
269
+ WHERE disabled = FALSE
270
+ GROUP BY window_id
271
+ ),
272
+ PrevMaxSlots AS (
273
+ -- Get the max slot from the immediate previous window
274
+ SELECT
275
+ tw.id as window_id,
276
+ prev.max_slot as prev_max_slot
277
+ FROM time_windows tw
278
+ LEFT JOIN time_windows prev_tw ON prev_tw.id = tw.id - 1
279
+ LEFT JOIN MaxSlots prev ON prev.window_id = prev_tw.id
280
+ )
281
+ UPDATE heartbeat_presence
282
+ SET disabled = TRUE
283
+ WHERE (window_id, best_tip_global_slot) IN (
284
+ SELECT
285
+ hp.window_id,
286
+ hp.best_tip_global_slot
287
+ FROM heartbeat_presence hp
288
+ JOIN PrevMaxSlots pms ON pms.window_id = hp.window_id
289
+ WHERE hp.disabled = FALSE
290
+ AND pms.prev_max_slot IS NOT NULL -- Ensure there is a previous window
291
+ AND hp.best_tip_global_slot < pms.prev_max_slot -- Less than previous window max
292
+ )
293
+ "#
294
+ )
295
+ . execute ( pool)
296
+ . await ?;
297
+
298
+ Ok ( affected. rows_affected ( ) as usize )
299
+ }
300
+
245
301
pub async fn process_heartbeats (
246
302
db : & FirestoreDb ,
247
303
pool : & SqlitePool ,
@@ -449,6 +505,15 @@ pub async fn process_heartbeats(
449
505
450
506
if latest_time > last_processed_time {
451
507
update_last_processed_time ( pool, latest_time) . await ?;
508
+
509
+ // Mark outdated presence entries as disabled
510
+ let disabled_count = mark_outdated_presence ( pool) . await ?;
511
+ if disabled_count > 0 {
512
+ println ! (
513
+ "Marked {} outdated presence entries as disabled" ,
514
+ disabled_count
515
+ ) ;
516
+ }
452
517
}
453
518
454
519
Ok ( total_heartbeats)
@@ -537,16 +602,17 @@ pub async fn update_scores(pool: &SqlitePool, config: &Config) -> Result<()> {
537
602
GROUP BY public_key_id
538
603
),
539
604
HeartbeatCounts AS (
540
- -- Count heartbeats only within valid windows
605
+ -- Count heartbeats only within valid windows and not disabled
541
606
SELECT
542
607
hp.public_key_id,
543
608
COUNT(DISTINCT hp.window_id) as heartbeats
544
609
FROM heartbeat_presence hp
545
610
JOIN ValidWindows vw ON vw.id = hp.window_id
611
+ WHERE hp.disabled = FALSE
546
612
GROUP BY hp.public_key_id
547
613
),
548
614
LastHeartbeats AS (
549
- -- Get last heartbeat time across all windows
615
+ -- Get last heartbeat time across all windows, including disabled entries
550
616
SELECT
551
617
public_key_id,
552
618
MAX(heartbeat_time) as last_heartbeat
0 commit comments