Repository navigation
Session close time is taken from the indexer unchecked; a failed read leaves the session "Trading" forever #3
Description
Summary
The web app takes a session's close time (openingTime) from the indexer and never checks it on chain. If the indexer fails to read it once, it stores null for good, and the session then shows "Trading" forever. Predict stays enabled, and the app's own close checks (assertOpen, the swap deadline cap) are skipped.
Liquidity is withdrawn once a session closes, so in the normal case a run on such a session finds no route and stops before anything is signed. That makes this mainly a wrong-status bug, not a loss of funds. See Impact for what is left.
How it happens
- The indexer reads the close time once, when it handles
ParentMarketDeployed. If any of the four reads fails, it logsOpening time failed for <market>and returnsnull(fetchOpeningTime.ts#L44-L47). The read fails if both public Gnosis RPCs fail, or if the node is behind the block that created the market. - The effect is declared
cache: true(L11), and the error path does not opt out of the cache. Thenullis kept, so a re-sync does not fix it. verifySessionreads every other signing field from chain, but passesopeningTimethrough from the indexer row (...row, verifySession.ts#L94-L95).- With
closesAtnull:sessionStatusreturns"trading"(toSessionDetail.ts#L58-L61).assertOpenreturns at once (driver.ts#L415-L417).- The swap deadline is not capped at the close (lens.ts#L70-L74).
- The session falls out of both the "open" and "closed" filters and appears only under "all" (listing.ts#L69-L70).
The same applies if an indexer serves a close time later than the real one.
Impact
- Expected case (liquidity already withdrawn): the session page shows "Trading" after the real close. Predict then fails with "No route was found…" instead of saying the session is closed.
- If the pools still have liquidity when a run starts after the close, for example in the gap before the withdrawal or if someone else has added liquidity, the app quotes and asks the user to sign trades after the close. The Predict flow moves each pool toward the user's estimate, so if the answer is already known, the user buys at a price above the token's real value. Funds stay in the user's own trade wallet, but the trades can lose value.
Trading the pools directly is possible for anyone at any time. The issue is only that the app tells its users the session is still open.
Suggested fix
Minimal fix, web only: in verifySession, read the close time from chain the same way the indexer does (questionsIds()[0], then realityProxy(), then realitio(), then getOpeningTS(id)), and return it in place of row.openingTime. If the read fails, show the existing "could not be verified" error. The status, assertOpen and the deadline cap then work unchanged. That is four extra cached reads per session page.
Follow-up for the indexer: don't cache a failed read (for example, set context.cache = false in the catch, so the next run retries). That fixes the listing filters as well.
How often it happens in production
This can be checked against the production indexer:
Session(where: {openingTime: {_is_null: true}})- indexer logs containing
Opening time failed for
Found in the review of #1. The verifiers confirmed the path against the code.
Activity