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}