Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
### Added

- Gateway: per-relay `blockfrost_gateway_relay_healthy`, `blockfrost_gateway_relay_data_node_up`, and `blockfrost_gateway_relay_info` metrics in `GET /metrics` (and the same data points in `GET /stats`)
- New endpoints proxied to the data node: `/accounts/{stake_address}/utxos`, `/addresses/{address}`, and `/blocks/slot/{slot_number}`
- New endpoints proxied to the data node: `/accounts/{stake_address}/utxos`, `/accounts/{stake_address}/transactions`, `/addresses/{address}`, `/blocks/slot/{slot_number}`, `/pools/retiring`, `/blocks/{hash_or_number}/txs/cbor`, and `/blocks/latest/txs/cbor`
- `/accounts/{stake_address}/*` endpoints now accept the CIP-19 credential forms (`stake_vk`, `stake_vkh`, `script`) in place of a canonical stake address, resolving them via the data node
- `--max-response-body-bytes` to configure the maximum proxied response body size (default 10 MiB)

### Fixed
Expand Down
6 changes: 5 additions & 1 deletion crates/api_provider/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use blockfrost_openapi::models::{
account_mir_content_inner::AccountMirContentInner,
account_registration_content_inner::AccountRegistrationContentInner,
account_reward_content_inner::AccountRewardContentInner,
account_transactions_content_inner::AccountTransactionsContentInner,
account_utxo_content_inner::AccountUtxoContentInner,
account_withdrawal_content_inner::AccountWithdrawalContentInner,
address_content::AddressContent, address_content_extended::AddressContentExtended,
Expand All @@ -18,7 +19,8 @@ use blockfrost_openapi::models::{
asset_addresses_inner::AssetAddressesInner, asset_history_inner::AssetHistoryInner,
asset_policy_inner::AssetPolicyInner, asset_transactions_inner::AssetTransactionsInner,
assets_inner::AssetsInner, block_content::BlockContent,
block_content_addresses_inner::BlockContentAddressesInner, drep::Drep,
block_content_addresses_inner::BlockContentAddressesInner,
block_content_txs_cbor_inner::BlockContentTxsCborInner, drep::Drep,
drep_delegators_inner::DrepDelegatorsInner, drep_metadata::DrepMetadata,
drep_updates_inner::DrepUpdatesInner, drep_votes_inner::DrepVotesInner,
epoch_content::EpochContent, epoch_param_content::EpochParamContent,
Expand Down Expand Up @@ -65,6 +67,7 @@ pub type AccountsHistoryResponse = Vec<AccountHistoryContentInner>;
pub type AccountsMirResponse = Vec<AccountMirContentInner>;
pub type AccountsUtxosResponse = Vec<AccountUtxoContentInner>;
pub type AccountsWithdrawalsResponse = Vec<AccountWithdrawalContentInner>;
pub type AccountsTransactionsResponse = Vec<AccountTransactionsContentInner>;

// addresses
pub type AddressesResponse = AddressContent;
Expand All @@ -87,6 +90,7 @@ pub type BlocksSingleResponse = BlockContent;
pub type BlocksResponse = Vec<BlockContent>;
pub type BlocksAddressesExtendedResponse = Vec<AddressContentExtended>;
pub type BlocksAddressesContentResponse = BlockContentAddressesInner;
pub type BlocksTxsCborResponse = Vec<BlockContentTxsCborInner>;

// epochs
pub type EpochsParamResponse = EpochParamContent;
Expand Down
13 changes: 12 additions & 1 deletion crates/data_node/src/api/accounts.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::client::DataNode;
use bf_api_provider::types::{
AccountsAddressesResponse, AccountsDelegationsResponse, AccountsRegistrationsResponse,
AccountsResponse, AccountsRewardsResponse, AccountsUtxosResponse, AccountsWithdrawalsResponse,
AccountsResponse, AccountsRewardsResponse, AccountsTransactionsResponse, AccountsUtxosResponse,
AccountsWithdrawalsResponse,
};
use bf_common::{pagination::Pagination, types::ApiResult};

Expand Down Expand Up @@ -81,4 +82,14 @@ impl DataNodeAccounts<'_> {

self.inner.client.get(&path, Some(pagination)).await
}

pub async fn transactions(
&self,
stake_address: &str,
pagination: &Pagination,
) -> ApiResult<AccountsTransactionsResponse> {
let path = format!("accounts/{stake_address}/transactions");

self.inner.client.get(&path, Some(pagination)).await
}
}
22 changes: 21 additions & 1 deletion crates/data_node/src/api/blocks.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::client::DataNode;
use bf_api_provider::types::{BlocksResponse, BlocksSingleResponse};
use bf_api_provider::types::{BlocksResponse, BlocksSingleResponse, BlocksTxsCborResponse};
use bf_common::{pagination::Pagination, types::ApiResult};

pub struct DataNodeBlocks<'a> {
Expand All @@ -21,6 +21,16 @@ impl DataNodeBlocks<'_> {
self.inner.client.get("blocks/latest/txs", None).await
}

pub async fn latest_txs_cbor(
&self,
pagination: &Pagination,
) -> ApiResult<BlocksTxsCborResponse> {
self.inner
.client
.get("blocks/latest/txs/cbor", Some(pagination))
.await
}

pub async fn by(&self, hash_or_number: &str) -> ApiResult<BlocksSingleResponse> {
let path = format!("blocks/{hash_or_number}");

Expand All @@ -37,6 +47,16 @@ impl DataNodeBlocks<'_> {
self.inner.client.get(&path, Some(pagination)).await
}

pub async fn txs_cbor(
&self,
hash_or_number: &str,
pagination: &Pagination,
) -> ApiResult<BlocksTxsCborResponse> {
let path = format!("blocks/{hash_or_number}/txs/cbor");

self.inner.client.get(&path, Some(pagination)).await
}

pub async fn previous(
&self,
hash_or_number: &str,
Expand Down
9 changes: 8 additions & 1 deletion crates/data_node/src/api/pools.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::client::DataNode;
use bf_api_provider::types::{
PoolsDelegatorsResponse, PoolsHistoryResponse, PoolsListExtendedResponse,
PoolsMetadataResponse, PoolsSingleResponse,
PoolsMetadataResponse, PoolsRetiresResponse, PoolsSingleResponse,
};
use bf_common::{pagination::Pagination, types::ApiResult};

Expand All @@ -23,6 +23,13 @@ impl DataNodePools<'_> {
.await
}

pub async fn retiring(&self, pagination: &Pagination) -> ApiResult<PoolsRetiresResponse> {
self.inner
.client
.get("pools/retiring", Some(pagination))
.await
}

pub async fn delegators(
&self,
pool_id: &str,
Expand Down
4 changes: 4 additions & 0 deletions crates/integration_tests/tests/data/supported_endpoints.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
"/accounts/{stake_address}/registrations",
"/accounts/{stake_address}/withdrawals",
"/accounts/{stake_address}/utxos",
"/accounts/{stake_address}/transactions",
"/accounts/{stake_address}",
"/assets/{asset}",
"/pools/extended",
"/pools/retiring",
"/pools/{pool_id}",
"/pools/{pool_id}/metadata",
"/pools/{pool_id}/delegators",
Expand All @@ -34,7 +36,9 @@
"/blocks/{hash_or_number}/next",
"/blocks/{hash_or_number}/previous",
"/blocks/{hash_or_number}/txs",
"/blocks/{hash_or_number}/txs/cbor",
"/blocks/latest/txs",
"/blocks/latest/txs/cbor",
"/blocks/slot/{slot_number}",
"/txs/{hash}/delegations",
"/epochs/{number}/parameters",
Expand Down
47 changes: 35 additions & 12 deletions crates/platform/src/addresses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,24 @@ impl fmt::Display for AddressType {
}

pub fn is_stake_address_valid(input: &str, network: &Network) -> Result<bool, BlockfrostError> {
let (hrp, _) = bech32::decode(input).map_err(|_| BlockfrostError::invalid_stake_address())?;
let prefix_str = match hrp.as_str() {
"stake" => Ok("stake"),
"stake_test" => Ok("stake_test"),
_ => Err(BlockfrostError::invalid_stake_address()),
}?;
let (hrp, data) =
bech32::decode(input).map_err(|_| BlockfrostError::invalid_stake_address())?;

match network {
Network::Mainnet if prefix_str == "stake" => Ok(true),
Network::Preprod | Network::Preview | Network::Custom if prefix_str == "stake_test" => {
Ok(true)
},
_ => Ok(false),
match hrp.as_str() {
// Canonical stake addresses carry the network in their header, so they
// must match the configured network.
"stake" => Ok(*network == Network::Mainnet),
"stake_test" => Ok(matches!(
network,
Network::Preprod | Network::Preview | Network::Custom
)),
// CIP-19 stake credentials (verification key, key hash or script hash)
// are network-agnostic; the data node resolves them against its own
// network. `stake_vkh` and `script` are 28-byte credentials, while
// `stake_vk` is a 32-byte Ed25519 verification key.
"stake_vkh" | "script" => Ok(data.len() == 28),
"stake_vk" => Ok(data.len() == 32),
_ => Err(BlockfrostError::invalid_stake_address()),
}
}

Expand Down Expand Up @@ -250,6 +255,24 @@ mod tests {
Network::Custom,
false
)]
#[case(
"CIP-19 stake_vkh credential is network-agnostic",
"stake_vkh18ncsfsl5ngpn0u3edp30mym5e0c795msf37yrhlm03hkjdcyt7g",
Network::Preview,
true
)]
#[case(
"CIP-19 stake_vk verification key is network-agnostic",
"stake_vk1px4j0r2fk7ux5p23shz8f3y5y2qam7s954rgf3lg5merqcj6aetsft99wu",
Network::Mainnet,
true
)]
#[case(
"CIP-19 script credential is network-agnostic",
"script1prjh5fpchv23u4ufyy7gs27tjjmqp0dtz2vwhkxvmvay6ehj87m",
Network::Preview,
true
)]
fn test_validate_stake_address(
#[case] description: &str,
#[case] input: &str,
Expand Down
1 change: 1 addition & 0 deletions crates/platform/src/api/accounts/stake_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ pub mod mirs;
pub mod registrations;
pub mod rewards;
pub mod root;
pub mod transactions;
pub mod utxos;
pub mod withdrawals;
25 changes: 25 additions & 0 deletions crates/platform/src/api/accounts/stake_address/transactions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::{
accounts::{AccountData, AccountsPath},
server::state::AppState,
};
use axum::extract::{Path, Query, State};
use bf_api_provider::types::AccountsTransactionsResponse;
use bf_common::{
pagination::{Pagination, PaginationQuery},
types::ApiResult,
};

pub async fn route(
Path(path): Path<AccountsPath>,
State(state): State<AppState>,
Query(pagination_query): Query<PaginationQuery>,
) -> ApiResult<AccountsTransactionsResponse> {
let account = AccountData::from_account_path(path.stake_address, &state.config.network)?;
let pagination = Pagination::from_query(pagination_query)?;
let data_node = state.data_node()?;

data_node
.accounts()
.transactions(&account.stake_address, &pagination)
.await
}
21 changes: 2 additions & 19 deletions crates/platform/src/api/blocks/hash_or_number/txs.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,2 @@
use crate::blocks::{BlockData, BlocksPath};
use crate::{api::ApiResult, server::state::AppState};
use axum::extract::{Path, Query, State};
use bf_common::pagination::{Pagination, PaginationQuery};

pub async fn route(
State(state): State<AppState>,
Query(pagination_query): Query<PaginationQuery>,
Path(blocks_path): Path<BlocksPath>,
) -> ApiResult<Vec<String>> {
let block_data = BlockData::from_string(blocks_path.hash_or_number)?;
let pagination = Pagination::from_query(pagination_query)?;
let data_node = state.data_node()?;

data_node
.blocks()
.txs(&block_data.hash_or_number, &pagination)
.await
}
pub mod cbor;
pub mod root;
20 changes: 20 additions & 0 deletions crates/platform/src/api/blocks/hash_or_number/txs/cbor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use crate::blocks::{BlockData, BlocksPath};
use crate::{api::ApiResult, server::state::AppState};
use axum::extract::{Path, Query, State};
use bf_api_provider::types::BlocksTxsCborResponse;
use bf_common::pagination::{Pagination, PaginationQuery};

pub async fn route(
State(state): State<AppState>,
Query(pagination_query): Query<PaginationQuery>,
Path(blocks_path): Path<BlocksPath>,
) -> ApiResult<BlocksTxsCborResponse> {
let block_data = BlockData::from_string(blocks_path.hash_or_number)?;
let pagination = Pagination::from_query(pagination_query)?;
let data_node = state.data_node()?;

data_node
.blocks()
.txs_cbor(&block_data.hash_or_number, &pagination)
.await
}
19 changes: 19 additions & 0 deletions crates/platform/src/api/blocks/hash_or_number/txs/root.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::blocks::{BlockData, BlocksPath};
use crate::{api::ApiResult, server::state::AppState};
use axum::extract::{Path, Query, State};
use bf_common::pagination::{Pagination, PaginationQuery};

pub async fn route(
State(state): State<AppState>,
Query(pagination_query): Query<PaginationQuery>,
Path(blocks_path): Path<BlocksPath>,
) -> ApiResult<Vec<String>> {
let block_data = BlockData::from_string(blocks_path.hash_or_number)?;
let pagination = Pagination::from_query(pagination_query)?;
let data_node = state.data_node()?;

data_node
.blocks()
.txs(&block_data.hash_or_number, &pagination)
.await
}
10 changes: 2 additions & 8 deletions crates/platform/src/api/blocks/latest/txs.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,2 @@
use crate::{api::ApiResult, server::state::AppState};
use axum::extract::State;

pub async fn route(State(state): State<AppState>) -> ApiResult<Vec<String>> {
let data_node = state.data_node()?;

data_node.blocks().latest_txs().await
}
pub mod cbor;
pub mod root;
14 changes: 14 additions & 0 deletions crates/platform/src/api/blocks/latest/txs/cbor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::{api::ApiResult, server::state::AppState};
use axum::extract::{Query, State};
use bf_api_provider::types::BlocksTxsCborResponse;
use bf_common::pagination::{Pagination, PaginationQuery};

pub async fn route(
State(state): State<AppState>,
Query(pagination_query): Query<PaginationQuery>,
) -> ApiResult<BlocksTxsCborResponse> {
let pagination = Pagination::from_query(pagination_query)?;
let data_node = state.data_node()?;

data_node.blocks().latest_txs_cbor(&pagination).await
}
8 changes: 8 additions & 0 deletions crates/platform/src/api/blocks/latest/txs/root.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use crate::{api::ApiResult, server::state::AppState};
use axum::extract::State;

pub async fn route(State(state): State<AppState>) -> ApiResult<Vec<String>> {
let data_node = state.data_node()?;

data_node.blocks().latest_txs().await
}
14 changes: 11 additions & 3 deletions crates/platform/src/api/pools/retiring.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
use crate::{BlockfrostError, api::ApiResult};
use crate::{api::ApiResult, server::state::AppState};
use axum::extract::{Query, State};
use bf_api_provider::types::PoolsRetiresResponse;
use bf_common::pagination::{Pagination, PaginationQuery};

pub async fn route() -> ApiResult<PoolsRetiresResponse> {
Err(BlockfrostError::not_found())
pub async fn route(
State(state): State<AppState>,
Query(pagination_query): Query<PaginationQuery>,
) -> ApiResult<PoolsRetiresResponse> {
let data_node = state.data_node()?;
Comment thread
michalrus marked this conversation as resolved.
let pagination = Pagination::from_query(pagination_query)?;

data_node.pools().retiring(&pagination).await
}
7 changes: 5 additions & 2 deletions crates/platform/src/server/routes/hidden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub fn get_hidden_api_routes(enable_metrics: bool) -> Router<AppState> {
.route("/accounts/{stake_address}/addresses/assets", get(accounts::stake_address::addresses::assets::route))
.route("/accounts/{stake_address}/addresses/total", get(accounts::stake_address::addresses::total::route))
.route("/accounts/{stake_address}/utxos", get(accounts::stake_address::utxos::route))
.route("/accounts/{stake_address}/transactions", get(accounts::stake_address::transactions::route))

// addresses
.route("/addresses/{address}", get(addresses::address::root::route))
Expand All @@ -47,12 +48,14 @@ pub fn get_hidden_api_routes(enable_metrics: bool) -> Router<AppState> {
.route("/blocks/epoch/{epoch_number}/slot/{slot_number}", get(blocks::epoch::epoch_number::slot::slot_number::route))
.route("/blocks/slot/{slot_number}", get(blocks::slot::slot_number::route))
.route("/blocks/latest", get(blocks::latest::root::route))
.route("/blocks/latest/txs", get(blocks::latest::txs::route))
.route("/blocks/latest/txs", get(blocks::latest::txs::root::route))
.route("/blocks/latest/txs/cbor", get(blocks::latest::txs::cbor::route))
.route("/blocks/{hash_or_number}", get(blocks::hash_or_number::root::route))
.route("/blocks/{hash_or_number}/addresses", get(blocks::hash_or_number::addresses::route))
.route("/blocks/{hash_or_number}/next", get(blocks::hash_or_number::next::route))
.route("/blocks/{hash_or_number}/previous", get(blocks::hash_or_number::previous::route))
.route("/blocks/{hash_or_number}/txs", get(blocks::hash_or_number::txs::route))
.route("/blocks/{hash_or_number}/txs", get(blocks::hash_or_number::txs::root::route))
.route("/blocks/{hash_or_number}/txs/cbor", get(blocks::hash_or_number::txs::cbor::route))

// epochs
.route("/epochs/latest", get(epochs::latest::root::route))
Expand Down
Loading