upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-12-19 17:18:50 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-12-19 17:18:50 +0000
commitd3c5cf71620d7a767506889239fe3747be4d3876 (patch)
tree661102263b1ed526f4a026e550d1426a6195644a /docs
parent64a9df3046e21d896f143c2fbbbefabdf86048f9 (diff)
docs: sync add pagination updates
Diffstat (limited to 'docs')
-rw-r--r--docs/explanation/grasp-02-proactive-sync.md106
1 files changed, 85 insertions, 21 deletions
diff --git a/docs/explanation/grasp-02-proactive-sync.md b/docs/explanation/grasp-02-proactive-sync.md
index 0607610..64fa096 100644
--- a/docs/explanation/grasp-02-proactive-sync.md
+++ b/docs/explanation/grasp-02-proactive-sync.md
@@ -106,16 +106,30 @@ pub enum SyncMethod {
106/// Key: relay URL 106/// Key: relay URL
107pub type PendingSyncIndex = Arc<RwLock<HashMap<String, Vec<PendingBatch>>>>; 107pub type PendingSyncIndex = Arc<RwLock<HashMap<String, Vec<PendingBatch>>>>;
108 108
109/// Pagination state for a subscription in non-Negentropy historic sync
110#[derive(Debug, Clone)]
111pub struct PaginationState {
112 /// Number of events received for this subscription
113 pub event_count: usize,
114 /// Smallest created_at timestamp seen (for pagination with `until`)
115 pub min_created_at: Option<Timestamp>,
116 /// Original filter to reconstruct for next page
117 pub original_filter: Filter,
118}
119
109pub struct PendingBatch { 120pub struct PendingBatch {
110 /// Unique ID for this batch - for debugging/logging 121 /// Unique ID for this batch - for debugging/logging
111 pub batch_id: u64, 122 pub batch_id: u64,
112 /// The items this batch is syncing 123 /// The items this batch is syncing
113 pub items: PendingItems, 124 pub items: PendingItems,
114 /// Subscription IDs that must ALL receive EOSE before confirming (for ReqEose) 125 /// Subscription IDs that must ALL receive EOSE before confirming (for ReqEose)
115 /// Empty for Negentropy sync method until missing event ids identified 126 /// Empty for Negentropy sync method
116 pub outstanding_subs: HashSet<SubscriptionId>, 127 pub outstanding_subs: HashSet<SubscriptionId>,
117 /// The sync method used for this batch 128 /// The sync method used for this batch
118 pub sync_method: SyncMethod, 129 pub sync_method: SyncMethod,
130 /// Pagination tracking for REQ+EOSE subscriptions (empty for Negentropy)
131 /// Maps subscription ID to its pagination state
132 pub pagination_state: HashMap<SubscriptionId, PaginationState>,
119} 133}
120 134
121#[derive(Debug, Clone, Default)] 135#[derive(Debug, Clone, Default)]
@@ -125,6 +139,17 @@ pub struct PendingItems {
125} 139}
126``` 140```
127 141
142**Pagination for REQ+EOSE Historic Sync:**
143
144When a relay doesn't support NIP-77 Negentropy, historic sync falls back to traditional REQ+EOSE. To handle large result sets efficiently:
145
146- **`PaginationState`** tracks per-subscription pagination progress
147 - `event_count`: Number of events received so far
148 - `min_created_at`: Smallest timestamp seen, used to set `until` for next page
149 - `original_filter`: Base filter to reconstruct with updated `until` parameter
150- **Automatic pagination**: When EOSE is received, if enough events were received to suggest more may exist, the system automatically issues a follow-up request with `until` set to `min_created_at`
151- **Completion**: Pagination continues until an EOSE is received with fewer events than expected, indicating the end of results
152
128--- 153---
129 154
130## Connection Lifecycle 155## Connection Lifecycle
@@ -155,13 +180,13 @@ stateDiagram-v2
155 180
156### Connection Flow Methods 181### Connection Flow Methods
157 182
158| Method | Purpose | When Called | Actions | 183| Method | Purpose | When Called | Actions |
159|--------|---------|-------------|---------| 184| ------------------------------- | ------------------------- | ------------------------------- | --------------------------------------------------------------- |
160| `register_relay()` | Initialize relay tracking | Discovery via RepoSyncIndex | Creates RelayConnection, stores in HashMap, returns immediately | 185| `register_relay()` | Initialize relay tracking | Discovery via RepoSyncIndex | Creates RelayConnection, stores in HashMap, returns immediately |
161| `try_connect_relay()` | Attempt connection | Periodic retry (500ms) | Calls connect_and_subscribe, sends notification on success | 186| `try_connect_relay()` | Attempt connection | Periodic retry (500ms) | Calls connect_and_subscribe, sends notification on success |
162| `handle_connect_or_reconnect()` | Setup after connection | ConnectNotification received | Spawns event loop, updates state, decides sync strategy | 187| `handle_connect_or_reconnect()` | Setup after connection | ConnectNotification received | Spawns event loop, updates state, decides sync strategy |
163| `handle_disconnect()` | Cleanup after disconnect | DisconnectNotification received | Updates state, clears pending, KEEPS RelayConnection | 188| `handle_disconnect()` | Cleanup after disconnect | DisconnectNotification received | Updates state, clears pending, KEEPS RelayConnection |
164| `retry_disconnected_relays()` | Periodic reconnection | Every 500ms | For each ready relay: try_connect_relay() | 189| `retry_disconnected_relays()` | Periodic reconnection | Every 500ms | For each ready relay: try_connect_relay() |
165 190
166### Event Loop Lifecycle 191### Event Loop Lifecycle
167 192
@@ -179,6 +204,7 @@ flowchart LR
179``` 204```
180 205
181**Why respawn is required**: 206**Why respawn is required**:
207
182- `run_event_loop()` breaks on RelayStatus::Disconnected 208- `run_event_loop()` breaks on RelayStatus::Disconnected
183- The spawned task completely exits 209- The spawned task completely exits
184- Cannot resume terminated task - must spawn fresh 210- Cannot resume terminated task - must spawn fresh
@@ -477,22 +503,11 @@ async fn historic_sync(
477 503
478Dispatches to: 504Dispatches to:
479 505
480- `historic_sync_negentropy()` - NIP-77 parallel sync (if supported) 506- `historic_sync_negentropy()` - NIP-77 parallel sync (if supported) - no pagination needed
481- `historic_sync_legacy()` - REQ+EOSE fallback 507- `historic_sync_legacy()` - REQ+EOSE fallback with automatic pagination for large result sets
482 508
483### Building Blocks 509### Building Blocks
484 510
485#### `reconstruct_filters()` - Rebuild from Confirmed State
486
487```rust
488/// Reconstruct filters from RelaySyncIndex (confirmed state ONLY)
489///
490/// Returns raw Vec<Filter> for L1+L2+L3.
491/// Used by: quick_reconnect, consolidate
492/// Does NOT include pending items - those flow through AddFilters path.
493async fn reconstruct_filters(&self, relay_url: &str) -> Vec<Filter>
494```
495
496#### `sync_computed_filters()` - Handle New AddFilters 511#### `sync_computed_filters()` - Handle New AddFilters
497 512
498```rust 513```rust
@@ -695,6 +710,55 @@ If negentropy fails (relay doesn't support NIP-77, network error, etc.):
695 710
696--- 711---
697 712
713## REQ+EOSE Pagination
714
715When a relay doesn't support NIP-77 Negentropy, historic sync uses traditional REQ+EOSE with automatic pagination to handle large result sets efficiently.
716
717### How Pagination Works
718
7191. **Initial Request**: Send REQ with filters (may include `since` parameter)
7202. **Track Events**: As events arrive, [`PaginationState`](src/sync/mod.rs:165) tracks:
721 - `event_count`: Number of events received
722 - `min_created_at`: Smallest timestamp seen (oldest event)
723 - `original_filter`: Base filter for reconstruction
7243. **EOSE Detection**: When EOSE arrives, check if pagination is needed
7254. **Next Page**: If enough events were received (suggesting more exist):
726 - Create new filter with `until: min_created_at`
727 - Issue another REQ for events older than the oldest seen
728 - Reuse same subscription ID
7295. **Completion**: Repeat until EOSE arrives with fewer events, indicating end of results
730
731### Pagination State Lifecycle
732
733```mermaid
734flowchart TB
735 REQ[Send REQ with filters] --> TRACK[Initialize PaginationState]
736 TRACK --> EVENT[Receive EVENT]
737 EVENT --> UPDATE[Update event_count and min_created_at]
738 UPDATE --> MORE{More events?}
739 MORE --> |yes| EVENT
740 MORE --> |no| EOSE[Receive EOSE]
741 EOSE --> CHECK{event_count suggests more pages?}
742 CHECK --> |yes| NEXT[Create filter with until=min_created_at]
743 NEXT --> REQ2[Send next page REQ]
744 REQ2 --> RESET[Reset event_count, keep min_created_at]
745 RESET --> EVENT
746 CHECK --> |no| DONE[Batch complete, confirm items]
747```
748
749### Pagination vs Negentropy
750
751| Aspect | Negentropy Sync | REQ+EOSE Pagination |
752| ------------------- | ---------------------------- | --------------------------------------------------------- |
753| **Efficiency** | High (set reconciliation) | Lower (sequential pages) |
754| **Bandwidth** | Minimal (only missing items) | Higher (all matching events transferred) |
755| **Relay support** | Requires NIP-77 | Universal (standard Nostr) |
756| **State tracking** | None needed | [`PaginationState`](src/sync/mod.rs:165) per subscription |
757| **Completion time** | Typically faster | Slower for large sets |
758| **Use cases** | Full sync, large event sets | Fallback, small gaps with `since` |
759
760---
761
698## State Flow Summary 762## State Flow Summary
699 763
700```mermaid 764```mermaid