Skip to content

Commit

Permalink
Merge branch 'dev' into feat/nova-visualizer/add-emitter-tilting
Browse files Browse the repository at this point in the history
  • Loading branch information
begonaalvarezd authored Feb 26, 2024
2 parents 2d7b6a1 + ae55f70 commit c21b1d4
Show file tree
Hide file tree
Showing 42 changed files with 1,202 additions and 162 deletions.
5 changes: 5 additions & 0 deletions api/src/models/api/nova/ISearchResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ export interface ISearchResponse extends IResponse {
* Nft id if it was found.
*/
nftId?: string;

/**
* Transaction included block.
*/
transactionBlock?: Block;
}
11 changes: 11 additions & 0 deletions api/src/models/api/nova/ISlotRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface ISlotRequest {
/**
* The network to search on.
*/
network: string;

/**
* The slot index to get the details for.
*/
slotIndex: string;
}
10 changes: 10 additions & 0 deletions api/src/models/api/nova/ISlotResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// eslint-disable-next-line import/no-unresolved
import { SlotCommitment } from "@iota/sdk-nova";
import { IResponse } from "./IResponse";

export interface ISlotResponse extends IResponse {
/**
* The deserialized slot.
*/
slot?: SlotCommitment;
}
11 changes: 11 additions & 0 deletions api/src/models/api/nova/ITransactionDetailsRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface ITransactionDetailsRequest {
/**
* The network to search on.
*/
network: string;

/**
* The transaction id to get the details for.
*/
transactionId: string;
}
11 changes: 11 additions & 0 deletions api/src/models/api/nova/ITransactionDetailsResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable import/no-unresolved */
/* eslint-disable @typescript-eslint/no-redundant-type-constituents */
import { Block } from "@iota/sdk-nova";
import { IResponse } from "./IResponse";

export interface ITransactionDetailsResponse extends IResponse {
/**
* Transaction included block.
*/
block?: Block;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* eslint-disable import/no-unresolved */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { SlotCommitment } from "@iota/sdk-nova";
import { IResponse } from "../../IResponse";

export enum SlotCommitmentStatus {
Committed = "committed",
Finalized = "finalized",
}

export interface ISlotCommitmentWrapper {
status: SlotCommitmentStatus;
slotCommitment: SlotCommitment;
}

export interface ILatestSlotCommitmentResponse extends IResponse {
slotCommitments: ISlotCommitmentWrapper[];
}
13 changes: 13 additions & 0 deletions api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,12 @@ export const routes: IRoute[] = [
folder: "nova/account/foundries",
func: "get",
},
{
path: "/nova/transaction/:network/:transactionId",
method: "get",
folder: "nova/transaction",
func: "get",
},
{
path: "/nova/account/congestion/:network/:accountId",
method: "get",
Expand All @@ -250,4 +256,11 @@ export const routes: IRoute[] = [
},
{ path: "/nova/block/:network/:blockId", method: "get", folder: "nova/block", func: "get" },
{ path: "/nova/block/metadata/:network/:blockId", method: "get", folder: "nova/block/metadata", func: "get" },
{
path: "/nova/commitment/latest/:network",
method: "get",
folder: "nova/commitment/latest",
func: "get",
},
{ path: "/nova/slot/:network/:slotIndex", method: "get", folder: "nova/slot", func: "get" },
];
30 changes: 30 additions & 0 deletions api/src/routes/nova/commitment/latest/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ServiceFactory } from "../../../../factories/serviceFactory";
import { ILatestSlotCommitmentResponse } from "../../../../models/api/nova/commitment/ILatestSlotCommitmentsResponse";
import { IConfiguration } from "../../../../models/configuration/IConfiguration";
import { NOVA } from "../../../../models/db/protocolVersion";
import { NetworkService } from "../../../../services/networkService";
import { NovaFeed } from "../../../../services/nova/feed/novaFeed";
import { ValidationHelper } from "../../../../utils/validationHelper";

/**
* Get the latest slot commitments.
* @param _ The configuration.
* @param request The request.
* @param request.network The network in context.
* @returns The response.
*/
export async function get(_: IConfiguration, request: { network: string }): Promise<ILatestSlotCommitmentResponse> {
const networkService = ServiceFactory.get<NetworkService>("network");
const networks = networkService.networkNames();
ValidationHelper.oneOf(request.network, networks, "network");
const networkConfig = networkService.get(request.network);

if (networkConfig.protocolVersion !== NOVA) {
return { error: "Endpoint available only on Nova networks.", slotCommitments: [] };
}

const feedService = ServiceFactory.get<NovaFeed>(`feed-${request.network}`);
const slotCommitments = feedService.getLatestSlotCommitments;

return { slotCommitments };
}
30 changes: 30 additions & 0 deletions api/src/routes/nova/slot/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ServiceFactory } from "../../../factories/serviceFactory";
import { ISlotRequest } from "../../../models/api/nova/ISlotRequest";
import { ISlotResponse } from "../../../models/api/nova/ISlotResponse";
import { IConfiguration } from "../../../models/configuration/IConfiguration";
import { NOVA } from "../../../models/db/protocolVersion";
import { NetworkService } from "../../../services/networkService";
import { NovaApiService } from "../../../services/nova/novaApiService";
import { ValidationHelper } from "../../../utils/validationHelper";

/**
* Fetch the block from the network.
* @param _ The configuration.
* @param request The request.
* @returns The response.
*/
export async function get(_: IConfiguration, request: ISlotRequest): Promise<ISlotResponse> {
const networkService = ServiceFactory.get<NetworkService>("network");
const networks = networkService.networkNames();
ValidationHelper.oneOf(request.network, networks, "network");
ValidationHelper.numberFromString(request.slotIndex, "slotIndex");

const networkConfig = networkService.get(request.network);

if (networkConfig.protocolVersion !== NOVA) {
return {};
}

const novaApiService = ServiceFactory.get<NovaApiService>(`api-service-${networkConfig.network}`);
return novaApiService.getSlotCommitment(Number(request.slotIndex));
}
30 changes: 30 additions & 0 deletions api/src/routes/nova/transaction/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ServiceFactory } from "../../../factories/serviceFactory";
import { ITransactionDetailsRequest } from "../../../models/api/nova/ITransactionDetailsRequest";
import { ITransactionDetailsResponse } from "../../../models/api/nova/ITransactionDetailsResponse";
import { IConfiguration } from "../../../models/configuration/IConfiguration";
import { NOVA } from "../../../models/db/protocolVersion";
import { NetworkService } from "../../../services/networkService";
import { NovaApiService } from "../../../services/nova/novaApiService";
import { ValidationHelper } from "../../../utils/validationHelper";

/**
* Find the object from the network.
* @param config The configuration.
* @param request The request.
* @returns The response.
*/
export async function get(config: IConfiguration, request: ITransactionDetailsRequest): Promise<ITransactionDetailsResponse> {
const networkService = ServiceFactory.get<NetworkService>("network");
const networks = networkService.networkNames();
ValidationHelper.oneOf(request.network, networks, "network");
ValidationHelper.string(request.transactionId, "transactionId");

const networkConfig = networkService.get(request.network);

if (networkConfig.protocolVersion !== NOVA) {
return {};
}

const novaApiService = ServiceFactory.get<NovaApiService>(`api-service-${networkConfig.network}`);
return novaApiService.transactionIncludedBlock(request.transactionId);
}
59 changes: 59 additions & 0 deletions api/src/services/nova/feed/novaFeed.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
/* eslint-disable import/no-unresolved */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { Block, Client, IBlockMetadata, SlotCommitment } from "@iota/sdk-nova";
import { ClassConstructor, plainToInstance } from "class-transformer";
import { ServiceFactory } from "../../../factories/serviceFactory";
import logger from "../../../logger";
import { ISlotCommitmentWrapper, SlotCommitmentStatus } from "../../../models/api/nova/commitment/ILatestSlotCommitmentsResponse";
import { IFeedUpdate } from "../../../models/api/nova/feed/IFeedUpdate";
import { INetwork } from "../../../models/db/INetwork";
import { NodeInfoService } from "../nodeInfoService";

const LATEST_SLOT_COMMITMENT_LIMIT = 30;

/**
* Wrapper class around Nova MqttClient.
* Streaming blocks from mqtt (upstream) to explorer-client connections (downstream).
Expand All @@ -25,6 +29,11 @@ export class NovaFeed {
*/
private _mqttClient: Client;

/**
* The latest slot commitments cache.
*/
private readonly latestSlotCommitmentCache: ISlotCommitmentWrapper[] = [];

/**
* The network in context.
*/
Expand Down Expand Up @@ -54,6 +63,14 @@ export class NovaFeed {
});
}

/**
* Get the latest slot commitment cache state.
* @returns The latest slot commitments.
*/
public get getLatestSlotCommitments() {
return this.latestSlotCommitmentCache;
}

/**
* Subscribe to the blocks nova feed.
* @param id The id of the subscriber.
Expand Down Expand Up @@ -124,10 +141,26 @@ export class NovaFeed {

// eslint-disable-next-line no-void
void this.broadcastBlock(update);

// eslint-disable-next-line no-void
void this.updateLatestSlotCommitmentCache(slotCommitment, true);
} catch {
logger.error("[NovaFeed]: Failed broadcasting finalized slot downstream.");
}
});

// eslint-disable-next-line no-void
void this._mqttClient.listenMqtt(["commitments/latest"], async (_, message) => {
try {
const deserializedMessage: { topic: string; payload: string } = JSON.parse(message);
const slotCommitment: SlotCommitment = JSON.parse(deserializedMessage.payload);

// eslint-disable-next-line no-void
void this.updateLatestSlotCommitmentCache(slotCommitment, false);
} catch {
logger.error("[NovaFeed]: Failed broadcasting commited slot downstream.");
}
});
}

private parseMqttPayloadMessage<T>(cls: ClassConstructor<T>, serializedMessage: string): T {
Expand Down Expand Up @@ -159,4 +192,30 @@ export class NovaFeed {
}
}
}

/**
* Updates the slot commitment cache.
* @param newSlotCommitment The new slot commitment.
* @param isFinalized Did the SlotCommitment get emitted from the 'commitments/finalized' topic or not ('commitments/latest').
*/
private async updateLatestSlotCommitmentCache(newSlotCommitment: SlotCommitment, isFinalized: boolean): Promise<void> {
if (!this.latestSlotCommitmentCache.map((commitment) => commitment.slotCommitment.slot).includes(newSlotCommitment.slot)) {
this.latestSlotCommitmentCache.unshift({
slotCommitment: newSlotCommitment,
status: isFinalized ? SlotCommitmentStatus.Finalized : SlotCommitmentStatus.Committed,
});

if (this.latestSlotCommitmentCache.length > LATEST_SLOT_COMMITMENT_LIMIT) {
this.latestSlotCommitmentCache.pop();
}
} else if (isFinalized) {
const commitmentToUpdate = this.latestSlotCommitmentCache.find(
(commitment) => commitment.slotCommitment.slot === newSlotCommitment.slot,
);

if (commitmentToUpdate) {
commitmentToUpdate.status = SlotCommitmentStatus.Finalized;
}
}
}
}
60 changes: 60 additions & 0 deletions api/src/services/nova/novaApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { INftDetailsResponse } from "../../models/api/nova/INftDetailsResponse";
import { IOutputDetailsResponse } from "../../models/api/nova/IOutputDetailsResponse";
import { IRewardsResponse } from "../../models/api/nova/IRewardsResponse";
import { ISearchResponse } from "../../models/api/nova/ISearchResponse";
import { ISlotResponse } from "../../models/api/nova/ISlotResponse";
import { ITransactionDetailsResponse } from "../../models/api/nova/ITransactionDetailsResponse";
import { INetwork } from "../../models/db/INetwork";
import { HexHelper } from "../../utils/hexHelper";
import { SearchExecutor } from "../../utils/nova/searchExecutor";
Expand Down Expand Up @@ -86,6 +88,30 @@ export class NovaApiService {
}
}

/**
* Get the transaction included block.
* @param transactionId The transaction id to get the details.
* @returns The item details.
*/
public async transactionIncludedBlock(transactionId: string): Promise<ITransactionDetailsResponse> {
transactionId = HexHelper.addPrefix(transactionId);
try {
const block = await this.client.getIncludedBlock(transactionId);

if (!block) {
return { error: `Couldn't find block from transaction id ${transactionId}` };
}
if (block && Object.keys(block).length > 0) {
return {
block,
};
}
} catch (e) {
logger.error(`Failed fetching block with transaction id ${transactionId}. Cause: ${e}`);
return { error: "Block fetch failed." };
}
}

/**
* Get the output details.
* @param outputId The output id to get the details.
Expand Down Expand Up @@ -158,6 +184,25 @@ export class NovaApiService {
}
}

/**
* Get the delegation output details.
* @param delegationId The delegationId to get the output details for.
* @returns The delegation output details.
*/
public async delegationDetails(delegationId: string): Promise<IOutputDetailsResponse | undefined> {
try {
const delegationOutputId = await this.client.delegationOutputId(delegationId);

if (delegationOutputId) {
const outputResponse = await this.outputDetails(delegationOutputId);

return outputResponse.error ? { error: outputResponse.error } : { output: outputResponse.output };
}
} catch {
return { message: "Delegation output not found" };
}
}

/**
* Get controlled Foundry output id by controller Account address
* @param accountAddress The bech32 account address to get the controlled Foundries for.
Expand Down Expand Up @@ -281,6 +326,21 @@ export class NovaApiService {
return manaRewardsResponse ? { outputId, manaRewards: manaRewardsResponse } : { outputId, message: "Rewards data not found" };
}

/**
* Get the slot commitment.
* @param slotIndex The slot index to get the commitment for.
* @returns The slot commitment.
*/
public async getSlotCommitment(slotIndex: number): Promise<ISlotResponse> {
try {
const slot = await this.client.getCommitmentByIndex(slotIndex);

return { slot };
} catch (e) {
logger.error(`Failed fetching slot with slot index ${slotIndex}. Cause: ${e}`);
}
}

/**
* Find item on the stardust network.
* @param query The query to use for finding items.
Expand Down
Loading

0 comments on commit c21b1d4

Please sign in to comment.