Skip to content
32 changes: 12 additions & 20 deletions crates/server/src/handlers/blocks/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,27 +146,19 @@ pub fn convert_digest_items_to_logs(items: &[DigestItem]) -> Vec<DigestLog> {
pub async fn get_validators_at_block(
client_at_block: &BlockClient,
) -> Result<Vec<AccountId32>, GetBlockError> {
// Use typed dynamic storage to decode as raw account bytes, then convert to AccountId32
// Note: AccountId32 from sp_runtime doesn't implement IntoVisitor, so we decode as [u8; 32]
let addr = subxt::dynamic::storage::<(), Vec<[u8; 32]>>("Session", "Validators");
let validators_raw = client_at_block
.storage()
.fetch(addr, ())
.await?
.decode()
.map_err(|e| {
tracing::debug!("Failed to decode validators: {}", e);
GetBlockError::StorageDecodeFailed(parity_scale_codec::Error::from(
use crate::handlers::runtime_queries::session;

session::get_validators(client_at_block).await.map_err(|e| {
tracing::debug!("Failed to get validators: {}", e);
match e {
session::SessionStorageError::NoValidatorsFound => GetBlockError::StorageDecodeFailed(
parity_scale_codec::Error::from("no validators found in storage"),
),
_ => GetBlockError::StorageDecodeFailed(parity_scale_codec::Error::from(
"Failed to decode validators",
))
})?;
let validators: Vec<AccountId32> = validators_raw.into_iter().map(AccountId32::from).collect();

if validators.is_empty() {
return Err(parity_scale_codec::Error::from("no validators found in storage").into());
}

Ok(validators)
)),
}
})
}

/// Extract author ID from block header digest logs by mapping authority index to validator
Expand Down
6 changes: 3 additions & 3 deletions crates/server/src/handlers/blocks/processing/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// and could potentially simplify most of the code in this module.
// See: https://github.com/polkadot-api/polkadot-rest-api/pull/XXX#discussion_rXXXXXXXXX

use crate::handlers::runtime_queries::system;
use crate::state::AppState;
use serde_json::Value;

Expand Down Expand Up @@ -228,9 +229,8 @@ async fn fetch_block_events_impl(
let metadata = client_at_block.metadata();
let resolver = metadata.types();

// Use dynamic storage address for System::Events
let addr = subxt::dynamic::storage::<(), scale_value::Value>("System", "Events");
let events_value = client_at_block.storage().fetch(addr, ()).await?;
// Fetch events using runtime_queries module
let events_value = system::get_events_raw(client_at_block).await?;

// Decode events once using the visitor pattern which provides all needed data:
// phase, pallet_name, event_name, and typed fields
Expand Down
18 changes: 18 additions & 0 deletions crates/server/src/handlers/blocks/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,24 @@ impl From<OnlineClientAtBlockError> for GetBlockError {
}
}

impl From<crate::handlers::runtime_queries::system::SystemStorageError> for GetBlockError {
fn from(err: crate::handlers::runtime_queries::system::SystemStorageError) -> Self {
match err {
crate::handlers::runtime_queries::system::SystemStorageError::StorageError(e) => {
GetBlockError::StorageFetchFailed(e)
}
crate::handlers::runtime_queries::system::SystemStorageError::StorageFetchFailed {
entry,
} => GetBlockError::StorageFetchFailed(
subxt::error::StorageError::StorageEntryNotFound {
pallet_name: "System".into(),
entry_name: entry.into(),
},
),
}
}
}

impl From<utils::ResolveClientAtBlockError> for GetBlockError {
fn from(err: utils::ResolveClientAtBlockError) -> Self {
match err {
Expand Down
6 changes: 6 additions & 0 deletions crates/server/src/handlers/coretime/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ pub enum CoretimeError {
pallet: &'static str,
entry: &'static str,
},

#[error("Storage query failed: {details}")]
StorageQueryFailed { details: String },
}

impl IntoResponse for CoretimeError {
Expand Down Expand Up @@ -253,6 +256,9 @@ impl IntoResponse for CoretimeError {
CoretimeError::StorageItemNotAvailableAtBlock { .. } => {
(StatusCode::NOT_FOUND, self.to_string())
}
CoretimeError::StorageQueryFailed { .. } => {
(StatusCode::INTERNAL_SERVER_ERROR, self.to_string())
}
};

// Match Sidecar's response format: { code: number, message: string }
Expand Down
221 changes: 20 additions & 201 deletions crates/server/src/handlers/coretime/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::extractors::JsonQuery;
use crate::handlers::coretime::common::{
AtResponse, CoretimeError, CoretimeQueryParams, has_broker_pallet, has_coretime_pallet,
};
use crate::handlers::runtime_queries::{broker, coretime, parachain_system};
use crate::state::AppState;
use crate::utils::{BlockId, resolve_block};
use axum::{
Expand Down Expand Up @@ -226,18 +227,24 @@ async fn handle_coretime_chain_info(
return Err(CoretimeError::BrokerPalletNotFound);
}

// Fetch all data in parallel using subxt's decode_as for type-safe decoding
// Fetch all data in parallel using runtime_queries modules
let (config_result, sale_result, status_result, timeslice_period_result, relay_block_result) = tokio::join!(
fetch_configuration(client_at_block),
fetch_sale_info(client_at_block),
fetch_status(client_at_block),
fetch_timeslice_period(client_at_block),
fetch_relay_block_number(client_at_block)
broker::get_configuration::<ConfigRecord>(client_at_block),
broker::get_sale_info::<SaleInfoRecord>(client_at_block),
broker::get_status::<StatusRecord>(client_at_block),
broker::get_timeslice_period(client_at_block),
parachain_system::get_last_relay_block_number(client_at_block)
);

let config = config_result?;
let sale_info = sale_result?;
let status = status_result?;
let config = config_result.map_err(|e| CoretimeError::StorageQueryFailed {
details: e.to_string(),
})?;
let sale_info = sale_result.map_err(|e| CoretimeError::StorageQueryFailed {
details: e.to_string(),
})?;
let status = status_result.map_err(|e| CoretimeError::StorageQueryFailed {
details: e.to_string(),
})?;
let timeslice_period = timeslice_period_result.unwrap_or(80); // Default to 80 if not found
// Use relay chain block number for price calculation since sale_start/leadin_length
// are stored as relay block numbers. Fall back to parachain block number if unavailable.
Expand Down Expand Up @@ -306,11 +313,11 @@ async fn handle_relay_chain_info(
return Err(CoretimeError::CoretimePalletNotFound);
}

// Fetch relay chain coretime info
// Fetch relay chain coretime info using runtime_queries modules
let (broker_id, storage_version, max_historical_revenue) = tokio::join!(
fetch_broker_id(client_at_block),
fetch_storage_version_decoded(client_at_block),
fetch_max_historical_revenue(client_at_block)
coretime::get_broker_id(client_at_block),
coretime::get_assignment_provider_storage_version(client_at_block),
coretime::get_max_historical_revenue(client_at_block)
);

let response = CoretimeRelayInfoResponse {
Expand All @@ -323,194 +330,6 @@ async fn handle_relay_chain_info(
Ok((StatusCode::OK, Json(response)).into_response())
}

/// Fetches and decodes Configuration from Broker pallet directly into ConfigRecord.
/// Uses subxt dynamic storage with DecodeAsType for type-safe decoding.
async fn fetch_configuration(
client_at_block: &OnlineClientAtBlock<SubstrateConfig>,
) -> Result<Option<ConfigRecord>, CoretimeError> {
let config_addr = subxt::dynamic::storage::<(), ConfigRecord>("Broker", "Configuration");

match client_at_block.storage().fetch(config_addr, ()).await {
Ok(storage_value) => {
let decoded =
storage_value
.decode()
.map_err(|e| CoretimeError::StorageDecodeFailed {
pallet: "Broker",
entry: "Configuration",
details: e.to_string(),
})?;
Ok(Some(decoded))
}
Err(subxt::error::StorageError::StorageEntryNotFound { .. }) => {
tracing::debug!("Could not find Broker.Configuration storage entry.");
Ok(None)
}
Err(e) => {
tracing::debug!(
"Failed to retrieve Broker.Configuration: {:?}",
format!("{e}")
);
Ok(None)
}
}
}

/// Fetches and decodes SaleInfo from Broker pallet directly into SaleInfoRecord.
/// Uses subxt dynamic storage with DecodeAsType for type-safe decoding.
async fn fetch_sale_info(
client_at_block: &OnlineClientAtBlock<SubstrateConfig>,
) -> Result<Option<SaleInfoRecord>, CoretimeError> {
let sale_addr = subxt::dynamic::storage::<(), SaleInfoRecord>("Broker", "SaleInfo");

match client_at_block.storage().fetch(sale_addr, ()).await {
Ok(storage_value) => {
let decoded =
storage_value
.decode()
.map_err(|e| CoretimeError::StorageDecodeFailed {
pallet: "Broker",
entry: "SaleInfo",
details: e.to_string(),
})?;
Ok(Some(decoded))
}
Err(subxt::error::StorageError::StorageEntryNotFound { .. }) => {
tracing::debug!("Could not find Broker.SaleInfo storage entry.");
Ok(None)
}
Err(e) => {
tracing::debug!("Failed to retrieve Broker.SaleInfo: {:?}", format!("{e}"));
Ok(None)
}
}
}

/// Fetches and decodes Status from Broker pallet directly into StatusRecord.
/// Uses subxt dynamic storage with DecodeAsType for type-safe decoding.
async fn fetch_status(
client_at_block: &OnlineClientAtBlock<SubstrateConfig>,
) -> Result<Option<StatusRecord>, CoretimeError> {
let status_addr = subxt::dynamic::storage::<(), StatusRecord>("Broker", "Status");

match client_at_block.storage().fetch(status_addr, ()).await {
Ok(storage_value) => {
let decoded =
storage_value
.decode()
.map_err(|e| CoretimeError::StorageDecodeFailed {
pallet: "Broker",
entry: "Status",
details: e.to_string(),
})?;
Ok(Some(decoded))
}
Err(subxt::error::StorageError::StorageEntryNotFound { .. }) => {
tracing::debug!("Could not find Broker.Status storage entry.");
Ok(None)
}
Err(e) => {
tracing::debug!("Failed to retrieve Broker.Status: {:?}", format!("{e}"));
Ok(None)
}
}
}

/// Fetches the relay chain block number from ParachainSystem pallet.
/// On coretime parachains, sale_start and leadin_length are stored as relay chain
/// block numbers, so we need the relay block number for accurate price calculation.
async fn fetch_relay_block_number(
client_at_block: &OnlineClientAtBlock<SubstrateConfig>,
) -> Result<Option<u32>, CoretimeError> {
let addr = subxt::dynamic::storage::<(), u32>("ParachainSystem", "LastRelayChainBlockNumber");

match client_at_block.storage().fetch(addr, ()).await {
Ok(storage_value) => {
let decoded =
storage_value
.decode()
.map_err(|e| CoretimeError::StorageDecodeFailed {
pallet: "ParachainSystem",
entry: "LastRelayChainBlockNumber",
details: e.to_string(),
})?;
Ok(Some(decoded))
}
Err(e) => {
tracing::debug!(
"Failed to retrieve ParachainSystem.LastRelayChainBlockNumber: {:?}",
format!("{e}")
);
Ok(None)
}
}
}

/// Fetches TimeslicePeriod constant from Broker pallet.
async fn fetch_timeslice_period(
client_at_block: &OnlineClientAtBlock<SubstrateConfig>,
) -> Result<u32, CoretimeError> {
let addr = subxt::dynamic::constant::<u32>("Broker", "TimeslicePeriod");
let value = client_at_block.constants().entry(addr).map_err(|_| {
CoretimeError::ConstantFetchFailed {
pallet: "Broker",
constant: "TimeslicePeriod",
}
})?;

Ok(value)
}

/// Fetches BrokerId constant from Coretime pallet.
async fn fetch_broker_id(
client_at_block: &OnlineClientAtBlock<SubstrateConfig>,
) -> Result<Option<u32>, CoretimeError> {
let addr = subxt::dynamic::constant::<u32>("Coretime", "BrokerId");
let value = client_at_block.constants().entry(addr).map_err(|_| {
CoretimeError::ConstantFetchFailed {
pallet: "Coretime",
constant: "BrokerId",
}
})?;
Ok(Some(value))
}

async fn fetch_storage_version_decoded(
client_at_block: &OnlineClientAtBlock<SubstrateConfig>,
) -> Result<Option<u16>, CoretimeError> {
let version = client_at_block
.storage()
.storage_version("CoretimeAssignmentProvider")
.await
.map_err(|_| CoretimeError::StorageVersionFetchFailed {
pallet: "CoretimeAssignmentProvider",
})?;

Ok(Some(version))
}

/// Fetches MaxHistoricalRevenue constant from OnDemandAssignmentProvider.
async fn fetch_max_historical_revenue(
client_at_block: &OnlineClientAtBlock<SubstrateConfig>,
) -> Result<Option<u32>, CoretimeError> {
let addr = subxt::dynamic::constant::<u32>("OnDemand", "MaxHistoricalRevenue");
if let Ok(value) = client_at_block.constants().entry(addr) {
Ok(Some(value))
} else {
let legacy_addr =
subxt::dynamic::constant::<u32>("OnDemandAssignmentProvider", "MaxHistoricalRevenue");
let value = client_at_block
.constants()
.entry(legacy_addr)
.map_err(|_| CoretimeError::ConstantFetchFailed {
pallet: "OnDemandAssignmentProvider",
constant: "MaxHistoricalRevenue",
})?;

Ok(Some(value))
}
}

// ============================================================================
// Value Extraction Helpers
// ============================================================================
Expand Down
Loading