Skip to content

Commit

Permalink
Merge branch 'dev' into feat/add-nova-transaction-history
Browse files Browse the repository at this point in the history
 Conflicts:
	api/src/routes.ts
	client/src/app/components/nova/address/section/AddressPageTabbedSections.tsx
	client/src/helpers/nova/hooks/useAccountAddressState.ts
	client/src/services/nova/novaApiClient.ts
  • Loading branch information
msarcev committed Feb 27, 2024
2 parents acf6be7 + d8a6acd commit 8bed9db
Show file tree
Hide file tree
Showing 90 changed files with 2,633 additions and 619 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/nova-build-temp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
TARGET_COMMIT:
description: "Target Commit Hash for the SDK"
required: false
default: "8f0ff5e1e899a0d960ddfea09237739a88c3bcf1"
default: "fc9f0f56bb5cfc146993e53aa9656ded220734e1"
environment:
type: choice
description: "Select the environment to deploy to"
Expand Down
4 changes: 2 additions & 2 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "explorer-api",
"description": "API for Tangle Explorer",
"version": "3.3.4-rc.1",
"version": "3.3.4",
"author": "Martyn Janes <[email protected]>",
"repository": {
"type": "git",
Expand Down
11 changes: 11 additions & 0 deletions api/src/models/api/nova/ICongestionRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface ICongestionRequest {
/**
* The network to search on.
*/
network: string;

/**
* The account id to get the congestion for.
*/
accountId: string;
}
11 changes: 11 additions & 0 deletions api/src/models/api/nova/ICongestionResponse.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-unsafe-argument */
import { CongestionResponse } from "@iota/sdk-nova";
import { IResponse } from "./IResponse";

export interface ICongestionResponse extends IResponse {
/**
* The Account Congestion.
*/
congestion?: CongestionResponse;
}
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[];
}
19 changes: 19 additions & 0 deletions api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,25 @@ export const routes: IRoute[] = [
folder: "nova/transactionhistory",
func: "get",
},
{
path: "/nova/transaction/:network/:transactionId",
method: "get",
folder: "nova/transaction",
func: "get",
},
{
path: "/nova/account/congestion/:network/:accountId",
method: "get",
folder: "nova/account/congestion",
func: "get",
},
{ 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/account/congestion/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ServiceFactory } from "../../../../factories/serviceFactory";
import { ICongestionRequest } from "../../../../models/api/nova/ICongestionRequest";
import { ICongestionResponse } from "../../../../models/api/nova/ICongestionResponse";
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";

/**
* Get Congestion for Account address
* @param config The configuration.
* @param request The request.
* @returns The response.
*/
export async function get(config: IConfiguration, request: ICongestionRequest): Promise<ICongestionResponse> {
const networkService = ServiceFactory.get<NetworkService>("network");
const networks = networkService.networkNames();
ValidationHelper.oneOf(request.network, networks, "network");
ValidationHelper.string(request.accountId, "accountId");

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

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

const novaApiService = ServiceFactory.get<NovaApiService>(`api-service-${networkConfig.network}`);
return novaApiService.getAccountCongestion(request.accountId);
}
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;
}
}
}
}
Loading

0 comments on commit 8bed9db

Please sign in to comment.