1use std::collections::BTreeMap;
6
7use axum::{
8 Json,
9 extract::{Path, State},
10};
11use iota_protocol_config::{ProtocolConfig, ProtocolConfigValue, ProtocolVersion};
12use iota_sdk_types::{Address, ObjectId, PublicKeyExt};
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15
16use crate::{
17 RestError, RestService, Result,
18 accept::AcceptFormat,
19 openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler},
20 reader::StateReader,
21};
22
23pub struct GetSystemStateSummary;
24
25impl ApiEndpoint<RestService> for GetSystemStateSummary {
26 fn method(&self) -> axum::http::Method {
27 axum::http::Method::GET
28 }
29
30 fn path(&self) -> &'static str {
31 "/system"
32 }
33
34 fn operation(
35 &self,
36 generator: &mut schemars::gen::SchemaGenerator,
37 ) -> openapiv3::v3_1::Operation {
38 OperationBuilder::new()
39 .tag("System")
40 .operation_id("GetSystemStateSummary")
41 .response(
42 200,
43 ResponseBuilder::new()
44 .json_content::<SystemStateSummary>(generator)
45 .build(),
46 )
47 .build()
48 }
49
50 fn handler(&self) -> RouteHandler<RestService> {
51 RouteHandler::new(self.method(), get_system_state_summary)
52 }
53}
54
55async fn get_system_state_summary(
56 accept: AcceptFormat,
57 State(state): State<StateReader>,
58) -> Result<Json<SystemStateSummary>> {
59 match accept {
60 AcceptFormat::Json => {}
61 _ => {
62 return Err(RestError::new(
63 axum::http::StatusCode::BAD_REQUEST,
64 "invalid accept type",
65 ));
66 }
67 }
68
69 let summary = state.get_system_state_summary()?;
70
71 Ok(Json(summary))
72}
73
74#[serde_with::serde_as]
75#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, JsonSchema)]
76pub struct SystemStateSummary {
77 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
79 #[schemars(with = "crate::_schemars::U64")]
80 pub epoch: u64,
81 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
83 #[schemars(with = "crate::_schemars::U64")]
84 pub protocol_version: u64,
85 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
87 #[schemars(with = "crate::_schemars::U64")]
88 pub system_state_version: u64,
89 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
91 #[schemars(with = "crate::_schemars::U64")]
92 pub iota_total_supply: u64,
93 pub iota_treasury_cap_id: ObjectId,
95 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
98 #[schemars(with = "crate::_schemars::U64")]
99 pub storage_fund_total_object_storage_rebates: u64,
100 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
104 #[schemars(with = "crate::_schemars::U64")]
105 pub storage_fund_non_refundable_balance: u64,
106 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
108 #[schemars(with = "crate::_schemars::U64")]
109 pub reference_gas_price: u64,
110 pub safe_mode: bool,
115 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
118 #[schemars(with = "crate::_schemars::U64")]
119 pub safe_mode_storage_charges: u64,
120 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
123 #[schemars(with = "crate::_schemars::U64")]
124 pub safe_mode_computation_charges: u64,
125 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
127 #[schemars(with = "crate::_schemars::U64")]
128 pub safe_mode_computation_charges_burned: u64,
129 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
132 #[schemars(with = "crate::_schemars::U64")]
133 pub safe_mode_storage_rebates: u64,
134 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
136 #[schemars(with = "crate::_schemars::U64")]
137 pub safe_mode_non_refundable_storage_fee: u64,
138 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
140 #[schemars(with = "crate::_schemars::U64")]
141 pub epoch_start_timestamp_ms: u64,
142
143 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
146 #[schemars(with = "crate::_schemars::U64")]
147 pub epoch_duration_ms: u64,
148
149 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
152 #[schemars(with = "crate::_schemars::U64")]
153 pub min_validator_count: u64,
154
155 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
158 #[schemars(with = "crate::_schemars::U64")]
159 pub max_validator_count: u64,
160
161 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
163 #[schemars(with = "crate::_schemars::U64")]
164 pub min_validator_joining_stake: u64,
165
166 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
171 #[schemars(with = "crate::_schemars::U64")]
172 pub validator_low_stake_threshold: u64,
173
174 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
177 #[schemars(with = "crate::_schemars::U64")]
178 pub validator_very_low_stake_threshold: u64,
179
180 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
183 #[schemars(with = "crate::_schemars::U64")]
184 pub validator_low_stake_grace_period: u64,
185
186 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
190 #[schemars(with = "crate::_schemars::U64")]
191 pub total_stake: u64,
192 #[serde_as(as = "Vec<iota_types::iota_serde::BigInt<u64>>")]
195 #[schemars(with = "Vec<crate::_schemars::U64>")]
196 pub committee_members: Vec<u64>,
197 pub active_validators: Vec<ValidatorSummary>,
199 pub pending_active_validators_id: ObjectId,
202 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
204 #[schemars(with = "crate::_schemars::U64")]
205 pub pending_active_validators_size: u64,
206 #[serde_as(as = "Vec<iota_types::iota_serde::BigInt<u64>>")]
209 #[schemars(with = "Vec<crate::_schemars::U64>")]
210 pub pending_removals: Vec<u64>,
211 pub staking_pool_mappings_id: ObjectId,
214 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
216 #[schemars(with = "crate::_schemars::U64")]
217 pub staking_pool_mappings_size: u64,
218 pub inactive_pools_id: ObjectId,
221 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
223 #[schemars(with = "crate::_schemars::U64")]
224 pub inactive_pools_size: u64,
225 pub validator_candidates_id: ObjectId,
228 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
230 #[schemars(with = "crate::_schemars::U64")]
231 pub validator_candidates_size: u64,
232 #[serde_as(as = "Vec<(_, iota_types::iota_serde::BigInt<u64>)>")]
235 #[schemars(with = "Vec<(Address, crate::_schemars::U64)>")]
236 pub at_risk_validators: Vec<(Address, u64)>,
237 pub validator_report_records: Vec<(Address, Vec<Address>)>,
239}
240
241#[serde_with::serde_as]
245#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, JsonSchema)]
246pub struct ValidatorSummary {
247 pub address: Address,
249 pub authority_public_key: iota_sdk_types::Bls12381PublicKey,
250 pub network_public_key: iota_sdk_types::Ed25519PublicKey,
251 pub protocol_public_key: iota_sdk_types::Ed25519PublicKey,
252 #[serde_as(as = "fastcrypto::encoding::Base64")]
253 #[schemars(with = "String")]
254 pub proof_of_possession_bytes: Vec<u8>,
255 pub name: String,
256 pub description: String,
257 pub image_url: String,
258 pub project_url: String,
259 pub net_address: String,
260 pub p2p_address: String,
261 pub primary_address: String,
262 pub next_epoch_authority_public_key: Option<iota_sdk_types::Bls12381PublicKey>,
263 pub next_epoch_network_public_key: Option<iota_sdk_types::Ed25519PublicKey>,
264 pub next_epoch_protocol_public_key: Option<iota_sdk_types::Ed25519PublicKey>,
265 #[serde_as(as = "Option<fastcrypto::encoding::Base64>")]
266 #[schemars(with = "Option<String>")]
267 pub next_epoch_proof_of_possession: Option<Vec<u8>>,
268 pub next_epoch_net_address: Option<String>,
269 pub next_epoch_p2p_address: Option<String>,
270 pub next_epoch_primary_address: Option<String>,
271
272 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
273 #[schemars(with = "crate::_schemars::U64")]
274 pub voting_power: u64,
275 pub operation_cap_id: ObjectId,
276 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
277 #[schemars(with = "crate::_schemars::U64")]
278 pub gas_price: u64,
279 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
280 #[schemars(with = "crate::_schemars::U64")]
281 pub commission_rate: u64,
282 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
283 #[schemars(with = "crate::_schemars::U64")]
284 pub next_epoch_stake: u64,
285 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
286 #[schemars(with = "crate::_schemars::U64")]
287 pub next_epoch_gas_price: u64,
288 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
289 #[schemars(with = "crate::_schemars::U64")]
290 pub next_epoch_commission_rate: u64,
291
292 pub staking_pool_id: ObjectId,
295 #[serde_as(as = "Option<iota_types::iota_serde::BigInt<u64>>")]
297 #[schemars(with = "Option<crate::_schemars::U64>")]
298 pub staking_pool_activation_epoch: Option<u64>,
299 #[serde_as(as = "Option<iota_types::iota_serde::BigInt<u64>>")]
302 #[schemars(with = "Option<crate::_schemars::U64>")]
303 pub staking_pool_deactivation_epoch: Option<u64>,
304 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
306 #[schemars(with = "crate::_schemars::U64")]
307 pub staking_pool_iota_balance: u64,
308 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
310 #[schemars(with = "crate::_schemars::U64")]
311 pub rewards_pool: u64,
312 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
314 #[schemars(with = "crate::_schemars::U64")]
315 pub pool_token_balance: u64,
316 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
318 #[schemars(with = "crate::_schemars::U64")]
319 pub pending_stake: u64,
320 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
323 #[schemars(with = "crate::_schemars::U64")]
324 pub pending_total_iota_withdraw: u64,
325 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
328 #[schemars(with = "crate::_schemars::U64")]
329 pub pending_pool_token_withdraw: u64,
330 pub exchange_rates_id: ObjectId,
332 #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
334 #[schemars(with = "crate::_schemars::U64")]
335 pub exchange_rates_size: u64,
336}
337
338impl From<iota_types::iota_system_state::iota_system_state_summary::IotaValidatorSummary>
339 for ValidatorSummary
340{
341 fn from(
342 value: iota_types::iota_system_state::iota_system_state_summary::IotaValidatorSummary,
343 ) -> Self {
344 let iota_types::iota_system_state::iota_system_state_summary::IotaValidatorSummary {
345 iota_address,
346 authority_pubkey_bytes,
347 network_pubkey_bytes,
348 protocol_pubkey_bytes,
349 proof_of_possession_bytes,
350 name,
351 description,
352 image_url,
353 project_url,
354 net_address,
355 p2p_address,
356 primary_address,
357 next_epoch_authority_pubkey_bytes,
358 next_epoch_proof_of_possession,
359 next_epoch_network_pubkey_bytes,
360 next_epoch_protocol_pubkey_bytes,
361 next_epoch_net_address,
362 next_epoch_p2p_address,
363 next_epoch_primary_address,
364 voting_power,
365 operation_cap_id,
366 gas_price,
367 commission_rate,
368 next_epoch_stake,
369 next_epoch_gas_price,
370 next_epoch_commission_rate,
371 staking_pool_id,
372 staking_pool_activation_epoch,
373 staking_pool_deactivation_epoch,
374 staking_pool_iota_balance,
375 rewards_pool,
376 pool_token_balance,
377 pending_stake,
378 pending_total_iota_withdraw,
379 pending_pool_token_withdraw,
380 exchange_rates_id,
381 exchange_rates_size,
382 } = value;
383
384 Self {
385 address: iota_address.into(),
386 authority_public_key: iota_sdk_types::Bls12381PublicKey::from_bytes(
387 authority_pubkey_bytes,
388 )
389 .unwrap(),
390 network_public_key: iota_sdk_types::Ed25519PublicKey::from_bytes(network_pubkey_bytes)
391 .unwrap(),
392 protocol_public_key: iota_sdk_types::Ed25519PublicKey::from_bytes(
393 protocol_pubkey_bytes,
394 )
395 .unwrap(),
396 proof_of_possession_bytes,
397 name,
398 description,
399 image_url,
400 project_url,
401 net_address,
402 p2p_address,
403 primary_address,
404 next_epoch_authority_public_key: next_epoch_authority_pubkey_bytes
405 .map(|bytes| iota_sdk_types::Bls12381PublicKey::from_bytes(bytes).unwrap()),
406 next_epoch_network_public_key: next_epoch_network_pubkey_bytes
407 .map(|bytes| iota_sdk_types::Ed25519PublicKey::from_bytes(bytes).unwrap()),
408 next_epoch_protocol_public_key: next_epoch_protocol_pubkey_bytes
409 .map(|bytes| iota_sdk_types::Ed25519PublicKey::from_bytes(bytes).unwrap()),
410 next_epoch_proof_of_possession,
411 next_epoch_net_address,
412 next_epoch_p2p_address,
413 next_epoch_primary_address,
414 voting_power,
415 operation_cap_id: operation_cap_id.into(),
416 gas_price,
417 commission_rate,
418 next_epoch_stake,
419 next_epoch_gas_price,
420 next_epoch_commission_rate,
421 staking_pool_id: staking_pool_id.into(),
422 staking_pool_activation_epoch,
423 staking_pool_deactivation_epoch,
424 staking_pool_iota_balance,
425 rewards_pool,
426 pool_token_balance,
427 pending_stake,
428 pending_total_iota_withdraw,
429 pending_pool_token_withdraw,
430 exchange_rates_id: exchange_rates_id.into(),
431 exchange_rates_size,
432 }
433 }
434}
435
436impl From<iota_types::iota_system_state::iota_system_state_summary::IotaSystemStateSummaryV2>
437 for SystemStateSummary
438{
439 fn from(
440 value: iota_types::iota_system_state::iota_system_state_summary::IotaSystemStateSummaryV2,
441 ) -> Self {
442 let iota_types::iota_system_state::iota_system_state_summary::IotaSystemStateSummaryV2 {
443 epoch,
444 protocol_version,
445 system_state_version,
446 iota_total_supply,
447 iota_treasury_cap_id,
448 storage_fund_total_object_storage_rebates,
449 storage_fund_non_refundable_balance,
450 reference_gas_price,
451 safe_mode,
452 safe_mode_storage_charges,
453 safe_mode_computation_charges,
454 safe_mode_computation_charges_burned,
455 safe_mode_storage_rebates,
456 safe_mode_non_refundable_storage_fee,
457 epoch_start_timestamp_ms,
458 epoch_duration_ms,
459 min_validator_count,
460 max_validator_count,
461 min_validator_joining_stake,
462 validator_low_stake_threshold,
463 validator_very_low_stake_threshold,
464 validator_low_stake_grace_period,
465 total_stake,
466 committee_members,
467 active_validators,
468 pending_active_validators_id,
469 pending_active_validators_size,
470 pending_removals,
471 staking_pool_mappings_id,
472 staking_pool_mappings_size,
473 inactive_pools_id,
474 inactive_pools_size,
475 validator_candidates_id,
476 validator_candidates_size,
477 at_risk_validators,
478 validator_report_records,
479 } = value;
480
481 Self {
482 epoch,
483 protocol_version,
484 system_state_version,
485 iota_total_supply,
486 iota_treasury_cap_id: iota_treasury_cap_id.into(),
487 storage_fund_total_object_storage_rebates,
488 storage_fund_non_refundable_balance,
489 reference_gas_price,
490 safe_mode,
491 safe_mode_storage_charges,
492 safe_mode_computation_charges,
493 safe_mode_computation_charges_burned,
494 safe_mode_storage_rebates,
495 safe_mode_non_refundable_storage_fee,
496 epoch_start_timestamp_ms,
497 epoch_duration_ms,
498 min_validator_count,
499 max_validator_count,
500 min_validator_joining_stake,
501 validator_low_stake_threshold,
502 validator_very_low_stake_threshold,
503 validator_low_stake_grace_period,
504 total_stake,
505 committee_members,
506 active_validators: active_validators.into_iter().map(Into::into).collect(),
507 pending_active_validators_id: pending_active_validators_id.into(),
508 pending_active_validators_size,
509 pending_removals,
510 staking_pool_mappings_id: staking_pool_mappings_id.into(),
511 staking_pool_mappings_size,
512 inactive_pools_id: inactive_pools_id.into(),
513 inactive_pools_size,
514 validator_candidates_id: validator_candidates_id.into(),
515 validator_candidates_size,
516 at_risk_validators: at_risk_validators
517 .into_iter()
518 .map(|(address, idx)| (address.into(), idx))
519 .collect(),
520 validator_report_records: validator_report_records
521 .into_iter()
522 .map(|(address, reports)| {
523 (
524 address.into(),
525 reports.into_iter().map(Into::into).collect(),
526 )
527 })
528 .collect(),
529 }
530 }
531}
532
533pub struct GetCurrentProtocolConfig;
534
535impl ApiEndpoint<RestService> for GetCurrentProtocolConfig {
536 fn method(&self) -> axum::http::Method {
537 axum::http::Method::GET
538 }
539
540 fn path(&self) -> &'static str {
541 "/system/protocol"
542 }
543
544 fn operation(
545 &self,
546 generator: &mut schemars::gen::SchemaGenerator,
547 ) -> openapiv3::v3_1::Operation {
548 OperationBuilder::new()
549 .tag("System")
550 .operation_id("GetCurrentProtocolConfig")
551 .response(
552 200,
553 ResponseBuilder::new()
554 .json_content::<ProtocolConfigResponse>(generator)
555 .header::<String>(X_IOTA_MIN_SUPPORTED_PROTOCOL_VERSION, generator)
556 .header::<String>(X_IOTA_MAX_SUPPORTED_PROTOCOL_VERSION, generator)
557 .build(),
558 )
559 .build()
560 }
561
562 fn handler(&self) -> RouteHandler<RestService> {
563 RouteHandler::new(self.method(), get_current_protocol_config)
564 }
565}
566
567async fn get_current_protocol_config(
568 accept: AcceptFormat,
569 State(state): State<StateReader>,
570) -> Result<(SupportedProtocolHeaders, Json<ProtocolConfigResponse>)> {
571 match accept {
572 AcceptFormat::Json => {}
573 _ => {
574 return Err(RestError::new(
575 axum::http::StatusCode::BAD_REQUEST,
576 "invalid accept type",
577 ));
578 }
579 }
580
581 let current_protocol_version = state.get_system_state_summary()?.protocol_version;
582
583 let config = ProtocolConfig::get_for_version_if_supported(
584 current_protocol_version.into(),
585 state.inner().get_chain_identifier()?.chain(),
586 )
587 .ok_or_else(|| ProtocolNotFoundError::new(current_protocol_version))?;
588
589 Ok((supported_protocol_headers(), Json(config.into())))
590}
591
592pub struct GetProtocolConfig;
593
594impl ApiEndpoint<RestService> for GetProtocolConfig {
595 fn method(&self) -> axum::http::Method {
596 axum::http::Method::GET
597 }
598
599 fn path(&self) -> &'static str {
600 "/system/protocol/{version}"
601 }
602
603 fn operation(
604 &self,
605 generator: &mut schemars::gen::SchemaGenerator,
606 ) -> openapiv3::v3_1::Operation {
607 OperationBuilder::new()
608 .tag("System")
609 .operation_id("GetProtocolConfig")
610 .path_parameter::<u64>("version", generator)
611 .response(
612 200,
613 ResponseBuilder::new()
614 .json_content::<ProtocolConfigResponse>(generator)
615 .header::<String>(X_IOTA_MIN_SUPPORTED_PROTOCOL_VERSION, generator)
616 .header::<String>(X_IOTA_MAX_SUPPORTED_PROTOCOL_VERSION, generator)
617 .build(),
618 )
619 .response(404, ResponseBuilder::new().build())
620 .build()
621 }
622
623 fn handler(&self) -> RouteHandler<RestService> {
624 RouteHandler::new(self.method(), get_protocol_config)
625 }
626}
627
628async fn get_protocol_config(
629 Path(version): Path<u64>,
630 accept: AcceptFormat,
631 State(state): State<StateReader>,
632) -> Result<(SupportedProtocolHeaders, Json<ProtocolConfigResponse>)> {
633 match accept {
634 AcceptFormat::Json => {}
635 _ => {
636 return Err(RestError::new(
637 axum::http::StatusCode::BAD_REQUEST,
638 "invalid accept type",
639 ));
640 }
641 }
642
643 let config = ProtocolConfig::get_for_version_if_supported(
644 version.into(),
645 state.inner().get_chain_identifier()?.chain(),
646 )
647 .ok_or_else(|| ProtocolNotFoundError::new(version))?;
648
649 Ok((supported_protocol_headers(), Json(config.into())))
650}
651
652#[derive(Debug)]
653pub struct ProtocolNotFoundError {
654 version: u64,
655}
656
657impl ProtocolNotFoundError {
658 pub fn new(version: u64) -> Self {
659 Self { version }
660 }
661}
662
663impl std::fmt::Display for ProtocolNotFoundError {
664 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
665 write!(f, "Protocol version {} not found", self.version)
666 }
667}
668
669impl std::error::Error for ProtocolNotFoundError {}
670
671impl From<ProtocolNotFoundError> for crate::RestError {
672 fn from(value: ProtocolNotFoundError) -> Self {
673 Self::new(axum::http::StatusCode::NOT_FOUND, value.to_string())
674 }
675}
676
677pub const X_IOTA_MIN_SUPPORTED_PROTOCOL_VERSION: &str = "x-iota-min-supported-protocol-version";
679pub const X_IOTA_MAX_SUPPORTED_PROTOCOL_VERSION: &str = "x-iota-max-supported-protocol-version";
681
682type SupportedProtocolHeaders = [(&'static str, String); 2];
683
684fn supported_protocol_headers() -> SupportedProtocolHeaders {
685 [
686 (
687 X_IOTA_MIN_SUPPORTED_PROTOCOL_VERSION,
688 ProtocolVersion::MIN.as_u64().to_string(),
689 ),
690 (
691 X_IOTA_MAX_SUPPORTED_PROTOCOL_VERSION,
692 ProtocolVersion::MAX.as_u64().to_string(),
693 ),
694 ]
695}
696
697#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema)]
698#[serde(rename = "ProtocolConfig")]
699pub struct ProtocolConfigResponse {
700 #[serde(with = "serde_with::As::<serde_with::DisplayFromStr>")]
701 #[schemars(with = "crate::_schemars::U64")]
702 pub protocol_version: u64,
703 pub feature_flags: BTreeMap<String, bool>,
704 pub attributes: BTreeMap<String, String>,
705}
706
707impl From<ProtocolConfig> for ProtocolConfigResponse {
708 fn from(config: ProtocolConfig) -> Self {
709 let attributes = config
710 .attr_map()
711 .into_iter()
712 .filter_map(|(k, maybe_v)| {
713 maybe_v.map(move |v| {
714 let v = match v {
715 ProtocolConfigValue::u16(x) => x.to_string(),
716 ProtocolConfigValue::u32(y) => y.to_string(),
717 ProtocolConfigValue::u64(z) => z.to_string(),
718 ProtocolConfigValue::bool(b) => b.to_string(),
719 };
720 (k, v)
721 })
722 })
723 .collect();
724 ProtocolConfigResponse {
725 protocol_version: config.version.as_u64(),
726 attributes,
727 feature_flags: config.feature_map(),
728 }
729 }
730}
731
732pub struct GetGasInfo;
733
734impl ApiEndpoint<RestService> for GetGasInfo {
735 fn method(&self) -> axum::http::Method {
736 axum::http::Method::GET
737 }
738
739 fn path(&self) -> &'static str {
740 "/system/gas"
741 }
742
743 fn operation(
744 &self,
745 generator: &mut schemars::gen::SchemaGenerator,
746 ) -> openapiv3::v3_1::Operation {
747 OperationBuilder::new()
748 .tag("System")
749 .operation_id("GetGasInfo")
750 .response(
751 200,
752 ResponseBuilder::new()
753 .json_content::<GasInfo>(generator)
754 .build(),
755 )
756 .build()
757 }
758
759 fn handler(&self) -> RouteHandler<RestService> {
760 RouteHandler::new(self.method(), get_gas_info)
761 }
762}
763
764async fn get_gas_info(
765 accept: AcceptFormat,
766 State(state): State<StateReader>,
767) -> Result<Json<GasInfo>> {
768 match accept {
769 AcceptFormat::Json => {}
770 _ => {
771 return Err(RestError::new(
772 axum::http::StatusCode::BAD_REQUEST,
773 "invalid accept type",
774 ));
775 }
776 }
777
778 let reference_gas_price = state.get_system_state_summary()?.reference_gas_price;
779
780 Ok(Json(GasInfo {
781 reference_gas_price,
782 }))
783}
784
785#[derive(serde::Serialize, serde::Deserialize, JsonSchema)]
786pub struct GasInfo {
787 #[serde(with = "serde_with::As::<serde_with::DisplayFromStr>")]
788 #[schemars(with = "crate::_schemars::U64")]
789 pub reference_gas_price: u64,
790}