Skip to content

Cold-seed the pool-indexer from on-chain events - #4733

Open
AryanGodara wants to merge 3 commits into
mainfrom
aryan/be-36-pool-indexer-onchain-seed
Open

Cold-seed the pool-indexer from on-chain events#4733
AryanGodara wants to merge 3 commits into
mainfrom
aryan/be-36-pool-indexer-onchain-seed

Conversation

@AryanGodara

Copy link
Copy Markdown
Member

Description

The pool-indexer used to cold-seeded using pre-existing subgraphs. This makes seeding subgraph-free: it replays the factory's on-chain event history from its deploy block instead, removing the last external-subgraph dependency for V3 indexing.
This'll be a no-op for already running indexers. So no risk of long running bootstrap on big chains like mainnet.
Note: But it will def take time to sync up if we add a new uni-v3 factory sometime in future, but that's expected; and only takes considerable time on mainnet.

Changes

  • Remove the subgraph seeder: subgraph_seeder.rs, the batch_seed_ticks DB helper, and the module declaration.
  • FactoryConfig takes deploy_block (replacing subgraph_url / seed_block); bootstrap_factory cold-seeds by replaying events from deploy_block - 1, and skips if a checkpoint already exists (so its idempotent).
  • Add a per-call timeout + bounded retry to the eth_getLogs bisection so a transport timeout during catch-up retries instead of crashing the seed.

How to test

  • run the unit and e2e tests for pool indexer

@AryanGodara AryanGodara self-assigned this Aug 12, 2026
@linear-code

linear-code Bot commented Aug 12, 2026

Copy link
Copy Markdown

BE-36

@AryanGodara
AryanGodara marked this pull request as ready for review August 12, 2026 18:54
@AryanGodara
AryanGodara requested a review from a team as a code owner August 12, 2026 18:54
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @AryanGodara's task in 4m 37s —— View job


PR Review: Cold-seed the pool-indexer from on-chain events

  • Read PR diff and changed files
  • Review config, indexer, run, db changes
  • Review e2e test changes
  • Post inline findings + summary

The core change is clean: replaying Mint/Burn from deploy_block and accumulating via batch_update_ticks (which does liquidity_net + EXCLUDED.liquidity_net) reconstructs the same net liquidity the subgraph used to hand over, so dropping batch_seed_ticks/delete_ticks_for_factory is correct. The getLogs timeout+retry (transient retried, range-size still bisected) is a sensible hardening. Nice removal of an external dependency.

One substantive concern posted inline, plus doc staleness below.

Inline

  • crates/pool-indexer/src/run.rs — an interrupted cold-seed leaves a partial checkpoint far behind the head, which bootstrap_factory then treats as a completed bootstrap on re-run. The interruption window grows from a short near-tip catch-up to the entire chain history, so this is worth guarding.

Docs now stale (not inline — outside the diff)

  • crates/pool-indexer/README.md still describes the removed subgraph seeder throughout: "seeds its DB from a subgraph at a fixed block" (L7–10), "initial subgraph seed" (L16), and "never touching the subgraph" (L25–26). Worth updating in this PR since the whole seeding model changed. Fix this →
  • crates/e2e/tests/e2e/pool_indexer.rs L1–4 module doc says "the subgraph_seeder bootstrap is skipped ... only the live-indexing and HTTP-serving paths are exercised" — inaccurate now that subgraph_seeder is gone and the new onchain_cold_seed test exercises the cold-seed path.

Minor (non-blocking)

  • In bisecting_get_logs_with_depth, non-range RPC errors (Ok(Err(err)) at indexer/uniswap_v3.rs:723) are retried up to MAX_GETLOGS_RETRIES even when permanent (e.g. bad params / method-not-found), costing ~4 min of backoff before failing. Acceptable, but a permanent-error fast-path would fail faster.

@AryanGodara
AryanGodara marked this pull request as draft August 12, 2026 18:55
Comment thread crates/pool-indexer/src/run.rs Outdated
@AryanGodara
AryanGodara force-pushed the aryan/be-36-pool-indexer-onchain-seed branch from 789efaa to d2b29a8 Compare August 12, 2026 19:36
@AryanGodara
AryanGodara marked this pull request as ready for review August 12, 2026 20:04
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @AryanGodara's task in 2m 22s —— View job


PR Review: Cold-seed the pool-indexer from on-chain events

  • Read PR diff and changed files
  • Review config, indexer, run, db changes
  • Review e2e test changes
  • Post inline findings + summary

Removing the subgraph dependency is a clean win. The math checks out: replaying Mint/Burn from deploy_block and accumulating via batch_update_ticks (liquidity_net + EXCLUDED.liquidity_net) reconstructs the same net liquidity the subgraph used to hand over, so dropping batch_seed_ticks/delete_ticks_for_factory is safe. The eth_getLogs timeout+bounded-retry (transient retried, range-size still bisected) is sensible hardening, and the new onchain_cold_seed e2e test covers the discovery + state-rebuild path.

One substantive concern (posted inline on run.rs:259): an interrupted cold-seed leaves a partial checkpoint that bootstrap_factory then treats as a completed bootstrap on re-run — and cold-seeding from deploy_block widens that window from a short near-tip catch-up to the entire chain history. Worth gating the skip on checkpoint < finalized rather than mere presence.

Docs left stale by this PR (lines aren't in the diff, so noting here rather than inline):

  • crates/pool-indexer/README.md:7-10,16,25-26 still describes the removed subgraph seeder ("seeds its DB from a subgraph at a fixed block", "initial subgraph seed", "never touching the subgraph"). Fix →
  • crates/e2e/tests/e2e/pool_indexer.rs:1-4 module doc still says "the subgraph_seeder bootstrap is skipped ... only the live-indexing and HTTP-serving paths are exercised" — inaccurate now that subgraph_seeder is gone and onchain_cold_seed exercises the cold-seed path.

Minor (non-blocking): in bisecting_get_logs_with_depth, non-range RPC errors (indexer/uniswap_v3.rs:723, the Ok(Err(err)) arm) are retried up to MAX_GETLOGS_RETRIES even when permanent (bad params / method-not-found), costing ~40s+ of backoff before failing. A permanent-error fast-path would fail faster, but this is acceptable.

Comment thread crates/pool-indexer/src/run.rs Outdated
@AryanGodara
AryanGodara force-pushed the aryan/be-36-pool-indexer-onchain-seed branch from ac39172 to 8fb7b49 Compare August 12, 2026 20:53

@MartinquaXD MartinquaXD left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused by the motivation of this PR.

Comment on lines -105 to -109
/// Subgraph GraphQL endpoint for the initial seed.
#[serde(deserialize_with = "configs::deserialize_env::deserialize_url_from_env")]
pub subgraph_url: Url,
/// Block to seed at. Defaults to the subgraph's current block.
pub seed_block: Option<u64>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want to get rid of subgraph seeding entirely? Ideally we'd be able to make use of a public subgraph if one is available instead of re-syncing the entire chain ourselves, no?
IMO it would make sense to introduce a seeding enum like:

enum SeedingMode {
    NoSeeding,
    Subgraph: {
        url: Url,
        start_block: u64.
    },
    Reindex: {
        start_block: u64.
    }
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely useful for syncing new chains, but realistically any new chain we add has very low support for subgraphs. We weren't able to find public subgraphs for the last 2 chains iirc.

So anyways it boiled down to 1. set up a subgraph (1-2 days to sync) 2. Use it to seed the pool-indexer.
Faster to seed the pool indexer directly from RPC, and we don't have to maintain a separate subscription for it.

It's def possible/straightforward to keep the subgraph sync, but I was going with the approach to explicitly remove that dependency altogether.

This also frees up #4735 so we can sync from multiple factories. Rn, we had issues where we couldn't find public subgraphs on bnb, and used an alternate to official uniswap v3 deployment, on that chain. So I feel it'll be a step up. Wdyt 👀

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

realistically any new chain we add has very low support for subgraphs
We weren't able to find public subgraphs for the last 2 chains iirc.

Yeah, makes sense.

@MartinquaXD MartinquaXD left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@jmg-duarte jmg-duarte left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall lgtm 🧹🧹

Comment thread crates/pool-indexer/src/run.rs Outdated
Comment on lines +38 to +41
/// Idempotent: a factory already at the head is a fast no-op and an interrupted
/// seed resumes from its checkpoint (see [`bootstrap_factory`]). On return
/// every factory is indexed to the finalized head, so a later `run` flips
/// `/startup` ready promptly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't what idempotent means

idempotent means that you can run an operation twice and the second one doesn't change anything

so a later run flips /startup ready promptly.

This alone breaks idempotency, the whole point is that you can call op -> op -> op and the resulting state is the same as just calling op

For example, if you .insert(k, v) into a (python-like! i can already see someone complain that some maps have adjacency lists) map, multiple inserts with the same values won't change anything

m = {}
m.insert(1, 2) // {1: 2}
m.insert(1, 2) // {1: 2}
m.insert(1, 2) // {1: 2}
m.insert(1, 2) // {1: 2}
// state didn't change

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true, incorrect use of the term.
I've removed it now. I mainly just wanted to convey, this won't make us re-sync any pre-running indexer / repeated already done ops.

Fixed the wording 🫡

}
attempt += 1;
tracing::warn!(%err, attempt, from, to, "get_logs failed, retrying");
tokio::time::sleep(GETLOGS_RETRY_BACKOFF * attempt).await;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we keep re-building retries and if im not mistaken each one of us has a slight different style of writing them, i wonder when we should just write (or adopt ideally) a retry library to make it uniform

its not that it saves plenty code, more that it makes all retries uniform and easier to review

cc @squadgazzz @MartinquaXD for you guys' take on this

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a separate PR, but I agree too. Seen a lot of variants/instances of retries across diff PRs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that can be easily generalized, then sure, we should use a common approach.

Comment thread crates/pool-indexer/src/indexer/uniswap_v3.rs Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants