# Subscription Matching Debug Plan ## Problem The relay is not matching kind 1059 (NIP-17 gift wrap) events to subscriptions, even though a subscription exists with `kinds:[1059]` filter. The log shows: ``` Event broadcast complete: 0 subscriptions matched ``` But we have this subscription: ``` sub:3 146.70.187.119 0x78edc9b43210 8m 27s kinds:[1059], since:10/23/2025, 4:27:59 PM, limit:50 ``` ## Investigation Strategy ### 1. Add Debug Output to `event_matches_filter()` (lines 386-564) Add debug logging at each filter check to trace the matching logic: - **Entry point**: Log the event kind and filter being tested - **Kinds filter check** (lines 392-415): Log whether kinds filter exists, the event kind value, and each filter kind being compared - **Authors filter check** (lines 417-442): Log if authors filter exists and matching results - **IDs filter check** (lines 444-469): Log if IDs filter exists and matching results - **Since filter check** (lines 471-482): Log the event timestamp vs filter since value - **Until filter check** (lines 484-495): Log the event timestamp vs filter until value - **Tag filters check** (lines 497-561): Log tag filter matching details - **Exit point**: Log whether the overall filter matched ### 2. Add Debug Output to `event_matches_subscription()` (lines 567-581) Add logging to show: - How many filters are in the subscription - Which filter (if any) matched - Overall subscription match result ### 3. Add Debug Output to `broadcast_event_to_subscriptions()` (lines 584-726) Add logging to show: - The event being broadcast (kind, id, created_at) - Total number of active subscriptions being checked - How many subscriptions matched after the first pass ### 4. Key Areas to Focus On Based on the code analysis, the most likely issues are: 1. **Kind matching logic** (lines 392-415): The event kind might not be extracted correctly, or the comparison might be failing 2. **Since timestamp** (lines 471-482): The subscription has a `since` filter - if the event timestamp is before this, it won't match 3. **Event structure**: The event JSON might not have the expected structure ### 5. Specific Debug Additions #### In `event_matches_filter()` at line 386: ```c // Add at start of function cJSON* event_kind_obj = cJSON_GetObjectItem(event, "kind"); cJSON* event_id_obj = cJSON_GetObjectItem(event, "id"); cJSON* event_created_at_obj = cJSON_GetObjectItem(event, "created_at"); DEBUG_TRACE("FILTER_MATCH: Testing event kind=%d id=%.8s created_at=%ld", event_kind_obj ? (int)cJSON_GetNumberValue(event_kind_obj) : -1, event_id_obj && cJSON_IsString(event_id_obj) ? cJSON_GetStringValue(event_id_obj) : "null", event_created_at_obj ? (long)cJSON_GetNumberValue(event_created_at_obj) : 0); ``` #### In kinds filter check (after line 392): ```c if (filter->kinds && cJSON_IsArray(filter->kinds)) { DEBUG_TRACE("FILTER_MATCH: Checking kinds filter with %d kinds", cJSON_GetArraySize(filter->kinds)); cJSON* event_kind = cJSON_GetObjectItem(event, "kind"); if (!event_kind || !cJSON_IsNumber(event_kind)) { DEBUG_WARN("FILTER_MATCH: Event has no valid kind field"); return 0; } int event_kind_val = (int)cJSON_GetNumberValue(event_kind); DEBUG_TRACE("FILTER_MATCH: Event kind=%d", event_kind_val); int kind_match = 0; cJSON* kind_item = NULL; cJSON_ArrayForEach(kind_item, filter->kinds) { if (cJSON_IsNumber(kind_item)) { int filter_kind = (int)cJSON_GetNumberValue(kind_item); DEBUG_TRACE("FILTER_MATCH: Comparing event kind %d with filter kind %d", event_kind_val, filter_kind); if (filter_kind == event_kind_val) { kind_match = 1; DEBUG_TRACE("FILTER_MATCH: Kind matched!"); break; } } } if (!kind_match) { DEBUG_TRACE("FILTER_MATCH: No kind match, filter rejected"); return 0; } DEBUG_TRACE("FILTER_MATCH: Kinds filter passed"); } ``` #### In since filter check (after line 472): ```c if (filter->since > 0) { cJSON* event_created_at = cJSON_GetObjectItem(event, "created_at"); if (!event_created_at || !cJSON_IsNumber(event_created_at)) { DEBUG_WARN("FILTER_MATCH: Event has no valid created_at field"); return 0; } long event_timestamp = (long)cJSON_GetNumberValue(event_created_at); DEBUG_TRACE("FILTER_MATCH: Checking since filter: event_ts=%ld filter_since=%ld", event_timestamp, filter->since); if (event_timestamp < filter->since) { DEBUG_TRACE("FILTER_MATCH: Event too old (before since), filter rejected"); return 0; } DEBUG_TRACE("FILTER_MATCH: Since filter passed"); } ``` #### At end of `event_matches_filter()` (before line 563): ```c DEBUG_TRACE("FILTER_MATCH: All filters passed, event matches!"); return 1; // All filters passed ``` #### In `event_matches_subscription()` at line 567: ```c int event_matches_subscription(cJSON* event, subscription_t* subscription) { if (!event || !subscription || !subscription->filters) { return 0; } DEBUG_TRACE("SUB_MATCH: Testing subscription '%s'", subscription->id); int filter_num = 0; subscription_filter_t* filter = subscription->filters; while (filter) { filter_num++; DEBUG_TRACE("SUB_MATCH: Testing filter #%d", filter_num); if (event_matches_filter(event, filter)) { DEBUG_TRACE("SUB_MATCH: Filter #%d matched! Subscription '%s' matches", filter_num, subscription->id); return 1; // Match found (OR logic) } filter = filter->next; } DEBUG_TRACE("SUB_MATCH: No filters matched for subscription '%s'", subscription->id); return 0; // No filters matched } ``` #### In `broadcast_event_to_subscriptions()` at line 584: ```c int broadcast_event_to_subscriptions(cJSON* event) { if (!event) { return 0; } // Log event details cJSON* event_kind = cJSON_GetObjectItem(event, "kind"); cJSON* event_id = cJSON_GetObjectItem(event, "id"); cJSON* event_created_at = cJSON_GetObjectItem(event, "created_at"); DEBUG_TRACE("BROADCAST: Event kind=%d id=%.8s created_at=%ld", event_kind ? (int)cJSON_GetNumberValue(event_kind) : -1, event_id && cJSON_IsString(event_id) ? cJSON_GetStringValue(event_id) : "null", event_created_at ? (long)cJSON_GetNumberValue(event_created_at) : 0); // ... existing expiration check code ... // After line 611 (before pthread_mutex_lock): pthread_mutex_lock(&g_subscription_manager.subscriptions_lock); int total_subs = 0; subscription_t* count_sub = g_subscription_manager.active_subscriptions; while (count_sub) { total_subs++; count_sub = count_sub->next; } DEBUG_TRACE("BROADCAST: Checking %d active subscriptions", total_subs); subscription_t* sub = g_subscription_manager.active_subscriptions; // ... rest of matching logic ... ``` ## Expected Outcome With these debug additions, we should see output like: ``` BROADCAST: Event kind=1059 id=abc12345 created_at=1729712279 BROADCAST: Checking 1 active subscriptions SUB_MATCH: Testing subscription 'sub:3' SUB_MATCH: Testing filter #1 FILTER_MATCH: Testing event kind=1059 id=abc12345 created_at=1729712279 FILTER_MATCH: Checking kinds filter with 1 kinds FILTER_MATCH: Event kind=1059 FILTER_MATCH: Comparing event kind 1059 with filter kind 1059 FILTER_MATCH: Kind matched! FILTER_MATCH: Kinds filter passed FILTER_MATCH: Checking since filter: event_ts=1729712279 filter_since=1729708079 FILTER_MATCH: Since filter passed FILTER_MATCH: All filters passed, event matches! SUB_MATCH: Filter #1 matched! Subscription 'sub:3' matches Event broadcast complete: 1 subscriptions matched ``` This will help us identify exactly where the matching is failing.