22 crate :: {
33 api:: AppState ,
44 arguments:: Arguments ,
5- config:: { Configuration , NetworkConfig } ,
5+ config:: { Configuration , FactoryConfig , NetworkConfig } ,
66 indexer:: uniswap_v3:: UniswapV3Indexer ,
77 } ,
88 alloy_provider:: Provider ,
@@ -21,8 +21,49 @@ pub async fn start(args: impl Iterator<Item = String>) {
2121 initialize_observability ( & args) ;
2222 observe:: metrics:: setup_registry ( None , None ) ;
2323 let config = Configuration :: from_path ( & args. config ) . expect ( "failed to load configuration" ) ;
24- tracing:: info!( "pool-indexer starting" ) ;
25- run ( config) . await ;
24+ if args. bootstrap_only {
25+ tracing:: info!( "pool-indexer bootstrap-only starting" ) ;
26+ bootstrap ( config) . await ;
27+ tracing:: info!( "pool-indexer bootstrap complete, exiting" ) ;
28+ } else {
29+ tracing:: info!( "pool-indexer starting" ) ;
30+ run ( config) . await ;
31+ }
32+ }
33+
34+ /// Runs the bootstrap phase (seed + catch-up to the finalized head) for every
35+ /// factory, then returns. Binds no HTTP ports — this is migration-style work
36+ /// meant to run as a K8s initContainer ahead of the serve container.
37+ ///
38+ /// Idempotent: each factory with an existing checkpoint is skipped (see
39+ /// [`bootstrap_factory`]), so re-running on an already-seeded DB is a fast
40+ /// no-op that never touches the subgraph. On return, a subsequent `run` finds
41+ /// the checkpoints present and flips `/startup` ready almost immediately.
42+ pub async fn bootstrap ( config : Configuration ) {
43+ let db = connect_db ( & config) . await ;
44+ let network = config. network ;
45+ let provider = build_provider_checked ( & network) . await ;
46+ let network = Arc :: new ( network) ;
47+
48+ // One factory per network (enforced in NetworkConfig::validate), so this
49+ // sequential loop is equivalent to the serve path's per-factory JoinSet.
50+ for factory in network. factories . iter ( ) . copied ( ) {
51+ let indexer = factory_indexer ( & provider, & db, & network, factory) ;
52+ bootstrap_factory ( & db, & indexer, & network, & factory) . await ;
53+ }
54+ }
55+
56+ fn factory_indexer (
57+ provider : & AlloyProvider ,
58+ db : & PgPool ,
59+ network : & NetworkConfig ,
60+ factory : FactoryConfig ,
61+ ) -> UniswapV3Indexer {
62+ UniswapV3Indexer :: new (
63+ provider. clone ( ) ,
64+ db. clone ( ) ,
65+ & network. indexer_config ( factory. address ) ,
66+ )
2667}
2768
2869pub async fn run ( config : Configuration ) {
@@ -125,31 +166,14 @@ async fn run_network_indexer(db: PgPool, network: NetworkConfig, barrier: Arc<St
125166 "starting network indexer" ,
126167 ) ;
127168
128- let provider = build_provider ( & network) ;
129-
130- // Catch misconfigured RPC-vs-network pairings (e.g. chain_id=1 pointed
131- // at an Arbitrum node) before we index the wrong chain into the DB.
132- let actual_chain_id = provider
133- . get_chain_id ( )
134- . await
135- . expect ( "failed to fetch chain_id from RPC" ) ;
136- assert_eq ! (
137- actual_chain_id, network. chain_id,
138- "chain_id mismatch for network {}: config says {}, RPC reports {}" ,
139- network. name, network. chain_id, actual_chain_id,
140- ) ;
141-
169+ let provider = build_provider_checked ( & network) . await ;
142170 let network = Arc :: new ( network) ;
143171
144172 // One task per factory. Provider + DB pool are shared; checkpoints are
145173 // per-factory because they're keyed by `contract_address`.
146174 let mut factory_set = JoinSet :: new ( ) ;
147175 for factory in network. factories . iter ( ) . copied ( ) {
148- let indexer = UniswapV3Indexer :: new (
149- provider. clone ( ) ,
150- db. clone ( ) ,
151- & network. indexer_config ( factory. address ) ,
152- ) ;
176+ let indexer = factory_indexer ( & provider, & db, & network, factory) ;
153177 factory_set. spawn ( run_factory_indexer (
154178 db. clone ( ) ,
155179 indexer,
@@ -255,6 +279,23 @@ fn build_provider(network: &NetworkConfig) -> AlloyProvider {
255279 . clone ( )
256280}
257281
282+ /// Builds the RPC provider and asserts the node's chain_id matches config.
283+ /// Catches misconfigured RPC-vs-network pairings (e.g. chain_id=1 pointed at
284+ /// an Arbitrum node) before we index the wrong chain into the DB.
285+ async fn build_provider_checked ( network : & NetworkConfig ) -> AlloyProvider {
286+ let provider = build_provider ( network) ;
287+ let actual_chain_id = provider
288+ . get_chain_id ( )
289+ . await
290+ . expect ( "failed to fetch chain_id from RPC" ) ;
291+ assert_eq ! (
292+ actual_chain_id, network. chain_id,
293+ "chain_id mismatch for network {}: config says {}, RPC reports {}" ,
294+ network. name, network. chain_id, actual_chain_id,
295+ ) ;
296+ provider
297+ }
298+
258299async fn connect_db ( config : & Configuration ) -> sqlx:: PgPool {
259300 PgPoolOptions :: new ( )
260301 . max_connections ( config. database . max_connections . get ( ) )
0 commit comments