Labels: Official Campaign | FWC26 GrantFox OSS Maybe Rewarded backend indexer performance bug
This is a backend issue for the GrantFox FWC26 campaign. Index event participants as columns and query them directly instead of scanning serialized JSON text.
Requirements and Context
backend/src/services/eventIndexer.js — queryEventsByType (and queryEventsByPublicKey, getEventStats):
let where = `payload::text ILIKE $1 AND event_type = $2`;
const params = [`%${publicKey}%`, eventType];
payload::text ILIKE '%GABC…%' is a full-text substring scan over the entire contract_events table. The table already stores from_addr and to_addr columns, but they are only populated on insert and never used for queries.
ILIKE '%<publicKey>%' can match unrelated text: the public key GAAAA… (55 chars) appears inside other fields (e.g. a memo containing the string, or a nested payload array), producing wrong results — a user can see events they were not part of, and events are missed if the key appears in a field the query wasn't meant to match.
getEventStats has the same payload::text ILIKE scan, grouping on event_type — the most common dashboard query, and it's a full table scan on every call.
Objectives
- Rewrite queries to use
from_addr = $1 OR to_addr = $2 (exact match) with an index on those columns (add a knex migration if missing).
- Keep
payload matching only as an explicit opt-in (e.g. a search query param), and bound it.
- Add tests: exact-participant filtering returns only the user's events; a public key string appearing in a memo does not match.
Suggested Execution
- Fork and branch:
git checkout -b fix/indexer-column-queries.
- Patch
eventIndexer.js query helpers; add a migration for the index.
- Add tests; run
npm run lint && npm test in backend/.
Acceptance Criteria
Guidelines
- Keep the in-memory fallback store in sync (it can keep the substring filter, documented as best-effort).
Timeframe: 48 hours
Labels:
Official Campaign | FWC26GrantFox OSSMaybe RewardedbackendindexerperformancebugRequirements and Context
backend/src/services/eventIndexer.js—queryEventsByType(andqueryEventsByPublicKey,getEventStats):payload::text ILIKE '%GABC…%'is a full-text substring scan over the entirecontract_eventstable. The table already storesfrom_addrandto_addrcolumns, but they are only populated on insert and never used for queries.ILIKE '%<publicKey>%'can match unrelated text: the public keyGAAAA…(55 chars) appears inside other fields (e.g. a memo containing the string, or a nestedpayloadarray), producing wrong results — a user can see events they were not part of, and events are missed if the key appears in a field the query wasn't meant to match.getEventStatshas the samepayload::text ILIKEscan, grouping onevent_type— the most common dashboard query, and it's a full table scan on every call.Objectives
from_addr = $1 OR to_addr = $2(exact match) with an index on those columns (add a knex migration if missing).payloadmatching only as an explicit opt-in (e.g. asearchquery param), and bound it.Suggested Execution
git checkout -b fix/indexer-column-queries.eventIndexer.jsquery helpers; add a migration for the index.npm run lint && npm testinbackend/.Acceptance Criteria
ILIKEon JSON.npm run lintclean.Guidelines
Timeframe: 48 hours