Skip to content

Commit

Permalink
Merge branch 'dev' into feat/add-delegation-search
Browse files Browse the repository at this point in the history
  • Loading branch information
begonaalvarezd authored Feb 26, 2024
2 parents c44dbd7 + a03f72a commit 365a72e
Show file tree
Hide file tree
Showing 12 changed files with 259 additions and 68 deletions.
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[];
}
6 changes: 6 additions & 0 deletions api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,5 +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 };
}
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;
}
}
}
}
20 changes: 19 additions & 1 deletion client/src/app/components/nova/landing/LandingSlotSection.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,39 @@
margin: 0 20px 20px;

.slots-feed__item {
display: flex;
display: grid;
grid-template-columns: 1fr 3fr 1fr 1fr;
margin: 0px 12px;
align-items: center;
line-height: 32px;
justify-content: center;
background-color: $gray-5;
border-radius: 4px;

&.basic {
grid-template-columns: none;
}

&.transparent {
background-color: transparent;
}

&:not(:last-child) {
margin-bottom: 20px;
}

.slot__index,
.slot__commitment-id,
.slot__rmc,
.slot__status {
display: flex;
margin: 0 auto;
justify-content: center;
}

.slot__commitment-id {
width: 220px;
}
}
}
}
41 changes: 32 additions & 9 deletions client/src/app/components/nova/landing/LandingSlotSection.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React from "react";
import useSlotsFeed from "~/helpers/nova/hooks/useSlotsFeed";
import "./LandingSlotSection.scss";
import ProgressBar from "./ProgressBar";
import { Utils } from "@iota/sdk-wasm-nova/web";
import Spinner from "../../Spinner";
import TruncatedId from "../../stardust/TruncatedId";
import "./LandingSlotSection.scss";

const LandingSlotSection: React.FC = () => {
const { currentSlot, currentSlotProgressPercent, latestSlots } = useSlotsFeed();
const { currentSlotIndex, currentSlotProgressPercent, latestSlotIndexes, latestSlotCommitments } = useSlotsFeed();

if (currentSlot === null || currentSlotProgressPercent === null) {
if (currentSlotIndex === null || currentSlotProgressPercent === null) {
return null;
}

Expand All @@ -15,13 +18,33 @@ const LandingSlotSection: React.FC = () => {
<h2 className="slots-section__header">Latest Slots</h2>
<div className="slots-feed__wrapper">
<ProgressBar progress={currentSlotProgressPercent} showLabel={false}>
<div className="slots-feed__item transparent">{currentSlot}</div>
</ProgressBar>
{latestSlots?.map((slot) => (
<div key={`slot-key-${slot}`} className="slots-feed__item">
{slot}
<div className="slots-feed__item transparent basic">
<div className="slot__index">{currentSlotIndex}</div>
</div>
))}
</ProgressBar>
{latestSlotIndexes?.map((slot) => {
const commitmentWrapper = latestSlotCommitments?.find((commitment) => commitment.slotCommitment.slot === slot) ?? null;
const commitmentId = !commitmentWrapper ? (
<Spinner compact />
) : (
<TruncatedId id={Utils.computeSlotCommitmentId(commitmentWrapper.slotCommitment)} showCopyButton />
);
const referenceManaCost = !commitmentWrapper ? (
<Spinner compact />
) : (
commitmentWrapper.slotCommitment.referenceManaCost.toString()
);
const slotStatus = !commitmentWrapper ? "pending" : commitmentWrapper.status;

return (
<div key={`slot-key-${slot}`} className="slots-feed__item">
<div className="slot__index">{slot}</div>
<div className="slot__commitment-id">{commitmentId}</div>
<div className="slot__rmc">{referenceManaCost}</div>
<div className="slot__status">{slotStatus}</div>
</div>
);
})}
</div>
</div>
);
Expand Down
1 change: 0 additions & 1 deletion client/src/app/lib/enums/index.ts

This file was deleted.

16 changes: 0 additions & 16 deletions client/src/app/lib/enums/slot-state.enums.ts

This file was deleted.

21 changes: 2 additions & 19 deletions client/src/app/routes/nova/SlotPage.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import React from "react";
import useuseSlotDetails from "~/helpers/nova/hooks/useSlotDetails";
import StatusPill from "~/app/components/nova/StatusPill";
import PageDataRow, { IPageDataRow } from "~/app/components/nova/PageDataRow";
import Modal from "~/app/components/Modal";
import mainHeaderMessage from "~assets/modals/nova/slot/main-header.json";
import NotFound from "~/app/components/NotFound";
import { SlotState } from "~/app/lib/enums";
import { RouteComponentProps } from "react-router-dom";
import { PillStatus } from "~/app/lib/ui/enums";
import "./SlotPage.scss";

const SLOT_STATE_TO_PILL_STATUS: Record<SlotState, PillStatus> = {
[SlotState.Pending]: PillStatus.Pending,
[SlotState.Committed]: PillStatus.Success,
[SlotState.Finalized]: PillStatus.Success,
};

export default function SlotPage({
match: {
params: { network, slotIndex },
Expand All @@ -27,18 +18,15 @@ export default function SlotPage({
const { slotCommitment } = useuseSlotDetails(network, slotIndex);

const parsedSlotIndex = parseSlotIndex(slotIndex);
const slotState = slotCommitment ? SlotState.Finalized : SlotState.Pending;
const pillStatus: PillStatus = SLOT_STATE_TO_PILL_STATUS[slotState];

const dataRows: IPageDataRow[] = [
{
label: "Slot Index",
value: slotCommitment?.slot || parsedSlotIndex,
highlight: true,
value: parsedSlotIndex ?? "-",
},
{
label: "RMC",
value: slotCommitment?.referenceManaCost.toString(),
value: slotCommitment?.referenceManaCost?.toString() ?? "-",
},
];

Expand All @@ -59,11 +47,6 @@ export default function SlotPage({
<h1>Slot</h1>
<Modal icon="info" data={mainHeaderMessage} />
</div>
{parsedSlotIndex && (
<div className="header--status">
<StatusPill status={pillStatus} label={slotState} />
</div>
)}
</div>
{parsedSlotIndex ? (
<div className="section">
Expand Down
Loading

0 comments on commit 365a72e

Please sign in to comment.