Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(analytics): add nft and native token activity endpoints #560

Merged
merged 5 commits into from
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
13 changes: 10 additions & 3 deletions bin/inx-chronicle/src/api/stardust/analytics/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize};

use crate::api::responses::impl_success_response;

/// Response of `GET /api/analytics/addresses[?start_timestamp=<i64>&end_timestamp=<i64>]`.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AddressAnalyticsResponse {
Expand All @@ -21,7 +20,6 @@ pub struct AddressAnalyticsResponse {

impl_success_response!(AddressAnalyticsResponse);

/// Response of `GET /api/analytics/transactions[?start_timestamp=<i64>&end_timestamp=<i64>]`.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OutputAnalyticsResponse {
Expand All @@ -31,7 +29,6 @@ pub struct OutputAnalyticsResponse {

impl_success_response!(OutputAnalyticsResponse);

/// Response of `GET /api/analytics/transactions[?start_timestamp=<i64>&end_timestamp=<i64>]`.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlockAnalyticsResponse {
Expand All @@ -55,6 +52,16 @@ pub struct StorageDepositAnalyticsResponse {

impl_success_response!(StorageDepositAnalyticsResponse);

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OutputDiffAnalyticsResponse {
pub created_count: String,
pub transferred_count: String,
pub burned_count: String,
}

impl_success_response!(OutputDiffAnalyticsResponse);

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RichestAddressesResponse {
Expand Down
32 changes: 30 additions & 2 deletions bin/inx-chronicle/src/api/stardust/analytics/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use chronicle::{
use super::{
extractors::{LedgerIndex, MilestoneRange, RichestAddressesQuery},
responses::{
AddressAnalyticsResponse, BlockAnalyticsResponse, OutputAnalyticsResponse, RichestAddressesResponse,
StorageDepositAnalyticsResponse, TokenDistributionResponse,
AddressAnalyticsResponse, BlockAnalyticsResponse, OutputAnalyticsResponse, OutputDiffAnalyticsResponse,
RichestAddressesResponse, StorageDepositAnalyticsResponse, TokenDistributionResponse,
},
};
use crate::api::{ApiError, ApiResult};
Expand All @@ -38,6 +38,8 @@ pub fn routes() -> Router {
"/activity",
Router::new()
.route("/addresses", get(address_analytics))
.route("/native-tokens", get(foundry_output_analytics))
.route("/nfts", get(nft_output_analytics))
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
.nest(
"/blocks",
Router::new()
Expand Down Expand Up @@ -138,6 +140,32 @@ async fn storage_deposit_analytics(
})
}

async fn nft_output_analytics(
database: Extension<MongoDb>,
MilestoneRange { start_index, end_index }: MilestoneRange,
) -> ApiResult<OutputDiffAnalyticsResponse> {
let res = database.get_nft_output_analytics(start_index, end_index).await?;

Ok(OutputDiffAnalyticsResponse {
created_count: res.created_count.to_string(),
transferred_count: res.transferred_count.to_string(),
burned_count: res.burned_count.to_string(),
})
}

async fn foundry_output_analytics(
database: Extension<MongoDb>,
MilestoneRange { start_index, end_index }: MilestoneRange,
) -> ApiResult<OutputDiffAnalyticsResponse> {
let res = database.get_foundry_output_analytics(start_index, end_index).await?;

Ok(OutputDiffAnalyticsResponse {
created_count: res.created_count.to_string(),
transferred_count: res.transferred_count.to_string(),
burned_count: res.burned_count.to_string(),
})
}

async fn richest_addresses(
database: Extension<MongoDb>,
RichestAddressesQuery { top, ledger_index }: RichestAddressesQuery,
Expand Down
125 changes: 123 additions & 2 deletions src/db/collections/outputs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ impl MongoDb {
"_id": null,
"count": { "$sum": 1 },
"total_value": { "$sum": { "$toDecimal": "$output.amount" } },
}},
} },
doc! { "$project": {
"count": 1,
"total_value": { "$toString": "$total_value" },
Expand Down Expand Up @@ -447,7 +447,7 @@ impl MongoDb {
"_id": null,
"count": { "$sum": 1 },
"total_value": { "$sum": { "$toDecimal": "$output.amount" } },
}},
} },
doc! { "$project": {
"count": 1,
"total_value": { "$toString": "$total_value" },
Expand All @@ -468,6 +468,127 @@ impl MongoDb {
}
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct OutputDiffAnalyticsResult {
pub created_count: u64,
pub transferred_count: u64,
pub burned_count: u64,
}

impl MongoDb {
/// Gathers nft output analytics.
pub async fn get_nft_output_analytics(
&self,
start_index: Option<MilestoneIndex>,
end_index: Option<MilestoneIndex>,
) -> Result<OutputDiffAnalyticsResult, Error> {
Ok(self
.db
.collection::<OutputDiffAnalyticsResult>(OutputDocument::COLLECTION)
.aggregate(
vec![
doc! { "$match": {
"output.kind": "nft",
"metadata.booked.milestone_index": { "$not": { "$gt": start_index } },
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
} },
doc! { "$facet": {
"start_state": [
{ "$match": {
"$or": [
{ "metadata.spent_metadata.spent": null },
{ "metadata.spent_metadata.spent.milestone_index": { "$not": { "$lte": start_index } } },
],
} },
{ "$project": {
"nft_id": "$output.nft_id"
} },
],
"end_state": [
{ "$match": {
"metadata.booked.milestone_index": { "$not": { "$lte": start_index } },
"$or": [
{ "metadata.spent_metadata.spent": null },
{ "metadata.spent_metadata.spent.milestone_index": { "$not": { "$lte": end_index } } },
],
} },
{ "$project": {
"nft_id": "$output.nft_id"
} },
],
} },
doc! { "$project": {
"created_count": { "$size": { "$setDifference": [ "$end_state", "$start_state" ] } },
"transferred_count": { "$size": { "$setIntersection": [ "$start_state", "$end_state" ] } },
"burned_count": { "$size": { "$setDifference": [ "$start_state", "$end_state" ] } },
} },
],
None,
)
.await?
.try_next()
.await?
.map(bson::from_document)
.transpose()?
.unwrap_or_default())
}

/// Gathers foundry output analytics.
pub async fn get_foundry_output_analytics(
&self,
start_index: Option<MilestoneIndex>,
end_index: Option<MilestoneIndex>,
) -> Result<OutputDiffAnalyticsResult, Error> {
Ok(self
.db
.collection::<OutputDiffAnalyticsResult>(OutputDocument::COLLECTION)
.aggregate(
vec![
doc! { "$match": {
"output.kind": "foundry",
"metadata.booked.milestone_index": { "$not": { "$gt": start_index } },
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
} },
doc! { "$facet": {
"start_state": [
{ "$match": {
"$or": [
{ "metadata.spent_metadata.spent": null },
{ "metadata.spent_metadata.spent.milestone_index": { "$not": { "$lte": start_index } } },
],
} },
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
{ "$project": {
"foundry_id": "$output.foundry_id"
} },
],
"end_state": [
{ "$match": {
"metadata.booked.milestone_index": { "$not": { "$lte": start_index } },
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
"$or": [
{ "metadata.spent_metadata.spent": null },
{ "metadata.spent_metadata.spent.milestone_index": { "$not": { "$lte": end_index } } },
],
} },
{ "$project": {
"foundry_id": "$output.foundry_id"
} },
],
} },
doc! { "$project": {
"created_count": { "$size": { "$setDifference": [ "$end_state", "$start_state" ] } },
"transferred_count": { "$size": { "$setIntersection": [ "$start_state", "$end_state" ] } },
"burned_count": { "$size": { "$setDifference": [ "$start_state", "$end_state" ] } },
} },
],
None,
)
.await?
.try_next()
.await?
.map(bson::from_document)
.transpose()?
.unwrap_or_default())
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StorageDepositAnalyticsResult {
pub output_count: u64,
Expand Down