1use std::collections::BTreeMap;
6
7use axum::{
8    Json,
9    extract::{Path, State},
10};
11use iota_protocol_config::{ProtocolConfig, ProtocolConfigValue, ProtocolVersion};
12use iota_sdk2::types::{Address, ObjectId};
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_sdk2::types::Bls12381PublicKey,
250    pub network_public_key: iota_sdk2::types::Ed25519PublicKey,
251    pub protocol_public_key: iota_sdk2::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_sdk2::types::Bls12381PublicKey>,
263    pub next_epoch_network_public_key: Option<iota_sdk2::types::Ed25519PublicKey>,
264    pub next_epoch_protocol_public_key: Option<iota_sdk2::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_sdk2::types::Bls12381PublicKey::from_bytes(
387                authority_pubkey_bytes,
388            )
389            .unwrap(),
390            network_public_key: iota_sdk2::types::Ed25519PublicKey::from_bytes(
391                network_pubkey_bytes,
392            )
393            .unwrap(),
394            protocol_public_key: iota_sdk2::types::Ed25519PublicKey::from_bytes(
395                protocol_pubkey_bytes,
396            )
397            .unwrap(),
398            proof_of_possession_bytes,
399            name,
400            description,
401            image_url,
402            project_url,
403            net_address,
404            p2p_address,
405            primary_address,
406            next_epoch_authority_public_key: next_epoch_authority_pubkey_bytes
407                .map(|bytes| iota_sdk2::types::Bls12381PublicKey::from_bytes(bytes).unwrap()),
408            next_epoch_network_public_key: next_epoch_network_pubkey_bytes
409                .map(|bytes| iota_sdk2::types::Ed25519PublicKey::from_bytes(bytes).unwrap()),
410            next_epoch_protocol_public_key: next_epoch_protocol_pubkey_bytes
411                .map(|bytes| iota_sdk2::types::Ed25519PublicKey::from_bytes(bytes).unwrap()),
412            next_epoch_proof_of_possession,
413            next_epoch_net_address,
414            next_epoch_p2p_address,
415            next_epoch_primary_address,
416            voting_power,
417            operation_cap_id: operation_cap_id.into(),
418            gas_price,
419            commission_rate,
420            next_epoch_stake,
421            next_epoch_gas_price,
422            next_epoch_commission_rate,
423            staking_pool_id: staking_pool_id.into(),
424            staking_pool_activation_epoch,
425            staking_pool_deactivation_epoch,
426            staking_pool_iota_balance,
427            rewards_pool,
428            pool_token_balance,
429            pending_stake,
430            pending_total_iota_withdraw,
431            pending_pool_token_withdraw,
432            exchange_rates_id: exchange_rates_id.into(),
433            exchange_rates_size,
434        }
435    }
436}
437
438impl From<iota_types::iota_system_state::iota_system_state_summary::IotaSystemStateSummaryV2>
439    for SystemStateSummary
440{
441    fn from(
442        value: iota_types::iota_system_state::iota_system_state_summary::IotaSystemStateSummaryV2,
443    ) -> Self {
444        let iota_types::iota_system_state::iota_system_state_summary::IotaSystemStateSummaryV2 {
445            epoch,
446            protocol_version,
447            system_state_version,
448            iota_total_supply,
449            iota_treasury_cap_id,
450            storage_fund_total_object_storage_rebates,
451            storage_fund_non_refundable_balance,
452            reference_gas_price,
453            safe_mode,
454            safe_mode_storage_charges,
455            safe_mode_computation_charges,
456            safe_mode_computation_charges_burned,
457            safe_mode_storage_rebates,
458            safe_mode_non_refundable_storage_fee,
459            epoch_start_timestamp_ms,
460            epoch_duration_ms,
461            min_validator_count,
462            max_validator_count,
463            min_validator_joining_stake,
464            validator_low_stake_threshold,
465            validator_very_low_stake_threshold,
466            validator_low_stake_grace_period,
467            total_stake,
468            committee_members,
469            active_validators,
470            pending_active_validators_id,
471            pending_active_validators_size,
472            pending_removals,
473            staking_pool_mappings_id,
474            staking_pool_mappings_size,
475            inactive_pools_id,
476            inactive_pools_size,
477            validator_candidates_id,
478            validator_candidates_size,
479            at_risk_validators,
480            validator_report_records,
481        } = value;
482
483        Self {
484            epoch,
485            protocol_version,
486            system_state_version,
487            iota_total_supply,
488            iota_treasury_cap_id: iota_treasury_cap_id.into(),
489            storage_fund_total_object_storage_rebates,
490            storage_fund_non_refundable_balance,
491            reference_gas_price,
492            safe_mode,
493            safe_mode_storage_charges,
494            safe_mode_computation_charges,
495            safe_mode_computation_charges_burned,
496            safe_mode_storage_rebates,
497            safe_mode_non_refundable_storage_fee,
498            epoch_start_timestamp_ms,
499            epoch_duration_ms,
500            min_validator_count,
501            max_validator_count,
502            min_validator_joining_stake,
503            validator_low_stake_threshold,
504            validator_very_low_stake_threshold,
505            validator_low_stake_grace_period,
506            total_stake,
507            committee_members,
508            active_validators: active_validators.into_iter().map(Into::into).collect(),
509            pending_active_validators_id: pending_active_validators_id.into(),
510            pending_active_validators_size,
511            pending_removals,
512            staking_pool_mappings_id: staking_pool_mappings_id.into(),
513            staking_pool_mappings_size,
514            inactive_pools_id: inactive_pools_id.into(),
515            inactive_pools_size,
516            validator_candidates_id: validator_candidates_id.into(),
517            validator_candidates_size,
518            at_risk_validators: at_risk_validators
519                .into_iter()
520                .map(|(address, idx)| (address.into(), idx))
521                .collect(),
522            validator_report_records: validator_report_records
523                .into_iter()
524                .map(|(address, reports)| {
525                    (
526                        address.into(),
527                        reports.into_iter().map(Into::into).collect(),
528                    )
529                })
530                .collect(),
531        }
532    }
533}
534
535pub struct GetCurrentProtocolConfig;
536
537impl ApiEndpoint<RestService> for GetCurrentProtocolConfig {
538    fn method(&self) -> axum::http::Method {
539        axum::http::Method::GET
540    }
541
542    fn path(&self) -> &'static str {
543        "/system/protocol"
544    }
545
546    fn operation(
547        &self,
548        generator: &mut schemars::gen::SchemaGenerator,
549    ) -> openapiv3::v3_1::Operation {
550        OperationBuilder::new()
551            .tag("System")
552            .operation_id("GetCurrentProtocolConfig")
553            .response(
554                200,
555                ResponseBuilder::new()
556                    .json_content::<ProtocolConfigResponse>(generator)
557                    .header::<String>(X_IOTA_MIN_SUPPORTED_PROTOCOL_VERSION, generator)
558                    .header::<String>(X_IOTA_MAX_SUPPORTED_PROTOCOL_VERSION, generator)
559                    .build(),
560            )
561            .build()
562    }
563
564    fn handler(&self) -> RouteHandler<RestService> {
565        RouteHandler::new(self.method(), get_current_protocol_config)
566    }
567}
568
569async fn get_current_protocol_config(
570    accept: AcceptFormat,
571    State(state): State<StateReader>,
572) -> Result<(SupportedProtocolHeaders, Json<ProtocolConfigResponse>)> {
573    match accept {
574        AcceptFormat::Json => {}
575        _ => {
576            return Err(RestError::new(
577                axum::http::StatusCode::BAD_REQUEST,
578                "invalid accept type",
579            ));
580        }
581    }
582
583    let current_protocol_version = state.get_system_state_summary()?.protocol_version;
584
585    let config = ProtocolConfig::get_for_version_if_supported(
586        current_protocol_version.into(),
587        state.inner().get_chain_identifier()?.chain(),
588    )
589    .ok_or_else(|| ProtocolNotFoundError::new(current_protocol_version))?;
590
591    Ok((supported_protocol_headers(), Json(config.into())))
592}
593
594pub struct GetProtocolConfig;
595
596impl ApiEndpoint<RestService> for GetProtocolConfig {
597    fn method(&self) -> axum::http::Method {
598        axum::http::Method::GET
599    }
600
601    fn path(&self) -> &'static str {
602        "/system/protocol/{version}"
603    }
604
605    fn operation(
606        &self,
607        generator: &mut schemars::gen::SchemaGenerator,
608    ) -> openapiv3::v3_1::Operation {
609        OperationBuilder::new()
610            .tag("System")
611            .operation_id("GetProtocolConfig")
612            .path_parameter::<u64>("version", generator)
613            .response(
614                200,
615                ResponseBuilder::new()
616                    .json_content::<ProtocolConfigResponse>(generator)
617                    .header::<String>(X_IOTA_MIN_SUPPORTED_PROTOCOL_VERSION, generator)
618                    .header::<String>(X_IOTA_MAX_SUPPORTED_PROTOCOL_VERSION, generator)
619                    .build(),
620            )
621            .response(404, ResponseBuilder::new().build())
622            .build()
623    }
624
625    fn handler(&self) -> RouteHandler<RestService> {
626        RouteHandler::new(self.method(), get_protocol_config)
627    }
628}
629
630async fn get_protocol_config(
631    Path(version): Path<u64>,
632    accept: AcceptFormat,
633    State(state): State<StateReader>,
634) -> Result<(SupportedProtocolHeaders, Json<ProtocolConfigResponse>)> {
635    match accept {
636        AcceptFormat::Json => {}
637        _ => {
638            return Err(RestError::new(
639                axum::http::StatusCode::BAD_REQUEST,
640                "invalid accept type",
641            ));
642        }
643    }
644
645    let config = ProtocolConfig::get_for_version_if_supported(
646        version.into(),
647        state.inner().get_chain_identifier()?.chain(),
648    )
649    .ok_or_else(|| ProtocolNotFoundError::new(version))?;
650
651    Ok((supported_protocol_headers(), Json(config.into())))
652}
653
654#[derive(Debug)]
655pub struct ProtocolNotFoundError {
656    version: u64,
657}
658
659impl ProtocolNotFoundError {
660    pub fn new(version: u64) -> Self {
661        Self { version }
662    }
663}
664
665impl std::fmt::Display for ProtocolNotFoundError {
666    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
667        write!(f, "Protocol version {} not found", self.version)
668    }
669}
670
671impl std::error::Error for ProtocolNotFoundError {}
672
673impl From<ProtocolNotFoundError> for crate::RestError {
674    fn from(value: ProtocolNotFoundError) -> Self {
675        Self::new(axum::http::StatusCode::NOT_FOUND, value.to_string())
676    }
677}
678
679pub const X_IOTA_MIN_SUPPORTED_PROTOCOL_VERSION: &str = "x-iota-min-supported-protocol-version";
681pub const X_IOTA_MAX_SUPPORTED_PROTOCOL_VERSION: &str = "x-iota-max-supported-protocol-version";
683
684type SupportedProtocolHeaders = [(&'static str, String); 2];
685
686fn supported_protocol_headers() -> SupportedProtocolHeaders {
687    [
688        (
689            X_IOTA_MIN_SUPPORTED_PROTOCOL_VERSION,
690            ProtocolVersion::MIN.as_u64().to_string(),
691        ),
692        (
693            X_IOTA_MAX_SUPPORTED_PROTOCOL_VERSION,
694            ProtocolVersion::MAX.as_u64().to_string(),
695        ),
696    ]
697}
698
699#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema)]
700#[serde(rename = "ProtocolConfig")]
701pub struct ProtocolConfigResponse {
702    #[serde(with = "serde_with::As::<serde_with::DisplayFromStr>")]
703    #[schemars(with = "crate::_schemars::U64")]
704    pub protocol_version: u64,
705    pub feature_flags: BTreeMap<String, bool>,
706    pub attributes: BTreeMap<String, String>,
707}
708
709impl From<ProtocolConfig> for ProtocolConfigResponse {
710    fn from(config: ProtocolConfig) -> Self {
711        let attributes = config
712            .attr_map()
713            .into_iter()
714            .filter_map(|(k, maybe_v)| {
715                maybe_v.map(move |v| {
716                    let v = match v {
717                        ProtocolConfigValue::u16(x) => x.to_string(),
718                        ProtocolConfigValue::u32(y) => y.to_string(),
719                        ProtocolConfigValue::u64(z) => z.to_string(),
720                        ProtocolConfigValue::bool(b) => b.to_string(),
721                    };
722                    (k, v)
723                })
724            })
725            .collect();
726        ProtocolConfigResponse {
727            protocol_version: config.version.as_u64(),
728            attributes,
729            feature_flags: config.feature_map(),
730        }
731    }
732}
733
734pub struct GetGasInfo;
735
736impl ApiEndpoint<RestService> for GetGasInfo {
737    fn method(&self) -> axum::http::Method {
738        axum::http::Method::GET
739    }
740
741    fn path(&self) -> &'static str {
742        "/system/gas"
743    }
744
745    fn operation(
746        &self,
747        generator: &mut schemars::gen::SchemaGenerator,
748    ) -> openapiv3::v3_1::Operation {
749        OperationBuilder::new()
750            .tag("System")
751            .operation_id("GetGasInfo")
752            .response(
753                200,
754                ResponseBuilder::new()
755                    .json_content::<GasInfo>(generator)
756                    .build(),
757            )
758            .build()
759    }
760
761    fn handler(&self) -> RouteHandler<RestService> {
762        RouteHandler::new(self.method(), get_gas_info)
763    }
764}
765
766async fn get_gas_info(
767    accept: AcceptFormat,
768    State(state): State<StateReader>,
769) -> Result<Json<GasInfo>> {
770    match accept {
771        AcceptFormat::Json => {}
772        _ => {
773            return Err(RestError::new(
774                axum::http::StatusCode::BAD_REQUEST,
775                "invalid accept type",
776            ));
777        }
778    }
779
780    let reference_gas_price = state.get_system_state_summary()?.reference_gas_price;
781
782    Ok(Json(GasInfo {
783        reference_gas_price,
784    }))
785}
786
787#[derive(serde::Serialize, serde::Deserialize, JsonSchema)]
788pub struct GasInfo {
789    #[serde(with = "serde_with::As::<serde_with::DisplayFromStr>")]
790    #[schemars(with = "crate::_schemars::U64")]
791    pub reference_gas_price: u64,
792}