@@ -2,9 +2,11 @@ use clap::Parser;
2
2
use ic_crypto_utils_threshold_sig_der:: {
3
3
parse_threshold_sig_key, parse_threshold_sig_key_from_der,
4
4
} ;
5
+ use ic_nns_constants:: { GOVERNANCE_CANISTER_ID , LEDGER_CANISTER_ID } ;
5
6
use ic_rosetta_api:: request_handler:: RosettaRequestHandler ;
6
7
use ic_rosetta_api:: rosetta_server:: { RosettaApiServer , RosettaApiServerOpt } ;
7
8
use ic_rosetta_api:: { ledger_client, DEFAULT_BLOCKCHAIN , DEFAULT_TOKEN_SYMBOL } ;
9
+ use ic_types:: crypto:: threshold_sig:: ThresholdSigPublicKey ;
8
10
use ic_types:: { CanisterId , PrincipalId } ;
9
11
use rosetta_core:: metrics:: RosettaMetrics ;
10
12
use std:: { path:: Path , path:: PathBuf , str:: FromStr , sync:: Arc } ;
@@ -18,6 +20,20 @@ use tracing_subscriber::util::SubscriberInitExt;
18
20
use tracing_subscriber:: { Layer , Registry } ;
19
21
use url:: Url ;
20
22
23
+ const TEST_LEDGER_CANISTER_ID : & str = "xafvr-biaaa-aaaai-aql5q-cai" ;
24
+ const TEST_TOKEN_SYMBOL : & str = "TESTICP" ;
25
+
26
+ #[ derive( Debug , Clone , clap:: ValueEnum , PartialEq , Default ) ]
27
+ enum Environment {
28
+ #[ clap( help = "Production ledger canister on mainnet" ) ]
29
+ Production ,
30
+ #[ clap( help = "Test ledger canister on mainnet" ) ]
31
+ Test ,
32
+ #[ clap( name = "deprecated-testnet" , help = "Testnet environment (deprecated)" ) ]
33
+ #[ default]
34
+ DeprecatedTestnet ,
35
+ }
36
+
21
37
#[ derive( Debug , Parser ) ]
22
38
#[ clap( next_help_heading = "Server Configuration" ) ]
23
39
struct ServerConfig {
@@ -60,6 +76,48 @@ struct NetworkConfig {
60
76
root_key : Option < PathBuf > ,
61
77
}
62
78
79
+ #[ derive( Debug ) ]
80
+ struct ParsedNetworkConfig {
81
+ pub ic_url : Url ,
82
+ pub root_key : Option < ThresholdSigPublicKey > ,
83
+ }
84
+
85
+ impl ParsedNetworkConfig {
86
+ fn from_config ( config : NetworkConfig , environment : & Environment ) -> Result < Self , String > {
87
+ const DEPRECATED_TESTNET_URL : & str = "https://exchanges.testnet.dfinity.network" ;
88
+ const MAINNET_URL : & str = "https://ic0.app" ;
89
+ const MAINNET_ROOT_KEY : & str = r#"MIGCMB0GDSsGAQQBgtx8BQMBAgEGDCsGAQQBgtx8BQMCAQNhAIFMDm7HH6tYOwi9gTc8JVw8NxsuhIY8mKTx4It0I10U+12cDNVG2WhfkToMCyzFNBWDv0tDkuRn25bWW5u0y3FxEvhHLg1aTRRQX/10hLASkQkcX4e5iINGP5gJGguqrg=="# ;
90
+
91
+ let url_str = match & config. ic_url {
92
+ Some ( url_str) => url_str. as_str ( ) ,
93
+ None => match environment {
94
+ Environment :: DeprecatedTestnet => DEPRECATED_TESTNET_URL ,
95
+ Environment :: Production | Environment :: Test => MAINNET_URL ,
96
+ } ,
97
+ } ;
98
+ let ic_url = Url :: parse ( url_str) . map_err ( |e| format ! ( "Unable to parse --ic-url: {}" , e) ) ?;
99
+
100
+ let root_key = match config. root_key {
101
+ Some ( root_key_path) => Some (
102
+ parse_threshold_sig_key ( root_key_path. as_path ( ) )
103
+ . map_err ( |e| format ! ( "Unable to parse root key from file: {}" , e) ) ?,
104
+ ) ,
105
+ None => {
106
+ match environment {
107
+ Environment :: Production | Environment :: Test => {
108
+ // The mainnet root key
109
+ let decoded = base64:: decode ( MAINNET_ROOT_KEY ) . unwrap ( ) ;
110
+ Some ( parse_threshold_sig_key_from_der ( & decoded) . unwrap ( ) )
111
+ }
112
+ Environment :: DeprecatedTestnet => None ,
113
+ }
114
+ }
115
+ } ;
116
+
117
+ Ok ( Self { ic_url, root_key } )
118
+ }
119
+ }
120
+
63
121
#[ derive( Debug , Parser ) ]
64
122
#[ clap( next_help_heading = "Canister Configuration" ) ]
65
123
struct CanisterConfig {
@@ -73,9 +131,73 @@ struct CanisterConfig {
73
131
governance_canister_id : Option < String > ,
74
132
}
75
133
134
+ #[ derive( Debug ) ]
135
+ struct ParsedCanisterConfig {
136
+ pub ledger_canister_id : CanisterId ,
137
+ pub token_symbol : String ,
138
+ pub governance_canister_id : CanisterId ,
139
+ }
140
+
141
+ impl ParsedCanisterConfig {
142
+ fn from_config ( config : CanisterConfig , environment : & Environment ) -> Result < Self , String > {
143
+ // Apply environment preset defaults when no explicit value provided
144
+ let ledger_canister_id = match config. ledger_canister_id {
145
+ Some ( explicit_value) => CanisterId :: unchecked_from_principal (
146
+ PrincipalId :: from_str ( & explicit_value) . map_err ( |e| {
147
+ format ! ( "Invalid ledger canister ID '{}': {}" , explicit_value, e)
148
+ } ) ?,
149
+ ) ,
150
+ None => match environment {
151
+ Environment :: Test => CanisterId :: unchecked_from_principal (
152
+ PrincipalId :: from_str ( TEST_LEDGER_CANISTER_ID ) . map_err ( |e| {
153
+ format ! (
154
+ "Invalid test ledger canister ID '{}': {}" ,
155
+ TEST_LEDGER_CANISTER_ID , e
156
+ )
157
+ } ) ?,
158
+ ) ,
159
+ Environment :: Production | Environment :: DeprecatedTestnet => LEDGER_CANISTER_ID ,
160
+ } ,
161
+ } ;
162
+
163
+ let token_symbol = match config. token_symbol {
164
+ Some ( explicit_value) => explicit_value,
165
+ None => match environment {
166
+ Environment :: Test => TEST_TOKEN_SYMBOL . to_string ( ) ,
167
+ Environment :: Production | Environment :: DeprecatedTestnet => {
168
+ DEFAULT_TOKEN_SYMBOL . to_string ( )
169
+ }
170
+ } ,
171
+ } ;
172
+
173
+ let governance_canister_id = match config. governance_canister_id {
174
+ Some ( explicit_value) => CanisterId :: unchecked_from_principal (
175
+ PrincipalId :: from_str ( & explicit_value) . map_err ( |e| {
176
+ format ! ( "Invalid governance canister ID '{}': {}" , explicit_value, e)
177
+ } ) ?,
178
+ ) ,
179
+ None => GOVERNANCE_CANISTER_ID ,
180
+ } ;
181
+
182
+ Ok ( Self {
183
+ ledger_canister_id,
184
+ token_symbol,
185
+ governance_canister_id,
186
+ } )
187
+ }
188
+ }
189
+
76
190
#[ derive( Debug , Parser ) ]
77
191
#[ clap( version) ]
78
192
struct Opt {
193
+ #[ clap(
194
+ short = 'e' ,
195
+ long = "environment" ,
196
+ default_value = "deprecated-testnet" ,
197
+ help = "Environment preset that configures network and canister settings."
198
+ ) ]
199
+ environment : Environment ,
200
+
79
201
#[ clap( flatten) ]
80
202
server : ServerConfig ,
81
203
@@ -117,24 +239,6 @@ struct Opt {
117
239
enable_rosetta_blocks : bool ,
118
240
}
119
241
120
- impl Opt {
121
- fn default_url ( & self ) -> Url {
122
- let url = if self . mainnet {
123
- "https://ic0.app"
124
- } else {
125
- "https://exchanges.testnet.dfinity.network"
126
- } ;
127
- Url :: parse ( url) . unwrap ( )
128
- }
129
-
130
- fn ic_url ( & self ) -> Result < Url , String > {
131
- match self . network . ic_url . as_ref ( ) {
132
- None => Ok ( self . default_url ( ) ) ,
133
- Some ( s) => Url :: parse ( s) . map_err ( |e| format ! ( "Unable to parse --ic-url: {}" , e) ) ,
134
- }
135
- }
136
- }
137
-
138
242
fn init_logging ( level : Level ) -> std:: io:: Result < WorkerGuard > {
139
243
std:: fs:: create_dir_all ( "log" ) ?;
140
244
@@ -186,6 +290,26 @@ async fn main() -> std::io::Result<()> {
186
290
warn ! ( "--log-config-file is deprecated and ignored" )
187
291
}
188
292
293
+ // Check for conflicting flags
294
+ if opt. mainnet && opt. environment != Environment :: DeprecatedTestnet {
295
+ eprintln ! ( "Cannot specify both --mainnet and --environment flags. Please use --environment production instead of --mainnet." ) ;
296
+ std:: process:: exit ( 1 ) ;
297
+ }
298
+
299
+ // Handle mainnet flag by treating it as environment production
300
+ let environment = if opt. mainnet {
301
+ warn ! ( "--mainnet flag is deprecated. Please use --environment production instead." ) ;
302
+ Environment :: Production
303
+ } else {
304
+ opt. environment
305
+ } ;
306
+
307
+ if environment == Environment :: DeprecatedTestnet {
308
+ warn ! (
309
+ "deprecated-testnet environment is deprecated. Please use --environment test instead."
310
+ ) ;
311
+ }
312
+
189
313
let pkg_name = env ! ( "CARGO_PKG_NAME" ) ;
190
314
let pkg_version = env ! ( "CARGO_PKG_VERSION" ) ;
191
315
info ! ( "Starting {}, pkg_version: {}" , pkg_name, pkg_version) ;
@@ -196,66 +320,25 @@ async fn main() -> std::io::Result<()> {
196
320
} ;
197
321
info ! ( "Listening on {}:{}" , opt. server. address, listen_port) ;
198
322
let addr = format ! ( "{}:{}" , opt. server. address, listen_port) ;
199
- let url = opt. ic_url ( ) . unwrap ( ) ;
200
- info ! ( "Internet Computer URL set to {}" , url) ;
201
-
202
- let ( root_key, canister_id, governance_canister_id) = if opt. mainnet {
203
- let root_key = match opt. network . root_key {
204
- Some ( root_key_path) => parse_threshold_sig_key ( root_key_path. as_path ( ) ) ?,
205
- None => {
206
- // The mainnet root key
207
- let root_key_text = r#"MIGCMB0GDSsGAQQBgtx8BQMBAgEGDCsGAQQBgtx8BQMCAQNhAIFMDm7HH6tYOwi9gTc8JVw8NxsuhIY8mKTx4It0I10U+12cDNVG2WhfkToMCyzFNBWDv0tDkuRn25bWW5u0y3FxEvhHLg1aTRRQX/10hLASkQkcX4e5iINGP5gJGguqrg=="# ;
208
- let decoded = base64:: decode ( root_key_text) . unwrap ( ) ;
209
- parse_threshold_sig_key_from_der ( & decoded) . unwrap ( )
210
- }
211
- } ;
212
323
213
- let canister_id = match opt. canister . ledger_canister_id {
214
- Some ( cid ) => {
215
- CanisterId :: unchecked_from_principal ( PrincipalId :: from_str ( & cid [ .. ] ) . unwrap ( ) )
216
- }
217
- None => ic_nns_constants :: LEDGER_CANISTER_ID ,
218
- } ;
324
+ let network_config = ParsedNetworkConfig :: from_config ( opt. network , & environment )
325
+ . unwrap_or_else ( |e| {
326
+ error ! ( "Configuration error: {}" , e ) ;
327
+ std :: process :: exit ( 1 ) ;
328
+ } ) ;
329
+ info ! ( "Internet Computer URL set to {}" , network_config . ic_url ) ;
219
330
220
- let governance_canister_id = match opt. canister . governance_canister_id {
221
- Some ( cid) => {
222
- CanisterId :: unchecked_from_principal ( PrincipalId :: from_str ( & cid[ ..] ) . unwrap ( ) )
223
- }
224
- None => ic_nns_constants:: GOVERNANCE_CANISTER_ID ,
225
- } ;
226
-
227
- ( Some ( root_key) , canister_id, governance_canister_id)
228
- } else {
229
- let root_key = match opt. network . root_key {
230
- Some ( root_key_path) => Some ( parse_threshold_sig_key ( root_key_path. as_path ( ) ) ?) ,
231
- None => {
232
- warn ! ( "Data certificate will not be verified due to missing root key" ) ;
233
- None
234
- }
235
- } ;
236
-
237
- let canister_id = match opt. canister . ledger_canister_id {
238
- Some ( cid) => {
239
- CanisterId :: unchecked_from_principal ( PrincipalId :: from_str ( & cid[ ..] ) . unwrap ( ) )
240
- }
241
- None => ic_nns_constants:: LEDGER_CANISTER_ID ,
242
- } ;
243
-
244
- let governance_canister_id = match opt. canister . governance_canister_id {
245
- Some ( cid) => {
246
- CanisterId :: unchecked_from_principal ( PrincipalId :: from_str ( & cid[ ..] ) . unwrap ( ) )
247
- }
248
- None => ic_nns_constants:: GOVERNANCE_CANISTER_ID ,
249
- } ;
331
+ if network_config. root_key . is_none ( ) {
332
+ warn ! ( "Data certificate will not be verified due to missing root key" ) ;
333
+ }
250
334
251
- ( root_key, canister_id, governance_canister_id)
252
- } ;
335
+ let canister_config = ParsedCanisterConfig :: from_config ( opt. canister , & environment)
336
+ . unwrap_or_else ( |e| {
337
+ error ! ( "Configuration error: {}" , e) ;
338
+ std:: process:: exit ( 1 ) ;
339
+ } ) ;
253
340
254
- let token_symbol = opt
255
- . canister
256
- . token_symbol
257
- . unwrap_or_else ( || DEFAULT_TOKEN_SYMBOL . to_string ( ) ) ;
258
- info ! ( "Token symbol set to {}" , token_symbol) ;
341
+ info ! ( "Token symbol set to {}" , canister_config. token_symbol) ;
259
342
260
343
let store_location: Option < & Path > = match opt. storage . store_type . as_ref ( ) {
261
344
"sqlite" => Some ( & opt. storage . location ) ,
@@ -272,7 +355,6 @@ async fn main() -> std::io::Result<()> {
272
355
let Opt {
273
356
offline,
274
357
exit_on_sync,
275
- mainnet,
276
358
not_whitelisted,
277
359
expose_metrics,
278
360
blockchain,
@@ -289,29 +371,33 @@ async fn main() -> std::io::Result<()> {
289
371
enable_rosetta_blocks = opt. enable_rosetta_blocks ;
290
372
}
291
373
374
+ // Determine effective mainnet setting based on the environment
375
+ let effective_mainnet =
376
+ environment == Environment :: Production || environment == Environment :: Test ;
377
+
292
378
let client = ledger_client:: LedgerClient :: new (
293
- url ,
294
- canister_id ,
295
- token_symbol,
296
- governance_canister_id,
379
+ network_config . ic_url ,
380
+ canister_config . ledger_canister_id ,
381
+ canister_config . token_symbol ,
382
+ canister_config . governance_canister_id ,
297
383
store_location,
298
384
opt. storage . max_blocks ,
299
385
offline,
300
- root_key,
386
+ network_config . root_key ,
301
387
enable_rosetta_blocks,
302
388
opt. storage . optimize_indexes ,
303
389
)
304
390
. await
305
391
. map_err ( |e| {
306
- let msg = if mainnet && !not_whitelisted && e. is_internal_error_403 ( ) {
392
+ let msg = if effective_mainnet && !not_whitelisted && e. is_internal_error_403 ( ) {
307
393
", You may not be whitelisted; please try running the Rosetta server again with the '--not_whitelisted' flag"
308
394
} else { "" } ;
309
395
( e, msg)
310
396
} )
311
397
. unwrap_or_else ( |( e, is_403) | panic ! ( "Failed to initialize ledger client{}: {:?}" , is_403, e) ) ;
312
398
313
399
let ledger = Arc :: new ( client) ;
314
- let canister_id_str = canister_id . to_string ( ) ;
400
+ let canister_id_str = canister_config . ledger_canister_id . to_string ( ) ;
315
401
let rosetta_metrics = RosettaMetrics :: new ( "ICP" . to_string ( ) , canister_id_str) ;
316
402
let req_handler = RosettaRequestHandler :: new ( blockchain, ledger. clone ( ) , rosetta_metrics) ;
317
403
@@ -335,7 +421,7 @@ async fn main() -> std::io::Result<()> {
335
421
serv. run ( RosettaApiServerOpt {
336
422
exit_on_sync,
337
423
offline,
338
- mainnet,
424
+ mainnet : effective_mainnet ,
339
425
not_whitelisted,
340
426
} )
341
427
. await
0 commit comments