From 5131eebdd26a6ef0c7b39a635abb197184df7c99 Mon Sep 17 00:00:00 2001 From: Mario Sarcevic Date: Wed, 6 Dec 2023 10:30:41 +0100 Subject: [PATCH 1/9] feat: Add support for NovaFeed in subscribe/unsubscribe endpoint --- api/src/models/api/nova/feed/IFeedUpdate.ts | 9 +++++++ api/src/routes/feed/subscribe.ts | 13 +++++++++- api/src/routes/feed/unsubscribe.ts | 5 +++- api/src/services/nova/feed/novaFeed.ts | 27 ++++++++++++++++++++- 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 api/src/models/api/nova/feed/IFeedUpdate.ts diff --git a/api/src/models/api/nova/feed/IFeedUpdate.ts b/api/src/models/api/nova/feed/IFeedUpdate.ts new file mode 100644 index 000000000..b4aa0c8f3 --- /dev/null +++ b/api/src/models/api/nova/feed/IFeedUpdate.ts @@ -0,0 +1,9 @@ +import { Block } from "@iota/sdk-nova"; + +type IFeedBlockUpdate = Block; + +export interface IFeedUpdate { + subscriptionId: string; + block?: IFeedBlockUpdate; +} + diff --git a/api/src/routes/feed/subscribe.ts b/api/src/routes/feed/subscribe.ts index 54c73af19..94561b275 100644 --- a/api/src/routes/feed/subscribe.ts +++ b/api/src/routes/feed/subscribe.ts @@ -4,10 +4,11 @@ import logger from "../../logger"; import { IFeedSubscribeRequest } from "../../models/api/IFeedSubscribeRequest"; import { IFeedSubscribeResponse } from "../../models/api/IFeedSubscribeResponse"; import { IConfiguration } from "../../models/configuration/IConfiguration"; -import { CHRYSALIS, LEGACY, STARDUST } from "../../models/db/protocolVersion"; +import { CHRYSALIS, LEGACY, NOVA, STARDUST } from "../../models/db/protocolVersion"; import { IItemsService as IItemsServiceChrysalis } from "../../models/services/chrysalis/IItemsService"; import { IItemsService as IItemsServiceLegacy } from "../../models/services/legacy/IItemsService"; import { NetworkService } from "../../services/networkService"; +import { NovaFeed } from "../../services/nova/feed/novaFeed"; import { StardustFeed } from "../../services/stardust/feed/stardustFeed"; import { ValidationHelper } from "../../utils/validationHelper"; @@ -67,6 +68,16 @@ export async function subscribe( ) ); } + } else if (networkConfig.protocolVersion === NOVA) { + const service = ServiceFactory.get(`feed-${request.network}`); + if (service) { + await service.subscribeBlocks( + socket.id, + async data => { + socket.emit("block", data); + } + ); + } } else { return { error: "Network protocol not supported for feed." diff --git a/api/src/routes/feed/unsubscribe.ts b/api/src/routes/feed/unsubscribe.ts index caa43bb75..6b39dcb2f 100644 --- a/api/src/routes/feed/unsubscribe.ts +++ b/api/src/routes/feed/unsubscribe.ts @@ -4,7 +4,7 @@ import logger from "../../logger"; import { IFeedUnsubscribeRequest } from "../../models/api/IFeedUnsubscribeRequest"; import { IResponse } from "../../models/api/IResponse"; import { IConfiguration } from "../../models/configuration/IConfiguration"; -import { CHRYSALIS, LEGACY, STARDUST } from "../../models/db/protocolVersion"; +import { CHRYSALIS, LEGACY, NOVA, STARDUST } from "../../models/db/protocolVersion"; import { IItemsService as IItemsServiceChrysalis } from "../../models/services/chrysalis/IItemsService"; import { IItemsService as IItemsServiceLegacy } from "../../models/services/legacy/IItemsService"; import { NetworkService } from "../../services/networkService"; @@ -55,6 +55,9 @@ export async function unsubscribe( service?.unsubscribeBlocks(request.subscriptionId); service?.unsubscribeMilestones(request.subscriptionId); } + } else if (networkConfig.protocolVersion === NOVA) { + const service = ServiceFactory.get(`feed-${request.network}`); + service?.unsubscribeBlocks(request.subscriptionId); } else { return { error: "Network protocol not supported for feed." diff --git a/api/src/services/nova/feed/novaFeed.ts b/api/src/services/nova/feed/novaFeed.ts index fa23a2a38..662cfc521 100644 --- a/api/src/services/nova/feed/novaFeed.ts +++ b/api/src/services/nova/feed/novaFeed.ts @@ -2,6 +2,7 @@ import { BasicBlock, Client, IBlockMetadata } from "@iota/sdk-nova"; import { ClassConstructor, plainToInstance } from "class-transformer"; import { ServiceFactory } from "../../../factories/serviceFactory"; import logger from "../../../logger"; +import { IFeedUpdate } from "../../../models/api/nova/feed/IFeedUpdate"; /** * Wrapper class around Nova MqttClient. @@ -12,7 +13,7 @@ export class NovaFeed { * The block feed subscribers (downstream). */ protected readonly blockSubscribers: { - [id: string]: (data: Record) => Promise; + [id: string]: (data: IFeedUpdate) => Promise; }; /** @@ -20,6 +21,11 @@ export class NovaFeed { */ private readonly _mqttClient: Client; + /** + * The network in context. + */ + private readonly network: string; + /** * Creates a new instance of NovaFeed. * @param networkId The network id. @@ -27,6 +33,7 @@ export class NovaFeed { constructor(networkId: string) { logger.debug("[NovaFeed] Constructing a Nova Feed"); this.blockSubscribers = {}; + this.network = networkId; this._mqttClient = ServiceFactory.get(`mqtt-${networkId}`); logger.debug(`[NovaFeed] Mqtt is ${JSON.stringify(this._mqttClient)}`); @@ -38,6 +45,24 @@ export class NovaFeed { } } + /** + * Subscribe to the blocks nova feed. + * @param id The id of the subscriber. + * @param callback The callback to call with data for the event. + */ + public async subscribeBlocks(id: string, callback: (data: IFeedUpdate) => Promise): Promise { + this.blockSubscribers[id] = callback; + } + + /** + * Unsubscribe from the blocks feed. + * @param subscriptionId The id to unsubscribe. + */ + public unsubscribeBlocks(subscriptionId: string): void { + logger.debug(`[NovaFeed] Removing subscriber ${subscriptionId} from blocks (${this.network})`); + delete this.blockSubscribers[subscriptionId]; + } + /** * Connects the callbacks for upstream data. */ From 3f0e16542c10db526b0db33c901209c5e5aba020 Mon Sep 17 00:00:00 2001 From: Mario Sarcevic Date: Tue, 12 Dec 2023 12:59:46 +0100 Subject: [PATCH 2/9] feat: Add infrastructure for nova (nodeInfo, novaApiClient, novaFeedClient) --- api/src/initServices.ts | 38 +++-- api/src/models/api/nova/INodeInfoResponse.ts | 8 ++ api/src/routes/node/info.ts | 13 +- api/src/services/nova/feed/novaFeed.ts | 12 +- api/src/services/nova/nodeInfoService.ts | 44 ++++++ client/script/postinstall.sh | 2 + client/src/app/AppUtils.tsx | 4 +- client/src/index.tsx | 20 ++- .../src/models/api/nova/INodeInfoResponse.ts | 9 ++ .../models/api/nova/feed/IFeedBlockData.ts | 7 + .../api/nova/feed/IFeedSubscribeRequest.ts | 14 ++ .../src/models/api/nova/feed/IFeedUpdate.ts | 9 ++ .../stardust/feed/IFeedSubscribeRequest.ts | 2 +- client/src/services/nodeInfoService.ts | 29 +++- client/src/services/nova/novaApiClient.ts | 20 +++ client/src/services/nova/novaFeedClient.ts | 131 ++++++++++++++++++ .../services/stardust/stardustFeedClient.ts | 3 +- client/tsconfig.json | 3 + 18 files changed, 330 insertions(+), 38 deletions(-) create mode 100644 api/src/models/api/nova/INodeInfoResponse.ts create mode 100644 api/src/services/nova/nodeInfoService.ts create mode 100644 client/src/models/api/nova/INodeInfoResponse.ts create mode 100644 client/src/models/api/nova/feed/IFeedBlockData.ts create mode 100644 client/src/models/api/nova/feed/IFeedSubscribeRequest.ts create mode 100644 client/src/models/api/nova/feed/IFeedUpdate.ts create mode 100644 client/src/services/nova/novaApiClient.ts create mode 100644 client/src/services/nova/novaFeedClient.ts diff --git a/api/src/initServices.ts b/api/src/initServices.ts index 7c95782d9..87938dd7a 100644 --- a/api/src/initServices.ts +++ b/api/src/initServices.ts @@ -1,6 +1,6 @@ import { MqttClient as ChrysalisMqttClient } from "@iota/mqtt.js"; import { Client as StardustClient } from "@iota/sdk"; -import { initLogger, Client as NovaMqttClient } from "@iota/sdk-nova"; +import { initLogger, Client as NovaClient } from "@iota/sdk-nova"; import { ServiceFactory } from "./factories/serviceFactory"; import logger from "./logger"; import { IConfiguration } from "./models/configuration/IConfiguration"; @@ -22,10 +22,11 @@ import { ZmqService } from "./services/legacy/zmqService"; import { LocalStorageService } from "./services/localStorageService"; import { NetworkService } from "./services/networkService"; import { NovaFeed } from "./services/nova/feed/novaFeed"; +import { NodeInfoService as NodeInfoServiceNova } from "./services/nova/nodeInfoService"; import { ChronicleService } from "./services/stardust/chronicleService"; import { StardustFeed } from "./services/stardust/feed/stardustFeed"; import { InfluxDBService } from "./services/stardust/influx/influxDbService"; -import { NodeInfoService } from "./services/stardust/nodeInfoService"; +import { NodeInfoService as NodeInfoServiceStardust } from "./services/stardust/nodeInfoService"; import { StardustStatsService } from "./services/stardust/stats/stardustStatsService"; // iota-sdk debug @@ -203,7 +204,7 @@ function initStardustServices(networkConfig: INetwork): void { } // eslint-disable-next-line no-void - void NodeInfoService.build(networkConfig).then(nodeInfoService => { + void NodeInfoServiceStardust.build(networkConfig).then(nodeInfoService => { ServiceFactory.register( `node-info-${networkConfig.network}`, () => nodeInfoService @@ -240,20 +241,31 @@ function initStardustServices(networkConfig: INetwork): void { */ function initNovaServices(networkConfig: INetwork): void { logger.verbose(`Initializing Nova services for ${networkConfig.network}`); + const novaClient = new NovaClient({ + nodes: [networkConfig.provider], + brokerOptions: { useWs: true }, + // Needed only for now in local development (NOT FOR PROD) + ignoreNodeHealth: true + }); - const mqttInstance = new NovaMqttClient( - { nodes: [networkConfig.feedEndpoint], brokerOptions: { useWs: true }, ignoreNodeHealth: true } - ); ServiceFactory.register( - `mqtt-${networkConfig.network}`, - () => mqttInstance + `client-${networkConfig.network}`, + () => novaClient ); - const feedInstance = new NovaFeed(networkConfig.network); - ServiceFactory.register( - `feed-${networkConfig.network}`, - () => feedInstance - ); + // eslint-disable-next-line no-void + void NodeInfoServiceNova.build(networkConfig).then(nodeInfoService => { + ServiceFactory.register( + `node-info-${networkConfig.network}`, + () => nodeInfoService + ); + + const feedInstance = new NovaFeed(networkConfig.network); + ServiceFactory.register( + `feed-${networkConfig.network}`, + () => feedInstance + ); + }); } /** diff --git a/api/src/models/api/nova/INodeInfoResponse.ts b/api/src/models/api/nova/INodeInfoResponse.ts new file mode 100644 index 000000000..2ca694651 --- /dev/null +++ b/api/src/models/api/nova/INodeInfoResponse.ts @@ -0,0 +1,8 @@ +import { INodeInfo } from "@iota/sdk-nova"; +import { IResponse } from "../IResponse"; + +/** + * The response with node info for a specific network. + */ +export type INodeInfoResponse = INodeInfo & IResponse; + diff --git a/api/src/routes/node/info.ts b/api/src/routes/node/info.ts index 8e50ed601..844a7f285 100644 --- a/api/src/routes/node/info.ts +++ b/api/src/routes/node/info.ts @@ -1,8 +1,9 @@ import { ServiceFactory } from "../../factories/serviceFactory"; import { INetworkBoundGetRequest } from "../../models/api/INetworkBoundGetRequest"; -import { INodeInfoResponse } from "../../models/api/stardust/INodeInfoResponse"; +import { INodeInfoResponse as INovaNodeInfoResponse } from "../../models/api/nova/INodeInfoResponse"; +import { INodeInfoResponse as IStardustNodeInfoResponse } from "../../models/api/stardust/INodeInfoResponse"; import { IConfiguration } from "../../models/configuration/IConfiguration"; -import { STARDUST } from "../../models/db/protocolVersion"; +import { NOVA, STARDUST } from "../../models/db/protocolVersion"; import { NetworkService } from "../../services/networkService"; import { NodeInfoService } from "../../services/stardust/nodeInfoService"; import { ValidationHelper } from "../../utils/validationHelper"; @@ -16,14 +17,17 @@ import { ValidationHelper } from "../../utils/validationHelper"; export async function info( _: IConfiguration, request: INetworkBoundGetRequest -): Promise { +): Promise { const networkService = ServiceFactory.get("network"); const networks = networkService.networkNames(); ValidationHelper.oneOf(request.network, networks, "network"); const networkConfig = networkService.get(request.network); - if (networkConfig.protocolVersion !== STARDUST) { + if ( + networkConfig.protocolVersion !== STARDUST && + networkConfig.protocolVersion !== NOVA + ) { return {}; } @@ -31,3 +35,4 @@ export async function info( return nodeService.getNodeInfo(); } + diff --git a/api/src/services/nova/feed/novaFeed.ts b/api/src/services/nova/feed/novaFeed.ts index 662cfc521..d510ca570 100644 --- a/api/src/services/nova/feed/novaFeed.ts +++ b/api/src/services/nova/feed/novaFeed.ts @@ -1,8 +1,9 @@ -import { BasicBlock, Client, IBlockMetadata } from "@iota/sdk-nova"; +import { Block, Client, IBlockMetadata } from "@iota/sdk-nova"; import { ClassConstructor, plainToInstance } from "class-transformer"; import { ServiceFactory } from "../../../factories/serviceFactory"; import logger from "../../../logger"; import { IFeedUpdate } from "../../../models/api/nova/feed/IFeedUpdate"; +import { NodeInfoService } from "../nodeInfoService"; /** * Wrapper class around Nova MqttClient. @@ -34,11 +35,10 @@ export class NovaFeed { logger.debug("[NovaFeed] Constructing a Nova Feed"); this.blockSubscribers = {}; this.network = networkId; - this._mqttClient = ServiceFactory.get(`mqtt-${networkId}`); + this._mqttClient = ServiceFactory.get(`client-${networkId}`); + const nodeInfoService = ServiceFactory.get(`node-info-${networkId}`); - logger.debug(`[NovaFeed] Mqtt is ${JSON.stringify(this._mqttClient)}`); - - if (this._mqttClient) { + if (this._mqttClient && nodeInfoService) { this.connect(); } else { throw new Error(`Failed to build novaFeed instance for ${networkId}`); @@ -71,7 +71,7 @@ export class NovaFeed { // eslint-disable-next-line no-void void this._mqttClient.listenMqtt(["blocks"], (_, message) => { try { - const block: BasicBlock = this.parseMqttPayloadMessage(BasicBlock, message); + const block: Block = this.parseMqttPayloadMessage(Block, message); const update: Partial> = { block }; diff --git a/api/src/services/nova/nodeInfoService.ts b/api/src/services/nova/nodeInfoService.ts new file mode 100644 index 000000000..723d41bf8 --- /dev/null +++ b/api/src/services/nova/nodeInfoService.ts @@ -0,0 +1,44 @@ +import { Client, INodeInfo } from "@iota/sdk-nova"; +import { NodeInfoError } from "../../errors/nodeInfoError"; +import { ServiceFactory } from "../../factories/serviceFactory"; +import { INetwork } from "../../models/db/INetwork"; + +/** + * Class to handle Nova protocol node info. + */ +export class NodeInfoService { + /** + * The network configuration. + */ + protected readonly _network: INetwork; + + /** + * The node and token info. + */ + protected _nodeInfo: INodeInfo; + + /** + * Create a new instance of NodeInfoService. + * @param network The network config. + * @param nodeInfo The fetched node info + */ + private constructor(network: INetwork, nodeInfo: INodeInfo) { + this._network = network; + this._nodeInfo = nodeInfo; + } + + public static async build(network: INetwork): Promise { + const apiClient = ServiceFactory.get(`client-${network.network}`); + + try { + const response = await apiClient.getInfo(); + return new NodeInfoService(network, response.nodeInfo); + } catch (err) { + throw new NodeInfoError(`Failed to fetch node info for "${network.network}" with error:\n${err}`); + } + } + + public getNodeInfo(): INodeInfo { + return this._nodeInfo; + } +} diff --git a/client/script/postinstall.sh b/client/script/postinstall.sh index 33d4fa7c1..b63990589 100755 --- a/client/script/postinstall.sh +++ b/client/script/postinstall.sh @@ -7,4 +7,6 @@ mkdir -p "$TARGET" # stardust cp "$NODE_MODULES/@iota/sdk-wasm/web/wasm/iota_sdk_wasm_bg.wasm" "$TARGET/iota_sdk_stardust_wasm_bg.wasm" +# nova +cp "$NODE_MODULES/@iota/sdk-wasm-nova/web/wasm/iota_sdk_wasm_bg.wasm" "$TARGET/iota_sdk_nova_wasm_bg.wasm" diff --git a/client/src/app/AppUtils.tsx b/client/src/app/AppUtils.tsx index 08baa7403..42abd023e 100644 --- a/client/src/app/AppUtils.tsx +++ b/client/src/app/AppUtils.tsx @@ -4,11 +4,11 @@ import NetworkContext from "./context/NetworkContext"; import { INetwork } from "~models/config/INetwork"; import { ALPHANET, CHRYSALIS_MAINNET, DEVNET, LEGACY_MAINNET, MAINNET, NetworkType, SHIMMER, TESTNET } from "~models/config/networkType"; import { IOTA_UI, Theme } from "~models/config/uiTheme"; -import { IReducedNodeInfo } from "~services/nodeInfoService"; +import { IStardustNodeInfo } from "~services/nodeInfoService"; export const networkContextWrapper = ( currentNetwork: string | undefined, - nodeInfo: IReducedNodeInfo | null, + nodeInfo: IStardustNodeInfo | null, uiTheme: Theme | undefined ) => function withNetworkContext(wrappedComponent: ReactNode) { return currentNetwork && nodeInfo ? ( diff --git a/client/src/index.tsx b/client/src/index.tsx index e6a065165..fc55a4871 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -2,7 +2,8 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ // needed for features from @iota/sdk which use reflection (decorators) import "reflect-metadata"; -import initStardustSdk from "@iota/sdk-wasm/web"; +import initSdkStardust from "@iota/sdk-wasm/web"; +import initSdkNova from "@iota/sdk-wasm-nova/web"; import React from "react"; import { createRoot } from "react-dom/client"; import { BrowserRouter, Route, RouteComponentProps } from "react-router-dom"; @@ -10,7 +11,7 @@ import App from "~app/App"; import { AppRouteProps } from "~app/AppRouteProps"; import { ServiceFactory } from "~factories/serviceFactory"; import "./index.scss"; -import { CHRYSALIS, LEGACY, STARDUST } from "~models/config/protocolVersion"; +import { CHRYSALIS, LEGACY, NOVA, STARDUST } from "~models/config/protocolVersion"; import { ChrysalisApiClient } from "~services/chrysalis/chrysalisApiClient"; import { ChrysalisFeedClient } from "~services/chrysalis/chrysalisFeedClient"; import { ChrysalisTangleCacheService } from "~services/chrysalis/chrysalisTangleCacheService"; @@ -25,16 +26,19 @@ import { NodeInfoService } from "~services/nodeInfoService"; import { SettingsService } from "~services/settingsService"; import { StardustApiClient } from "~services/stardust/stardustApiClient"; import { StardustFeedClient } from "~services/stardust/stardustFeedClient"; +import { NovaApiClient } from "./services/nova/novaApiClient"; +import { TokenRegistryClient } from "~services/stardust/tokenRegistryClient"; import "@fontsource/ibm-plex-mono"; import "@fontsource/material-icons"; -import { TokenRegistryClient } from "~services/stardust/tokenRegistryClient"; +import { NovaFeedClient } from "./services/nova/novaFeedClient"; // eslint-disable-next-line @typescript-eslint/no-explicit-any const apiEndpoint = (window as any).env.API_ENDPOINT; initialiseServices().then(async () => { // load the wasm - await initStardustSdk("/wasm/iota_sdk_stardust_wasm_bg.wasm"); + await initSdkStardust("/wasm/iota_sdk_stardust_wasm_bg.wasm"); + await initSdkNova("/wasm/iota_sdk_nova_wasm_bg.wasm"); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const container = document.querySelector("#root")!; @@ -58,6 +62,7 @@ async function initialiseServices(): Promise { ServiceFactory.register(`api-client-${LEGACY}`, () => new LegacyApiClient(apiEndpoint)); ServiceFactory.register(`api-client-${CHRYSALIS}`, () => new ChrysalisApiClient(apiEndpoint)); ServiceFactory.register(`api-client-${STARDUST}`, () => new StardustApiClient(apiEndpoint)); + ServiceFactory.register(`api-client-${NOVA}`, () => new NovaApiClient(apiEndpoint)); ServiceFactory.register("settings", () => new SettingsService()); ServiceFactory.register("local-storage", () => new LocalStorageService()); @@ -103,6 +108,13 @@ async function initialiseServices(): Promise { ); break; } + case NOVA: { + ServiceFactory.register( + `feed-${netConfig.network}`, + serviceName => new NovaFeedClient(apiEndpoint, serviceName.slice(5)) + ); + break; + } default: } } diff --git a/client/src/models/api/nova/INodeInfoResponse.ts b/client/src/models/api/nova/INodeInfoResponse.ts new file mode 100644 index 000000000..4eeeff633 --- /dev/null +++ b/client/src/models/api/nova/INodeInfoResponse.ts @@ -0,0 +1,9 @@ +import { INodeInfo } from "@iota/sdk-wasm-nova/web"; +import { IResponse } from "../IResponse"; + +/** + * The response with node info for a specific network. + */ +export type INodeInfoResponse = INodeInfo & IResponse + + diff --git a/client/src/models/api/nova/feed/IFeedBlockData.ts b/client/src/models/api/nova/feed/IFeedBlockData.ts new file mode 100644 index 000000000..741ed0966 --- /dev/null +++ b/client/src/models/api/nova/feed/IFeedBlockData.ts @@ -0,0 +1,7 @@ +export interface IFeedBlockData { + /** + * The block id. + */ + blockId: string; +} + diff --git a/client/src/models/api/nova/feed/IFeedSubscribeRequest.ts b/client/src/models/api/nova/feed/IFeedSubscribeRequest.ts new file mode 100644 index 000000000..81a0c5a49 --- /dev/null +++ b/client/src/models/api/nova/feed/IFeedSubscribeRequest.ts @@ -0,0 +1,14 @@ +export type IFeedSelect = "block"; + +export interface IFeedSubscribeRequest { + /** + * The network in context for the request. + */ + network: string; + + /** + * The specific feed to subscribe too. + */ + feedSelect: IFeedSelect; +} + diff --git a/client/src/models/api/nova/feed/IFeedUpdate.ts b/client/src/models/api/nova/feed/IFeedUpdate.ts new file mode 100644 index 000000000..3b32e1217 --- /dev/null +++ b/client/src/models/api/nova/feed/IFeedUpdate.ts @@ -0,0 +1,9 @@ +import { Block } from "@iota/sdk-wasm-nova/web"; + +type IFeedBlockUpdate = Block; + +export interface IFeedUpdate { + subscriptionId: string; + block?: IFeedBlockUpdate; +} + diff --git a/client/src/models/api/stardust/feed/IFeedSubscribeRequest.ts b/client/src/models/api/stardust/feed/IFeedSubscribeRequest.ts index a12bbb6d6..c8a3103f2 100644 --- a/client/src/models/api/stardust/feed/IFeedSubscribeRequest.ts +++ b/client/src/models/api/stardust/feed/IFeedSubscribeRequest.ts @@ -7,7 +7,7 @@ export interface IFeedSubscribeRequest { network: string; /** - * The specific feed to subscribe too (expected only on stardust feed). + * The specific feed to subscribe too (expected only on stardust/nova feeds). */ feedSelect: IFeedSelect; } diff --git a/client/src/services/nodeInfoService.ts b/client/src/services/nodeInfoService.ts index 47d284237..00bb001d4 100644 --- a/client/src/services/nodeInfoService.ts +++ b/client/src/services/nodeInfoService.ts @@ -1,14 +1,16 @@ import { INodeInfoBaseToken, IRent } from "@iota/sdk-wasm/web"; import { StardustApiClient } from "./stardust/stardustApiClient"; import { ServiceFactory } from "~factories/serviceFactory"; -import { INodeInfoResponse } from "~models/api/stardust/INodeInfoResponse"; -import { STARDUST } from "~models/config/protocolVersion"; +import { INodeInfoResponse as IStardustInfoResponse } from "~models/api/stardust/INodeInfoResponse"; +import { INodeInfoResponse as INovaInfoResponse } from "~models/api/nova/INodeInfoResponse"; +import { NOVA, STARDUST } from "~models/config/protocolVersion"; import { NetworkService } from "~services/networkService"; +import { NovaApiClient } from "./nova/novaApiClient"; /** - * The reduced node info fields relevant for Explorer. + * The reduced stardust node info fields relevant for Explorer. */ -export interface IReducedNodeInfo { +export interface IStardustNodeInfo { /** * The base token info of the node. */ @@ -34,14 +36,14 @@ export class NodeInfoService { /** * Cache of the base taken infos. */ - private _cache: { [network: string]: IReducedNodeInfo } = {}; + private _cache: { [network: string]: IStardustNodeInfo | INovaInfoResponse } = {}; /** * Get the base token info by network. * @param network The name of the network. * @returns The base token info. */ - public get(network: string): IReducedNodeInfo { + public get(network: string): IStardustNodeInfo | INovaInfoResponse { return this._cache[network]; } @@ -51,17 +53,30 @@ export class NodeInfoService { public async buildCache(): Promise { const networksService = ServiceFactory.get("network"); const stardustNetworks = networksService.networks().filter(n => n.protocolVersion === STARDUST); + const novaNetworks = networksService.networks().filter(n => n.protocolVersion === NOVA); for (const networkDetails of stardustNetworks) { const apiClient = ServiceFactory.get(`api-client-${STARDUST}`); const network = networkDetails.network; - const response: INodeInfoResponse = await apiClient.nodeInfo({ network }); + const response: IStardustInfoResponse = await apiClient.nodeInfo({ network }); const { baseToken, protocolVersion, bech32Hrp, rentStructure } = response; if (baseToken && protocolVersion && bech32Hrp && rentStructure) { this._cache[network] = { baseToken, protocolVersion, bech32Hrp, rentStructure }; } } + + // TODO Split this into separate services + for (const networkDetails of novaNetworks) { + const apiClient = ServiceFactory.get(`api-client-${NOVA}`); + const network = networkDetails.network; + const response: INovaInfoResponse = await apiClient.nodeInfo({ network }); + const { baseToken, status, protocolParameters } = response; + + if (baseToken && status && protocolParameters) { + this._cache[network] = response; + } + } } } diff --git a/client/src/services/nova/novaApiClient.ts b/client/src/services/nova/novaApiClient.ts new file mode 100644 index 000000000..3051237bd --- /dev/null +++ b/client/src/services/nova/novaApiClient.ts @@ -0,0 +1,20 @@ +import { INetworkBoundGetRequest } from "~/models/api/INetworkBoundGetRequest"; +import { INodeInfoResponse } from "~/models/api/nova/INodeInfoResponse"; +import { ApiClient } from "../apiClient"; + +/** + * Class to handle api communications on stardust. + */ +export class NovaApiClient extends ApiClient { + /** + * Perform a request to get the base token info for the network. + * @param request The Base token request. + * @returns The response from the request. + */ + public async nodeInfo(request: INetworkBoundGetRequest): Promise { + return this.callApi( + `node-info/${request.network}`, + "get" + ); + } +} diff --git a/client/src/services/nova/novaFeedClient.ts b/client/src/services/nova/novaFeedClient.ts new file mode 100644 index 000000000..bc6dedc51 --- /dev/null +++ b/client/src/services/nova/novaFeedClient.ts @@ -0,0 +1,131 @@ +import { + Block, Utils +} from "@iota/sdk-wasm-nova/web"; +import { io, Socket } from "socket.io-client"; +import { ServiceFactory } from "~/factories/serviceFactory"; +import { IFeedSubscribeResponse } from "~/models/api/IFeedSubscribeResponse"; +import { IFeedBlockData } from "~/models/api/nova/feed/IFeedBlockData"; +import { IFeedSubscribeRequest } from "~/models/api/nova/feed/IFeedSubscribeRequest"; +import { IFeedUpdate } from "~/models/api/nova/feed/IFeedUpdate"; +import { INodeInfoResponse } from "~/models/api/nova/INodeInfoResponse"; +import { INetwork } from "~/models/config/INetwork"; +import { NetworkService } from "../networkService"; +import { NodeInfoService } from "../nodeInfoService"; + +export class NovaFeedClient { + /** + * Network configuration. + */ + protected readonly _networkConfig?: INetwork; + + /** + * Network node info. + */ + protected readonly _nodeInfo?: INodeInfoResponse; + + /** + * Socket endpoint + */ + protected readonly endpoint: string; + + /** + * The web socket to communicate on. + */ + private socket: Socket | null = null; + + /** + * The subscription id for blocks feed. + */ + private blockSubscriptionId?: string; + + /** + * Create a new instance of StardustFeedClient. + * @param endpoint The endpoint for the api. + * @param networkId The network configurations. + */ + constructor(endpoint: string, networkId: string) { + this.endpoint = endpoint; + const networkService = ServiceFactory.get("network"); + const theNetworkConfig = networkService.get(networkId); + const nodeService = ServiceFactory.get("node-info"); + const nodeInfo = theNetworkConfig?.network ? nodeService.get(theNetworkConfig?.network) : null; + + if (theNetworkConfig && nodeInfo) { + this._networkConfig = theNetworkConfig; + // @ts-expect-error We should splint the node info service for stardust vs nova to remove the ambiguity hear + this._nodeInfo = nodeInfo + } else { + console.error("[FeedClient] Couldn't initialize client for network", networkId); + } + } + + /** + * Subscribe to the feed of blocks. + * @param onBlockDataCallback the callback for block data updates. + */ + public subscribeBlocks( + onBlockDataCallback?: (blockData: IFeedBlockData) => void + ) { + this.socket = io(this.endpoint, { upgrade: true, transports: ["websocket"] }); + + // If reconnect fails then also try polling mode. + this.socket.on("reconnect_attempt", () => { + if (this.socket) { + this.socket.io.opts.transports = ["polling", "websocket"]; + } + }); + + try { + if (!this.blockSubscriptionId && this._networkConfig?.network && this.socket) { + const subscribeRequest: IFeedSubscribeRequest = { + network: this._networkConfig.network, + feedSelect: "block" + }; + + this.socket.on("subscribe", (subscribeResponse: IFeedSubscribeResponse) => { + if (subscribeResponse.error) { + console.log( + "Failed subscribing to feed", + this._networkConfig?.network, + subscribeResponse.error + ); + } else { + this.blockSubscriptionId = subscribeResponse.subscriptionId; + } + }); + + this.socket.on("block", async (update: IFeedUpdate) => { + if (update.subscriptionId === this.blockSubscriptionId) { + if (update.block) { + const block: IFeedBlockData = this.buildFeedBlockData(update.block); + onBlockDataCallback?.(block); + } + } + }); + + this.socket.emit("subscribe", subscribeRequest); + } + } catch (error) { + console.log("Failed subscribing to block feed", this._networkConfig?.network, error); + } + } + + /** + * Build the block data object. + * @param block The item source. + * @returns The feed item. + */ + private buildFeedBlockData(block: Block): IFeedBlockData { + let blockId = "unknown" + + const latestProtocolParameters = this._nodeInfo?.protocolParameters.at(-1)?.parameters.at(-1) ?? null + + if (latestProtocolParameters) { + blockId = Utils.blockId(block, latestProtocolParameters); + } + + return { + blockId, + }; + } +} diff --git a/client/src/services/stardust/stardustFeedClient.ts b/client/src/services/stardust/stardustFeedClient.ts index 56a56f8d4..1dba5eeae 100644 --- a/client/src/services/stardust/stardustFeedClient.ts +++ b/client/src/services/stardust/stardustFeedClient.ts @@ -66,7 +66,7 @@ export class StardustFeedClient { private cacheTrimTimer: NodeJS.Timer | null = null; /** - * Create a new instance of TransactionsClient. + * Create a new instance of StardustFeedClient. * @param endpoint The endpoint for the api. * @param networkId The network configurations. */ @@ -122,6 +122,7 @@ export class StardustFeedClient { this.blockSubscriptionId = subscribeResponse.subscriptionId; } }); + this.socket.on("block", async (update: IFeedUpdate) => { if (update.subscriptionId === this.blockSubscriptionId) { if (update.blockMetadata) { diff --git a/client/tsconfig.json b/client/tsconfig.json index 9f084cf5f..d14a138eb 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -43,6 +43,9 @@ ], "~features/*": [ "src/features/*" + ], + "~nova/*": [ + "src/services/nova/*" ] } }, From 5603541d7ab646b942358284c06b8e88d480d8ed Mon Sep 17 00:00:00 2001 From: Mario Sarcevic Date: Wed, 13 Dec 2023 13:34:21 +0100 Subject: [PATCH 3/9] chore(local-nova-sdk): Add step in script to rename the nodejs&wasm bindings packages before install --- setup_nova.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/setup_nova.sh b/setup_nova.sh index 981288845..d74927ba6 100755 --- a/setup_nova.sh +++ b/setup_nova.sh @@ -14,13 +14,17 @@ fi echo "Checking out nova-sdk commit $TARGET_COMMIT" git checkout "$TARGET_COMMIT" -echo "Building nodejs bindings" cd "./bindings/nodejs" +echo "Renaming nodejs sdk (sdk-nova)" +sed -i '' '2s/.*/ \"name\": \"@iota\/sdk-nova\",/' package.json +echo "Building nodejs bindings" yarn yarn build -echo "Building wasm bindings" cd "../wasm" +echo "Renaming wask sdk (sdk-wasm-nova)" +sed -i '' '2s/.*/ \"name\": \"@iota\/sdk-wasm-nova\",/' package.json +echo "Building wasm bindings" yarn yarn build From 4d17b3f621b405144d3fbc396223e193d8d7520a Mon Sep 17 00:00:00 2001 From: Mario Sarcevic Date: Wed, 13 Dec 2023 14:02:07 +0100 Subject: [PATCH 4/9] feat: Fix instantiation of sdk Client from new version --- api/package-lock.json | 2 +- api/src/initServices.ts | 36 +++++++++++++++++++----------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index 5f0ef8b1e..6fc4f6198 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -63,7 +63,7 @@ } }, "../iota-sdk/bindings/nodejs": { - "name": "@iota/sdk", + "name": "@iota/sdk-nova", "version": "1.1.3", "hasInstallScript": true, "license": "Apache-2.0", diff --git a/api/src/initServices.ts b/api/src/initServices.ts index 87938dd7a..58937f5c3 100644 --- a/api/src/initServices.ts +++ b/api/src/initServices.ts @@ -241,30 +241,32 @@ function initStardustServices(networkConfig: INetwork): void { */ function initNovaServices(networkConfig: INetwork): void { logger.verbose(`Initializing Nova services for ${networkConfig.network}`); - const novaClient = new NovaClient({ + + // eslint-disable-next-line no-void + void NovaClient.create({ nodes: [networkConfig.provider], brokerOptions: { useWs: true }, // Needed only for now in local development (NOT FOR PROD) ignoreNodeHealth: true - }); - - ServiceFactory.register( - `client-${networkConfig.network}`, - () => novaClient - ); - - // eslint-disable-next-line no-void - void NodeInfoServiceNova.build(networkConfig).then(nodeInfoService => { + }).then(novaClient => { ServiceFactory.register( - `node-info-${networkConfig.network}`, - () => nodeInfoService + `client-${networkConfig.network}`, + () => novaClient ); - const feedInstance = new NovaFeed(networkConfig.network); - ServiceFactory.register( - `feed-${networkConfig.network}`, - () => feedInstance - ); + // eslint-disable-next-line no-void + void NodeInfoServiceNova.build(networkConfig).then(nodeInfoService => { + ServiceFactory.register( + `node-info-${networkConfig.network}`, + () => nodeInfoService + ); + + const feedInstance = new NovaFeed(networkConfig.network); + ServiceFactory.register( + `feed-${networkConfig.network}`, + () => feedInstance + ); + }); }); } From 71826c2af9da1bb268bf476be82509477daf6b76 Mon Sep 17 00:00:00 2001 From: Mario Sarcevic Date: Wed, 13 Dec 2023 14:04:03 +0100 Subject: [PATCH 5/9] feat: novaFeedClient works [WiP]. Visualizer 2.0 switched to nova networks. --- client/package-lock.json | 2 +- client/src/app/routes.tsx | 16 +++++++++++++--- .../visualizer-threejs/VisualizerInstance.tsx | 1 + .../src/models/api/nova/feed/IFeedBlockData.ts | 9 ++++++++- client/src/services/nova/novaFeedClient.ts | 18 ++++++++++-------- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 31b4cf0fa..cc8e515b3 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -101,7 +101,7 @@ } }, "../iota-sdk/bindings/wasm": { - "name": "@iota/sdk-wasm", + "name": "@iota/sdk-wasm-nova", "version": "1.1.1", "license": "Apache-2.0", "dependencies": { diff --git a/client/src/app/routes.tsx b/client/src/app/routes.tsx index dde1f7194..3f7e9c9cd 100644 --- a/client/src/app/routes.tsx +++ b/client/src/app/routes.tsx @@ -35,12 +35,12 @@ import StardustOutputPage from "./routes/stardust/OutputPage"; import StardustSearch from "./routes/stardust/Search"; import StardustStatisticsPage from "./routes/stardust/statistics/StatisticsPage"; import StardustTransactionPage from "./routes/stardust/TransactionPage"; -// import { Visualizer as StardustVisualizer } from "./routes/stardust/Visualizer"; -import StardustVisualizer from "../features/visualizer-threejs/VisualizerInstance"; +import { Visualizer as StardustVisualizer } from "./routes/stardust/Visualizer"; +import NovaVisualizer from "../features/visualizer-threejs/VisualizerInstance"; import StreamsV0 from "./routes/StreamsV0"; import { StreamsV0RouteProps } from "./routes/StreamsV0RouteProps"; import { VisualizerRouteProps } from "./routes/VisualizerRouteProps"; -import { CHRYSALIS, LEGACY, STARDUST } from "~models/config/protocolVersion"; +import { CHRYSALIS, LEGACY, NOVA, STARDUST } from "~models/config/protocolVersion"; /** * Generator for keys in routes. Gives an incremented value on every next(). @@ -208,6 +208,13 @@ const buildAppRoutes = ( /> ]; + const novaRoutes = [ + + ]; + return ( {commonRoutes} @@ -220,6 +227,9 @@ const buildAppRoutes = ( {protocolVersion === STARDUST && ( withNetworkContext(stardustRoutes) )} + {protocolVersion === NOVA && ( + novaRoutes + )} ); }; diff --git a/client/src/features/visualizer-threejs/VisualizerInstance.tsx b/client/src/features/visualizer-threejs/VisualizerInstance.tsx index 69bac25b7..7eeb64b50 100644 --- a/client/src/features/visualizer-threejs/VisualizerInstance.tsx +++ b/client/src/features/visualizer-threejs/VisualizerInstance.tsx @@ -16,6 +16,7 @@ import { ServiceFactory } from "../../factories/serviceFactory"; import { useNetworkConfig } from "../../helpers/hooks/useNetworkConfig"; import { IFeedBlockData } from "../../models/api/stardust/feed/IFeedBlockData"; import { StardustFeedClient } from "../../services/stardust/stardustFeedClient"; +import { NovaFeedClient } from "../../services/nova/novaFeedClient"; import { Wrapper } from "./wrapper/Wrapper"; import "./Visualizer.scss"; import { IFeedBlockMetadata } from "~/models/api/stardust/feed/IFeedBlockMetadata"; diff --git a/client/src/models/api/nova/feed/IFeedBlockData.ts b/client/src/models/api/nova/feed/IFeedBlockData.ts index 741ed0966..fae7eeb09 100644 --- a/client/src/models/api/nova/feed/IFeedBlockData.ts +++ b/client/src/models/api/nova/feed/IFeedBlockData.ts @@ -1,7 +1,14 @@ +import { Block } from "@iota/sdk-wasm-nova/web"; + export interface IFeedBlockData { /** * The block id. */ - blockId: string; + blockId?: string; + + /** + * The block. + */ + block: Block } diff --git a/client/src/services/nova/novaFeedClient.ts b/client/src/services/nova/novaFeedClient.ts index bc6dedc51..c4c357271 100644 --- a/client/src/services/nova/novaFeedClient.ts +++ b/client/src/services/nova/novaFeedClient.ts @@ -98,6 +98,7 @@ export class NovaFeedClient { if (update.subscriptionId === this.blockSubscriptionId) { if (update.block) { const block: IFeedBlockData = this.buildFeedBlockData(update.block); + console.log("[NovaFeed] New block", block); onBlockDataCallback?.(block); } } @@ -116,16 +117,17 @@ export class NovaFeedClient { * @returns The feed item. */ private buildFeedBlockData(block: Block): IFeedBlockData { - let blockId = "unknown" - - const latestProtocolParameters = this._nodeInfo?.protocolParameters.at(-1)?.parameters.at(-1) ?? null - - if (latestProtocolParameters) { - blockId = Utils.blockId(block, latestProtocolParameters); - } + // TODO Figure out how to use Protocol parameters from SDK to build blockId + // let blockId = "unknown" + // + // const latestProtocolParameters = this._nodeInfo?.protocolParameters.at(-1)?.parameters ?? null + // if (latestProtocolParameters) { + // console.log(latestProtocolParameters) + // blockId = Utils.blockId(block, latestProtocolParameters); + // } return { - blockId, + block }; } } From f12ad0348ec6e10fc77bda340db2fe3a0a31febb Mon Sep 17 00:00:00 2001 From: Mario Sarcevic Date: Wed, 13 Dec 2023 15:15:53 +0100 Subject: [PATCH 6/9] feat: Move stardust NodeInfoService to stardust folder --- client/src/app/App.tsx | 4 ++-- client/src/index.tsx | 6 +++--- client/src/services/nova/novaApiClient.ts | 2 +- client/src/services/nova/novaFeedClient.ts | 4 ++-- client/src/services/{ => stardust}/nodeInfoService.ts | 0 5 files changed, 8 insertions(+), 8 deletions(-) rename client/src/services/{ => stardust}/nodeInfoService.ts (100%) diff --git a/client/src/app/App.tsx b/client/src/app/App.tsx index d72336ffd..d684ec8c2 100644 --- a/client/src/app/App.tsx +++ b/client/src/app/App.tsx @@ -20,7 +20,7 @@ import { INetwork } from "~models/config/INetwork"; import { MAINNET } from "~models/config/networkType"; import { STARDUST } from "~models/config/protocolVersion"; import { NetworkService } from "~services/networkService"; -import { NodeInfoService } from "~services/nodeInfoService"; +import { NodeInfoService as NodeInfoServiceStardust } from "~services/stardust/nodeInfoService"; import "./App.scss"; const App: React.FC> = ( @@ -48,7 +48,7 @@ const App: React.FC> = ( const identityResolverEnabled = networkConfig?.identityResolverEnabled ?? true; const currentNetworkName = networkConfig?.network; const isShimmer = isShimmerUiTheme(networkConfig?.uiTheme); - const nodeService = ServiceFactory.get("node-info"); + const nodeService = ServiceFactory.get("node-info-stardust"); const nodeInfo = networkConfig?.network ? nodeService.get(networkConfig?.network) : null; const withNetworkContext = networkContextWrapper(currentNetworkName, nodeInfo, networkConfig?.uiTheme); scrollToTop(); diff --git a/client/src/index.tsx b/client/src/index.tsx index fc55a4871..d703e1ae4 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -22,7 +22,7 @@ import { LegacyFeedClient } from "~services/legacy/legacyFeedClient"; import { LegacyTangleCacheService } from "~services/legacy/legacyTangleCacheService"; import { LocalStorageService } from "~services/localStorageService"; import { NetworkService } from "~services/networkService"; -import { NodeInfoService } from "~services/nodeInfoService"; +import { NodeInfoService as NodeInfoServiceStardust } from "~services/stardust/nodeInfoService"; import { SettingsService } from "~services/settingsService"; import { StardustApiClient } from "~services/stardust/stardustApiClient"; import { StardustFeedClient } from "~services/stardust/stardustFeedClient"; @@ -74,9 +74,9 @@ async function initialiseServices(): Promise { await networkService.buildCache(); ServiceFactory.register("network", () => networkService); - const nodeInfoService = new NodeInfoService(); + const nodeInfoService = new NodeInfoServiceStardust(); await nodeInfoService.buildCache(); - ServiceFactory.register("node-info", () => nodeInfoService); + ServiceFactory.register("node-info-stardust", () => nodeInfoService); ServiceFactory.register("currency", () => new CurrencyService(apiEndpoint)); ServiceFactory.register(`tangle-cache-${LEGACY}`, () => new LegacyTangleCacheService()); diff --git a/client/src/services/nova/novaApiClient.ts b/client/src/services/nova/novaApiClient.ts index 3051237bd..b318c0b19 100644 --- a/client/src/services/nova/novaApiClient.ts +++ b/client/src/services/nova/novaApiClient.ts @@ -3,7 +3,7 @@ import { INodeInfoResponse } from "~/models/api/nova/INodeInfoResponse"; import { ApiClient } from "../apiClient"; /** - * Class to handle api communications on stardust. + * Class to handle api communications on nova. */ export class NovaApiClient extends ApiClient { /** diff --git a/client/src/services/nova/novaFeedClient.ts b/client/src/services/nova/novaFeedClient.ts index c4c357271..18811cc26 100644 --- a/client/src/services/nova/novaFeedClient.ts +++ b/client/src/services/nova/novaFeedClient.ts @@ -1,5 +1,5 @@ import { - Block, Utils + Block } from "@iota/sdk-wasm-nova/web"; import { io, Socket } from "socket.io-client"; import { ServiceFactory } from "~/factories/serviceFactory"; @@ -10,7 +10,7 @@ import { IFeedUpdate } from "~/models/api/nova/feed/IFeedUpdate"; import { INodeInfoResponse } from "~/models/api/nova/INodeInfoResponse"; import { INetwork } from "~/models/config/INetwork"; import { NetworkService } from "../networkService"; -import { NodeInfoService } from "../nodeInfoService"; +import { NodeInfoService } from "../stardust/nodeInfoService"; export class NovaFeedClient { /** diff --git a/client/src/services/nodeInfoService.ts b/client/src/services/stardust/nodeInfoService.ts similarity index 100% rename from client/src/services/nodeInfoService.ts rename to client/src/services/stardust/nodeInfoService.ts From 612e0060dbd638ae082aa55ecc84bf2913e6594b Mon Sep 17 00:00:00 2001 From: Mario Sarcevic Date: Wed, 13 Dec 2023 15:27:36 +0100 Subject: [PATCH 7/9] feat: Add separate nodeInfoService for nova --- .../visualizer-threejs/VisualizerInstance.tsx | 5 +-- client/src/index.tsx | 11 +++-- client/src/services/nova/nodeInfoService.ts | 44 +++++++++++++++++++ client/src/services/nova/novaFeedClient.ts | 6 +-- .../src/services/stardust/nodeInfoService.ts | 25 +++-------- 5 files changed, 62 insertions(+), 29 deletions(-) create mode 100644 client/src/services/nova/nodeInfoService.ts diff --git a/client/src/features/visualizer-threejs/VisualizerInstance.tsx b/client/src/features/visualizer-threejs/VisualizerInstance.tsx index 7eeb64b50..463a939e2 100644 --- a/client/src/features/visualizer-threejs/VisualizerInstance.tsx +++ b/client/src/features/visualizer-threejs/VisualizerInstance.tsx @@ -15,7 +15,6 @@ import { VisualizerRouteProps } from "../../app/routes/VisualizerRouteProps"; import { ServiceFactory } from "../../factories/serviceFactory"; import { useNetworkConfig } from "../../helpers/hooks/useNetworkConfig"; import { IFeedBlockData } from "../../models/api/stardust/feed/IFeedBlockData"; -import { StardustFeedClient } from "../../services/stardust/stardustFeedClient"; import { NovaFeedClient } from "../../services/nova/novaFeedClient"; import { Wrapper } from "./wrapper/Wrapper"; import "./Visualizer.scss"; @@ -58,7 +57,7 @@ const VisualizerInstance: React.FC> = const indexToBlockId = useTangleStore(s => s.indexToBlockId); const emitterRef = useRef(null); - const feedServiceRef = useRef(null); + const feedServiceRef = useRef(null); /** * Pause on tab or window change @@ -172,7 +171,7 @@ const VisualizerInstance: React.FC> = if (!runListeners) { return; } - feedServiceRef.current = ServiceFactory.get( + feedServiceRef.current = ServiceFactory.get( `feed-${network}` ); setIsPlaying(true); diff --git a/client/src/index.tsx b/client/src/index.tsx index d703e1ae4..4c6ce16e8 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -23,6 +23,7 @@ import { LegacyTangleCacheService } from "~services/legacy/legacyTangleCacheServ import { LocalStorageService } from "~services/localStorageService"; import { NetworkService } from "~services/networkService"; import { NodeInfoService as NodeInfoServiceStardust } from "~services/stardust/nodeInfoService"; +import { NodeInfoService as NodeInfoServiceNova } from "~services/nova/nodeInfoService"; import { SettingsService } from "~services/settingsService"; import { StardustApiClient } from "~services/stardust/stardustApiClient"; import { StardustFeedClient } from "~services/stardust/stardustFeedClient"; @@ -74,9 +75,13 @@ async function initialiseServices(): Promise { await networkService.buildCache(); ServiceFactory.register("network", () => networkService); - const nodeInfoService = new NodeInfoServiceStardust(); - await nodeInfoService.buildCache(); - ServiceFactory.register("node-info-stardust", () => nodeInfoService); + const nodeInfoServiceStardust = new NodeInfoServiceStardust(); + await nodeInfoServiceStardust.buildCache(); + ServiceFactory.register("node-info-stardust", () => nodeInfoServiceStardust); + + const nodeInfoServiceNova = new NodeInfoServiceNova(); + await nodeInfoServiceNova.buildCache(); + ServiceFactory.register("node-info-nova", () => nodeInfoServiceNova); ServiceFactory.register("currency", () => new CurrencyService(apiEndpoint)); ServiceFactory.register(`tangle-cache-${LEGACY}`, () => new LegacyTangleCacheService()); diff --git a/client/src/services/nova/nodeInfoService.ts b/client/src/services/nova/nodeInfoService.ts new file mode 100644 index 000000000..07b1e123c --- /dev/null +++ b/client/src/services/nova/nodeInfoService.ts @@ -0,0 +1,44 @@ +import { ServiceFactory } from "~/factories/serviceFactory"; +import { NetworkService } from "../networkService"; +import { NovaApiClient } from "./novaApiClient"; +import { INodeInfoResponse } from "~models/api/nova/INodeInfoResponse" +import { NOVA } from "~/models/config/protocolVersion"; + +/** + * Service to handle base token info on nova. + */ +export class NodeInfoService { + /** + * Cache of the base taken infos. + */ + private _cache: { [network: string]: INodeInfoResponse } = {}; + + /** + * Get the base token info by network. + * @param network The name of the network. + * @returns The base token info. + */ + public get(network: string): INodeInfoResponse { + return this._cache[network]; + } + + /** + * Build the cache of base token infos. + */ + public async buildCache(): Promise { + const networksService = ServiceFactory.get("network"); + const novaNetworks = networksService.networks().filter(n => n.protocolVersion === NOVA); + + for (const networkDetails of novaNetworks) { + const apiClient = ServiceFactory.get(`api-client-${NOVA}`); + const network = networkDetails.network; + const response: INodeInfoResponse = await apiClient.nodeInfo({ network }); + const { baseToken, status, protocolParameters } = response; + + if (baseToken && status && protocolParameters) { + this._cache[network] = response; + } + } + } +} + diff --git a/client/src/services/nova/novaFeedClient.ts b/client/src/services/nova/novaFeedClient.ts index 18811cc26..03a440c75 100644 --- a/client/src/services/nova/novaFeedClient.ts +++ b/client/src/services/nova/novaFeedClient.ts @@ -10,7 +10,7 @@ import { IFeedUpdate } from "~/models/api/nova/feed/IFeedUpdate"; import { INodeInfoResponse } from "~/models/api/nova/INodeInfoResponse"; import { INetwork } from "~/models/config/INetwork"; import { NetworkService } from "../networkService"; -import { NodeInfoService } from "../stardust/nodeInfoService"; +import { NodeInfoService } from "./nodeInfoService"; export class NovaFeedClient { /** @@ -47,12 +47,11 @@ export class NovaFeedClient { this.endpoint = endpoint; const networkService = ServiceFactory.get("network"); const theNetworkConfig = networkService.get(networkId); - const nodeService = ServiceFactory.get("node-info"); + const nodeService = ServiceFactory.get("node-info-nova"); const nodeInfo = theNetworkConfig?.network ? nodeService.get(theNetworkConfig?.network) : null; if (theNetworkConfig && nodeInfo) { this._networkConfig = theNetworkConfig; - // @ts-expect-error We should splint the node info service for stardust vs nova to remove the ambiguity hear this._nodeInfo = nodeInfo } else { console.error("[FeedClient] Couldn't initialize client for network", networkId); @@ -118,6 +117,7 @@ export class NovaFeedClient { */ private buildFeedBlockData(block: Block): IFeedBlockData { // TODO Figure out how to use Protocol parameters from SDK to build blockId + // let blockId = "unknown" // // const latestProtocolParameters = this._nodeInfo?.protocolParameters.at(-1)?.parameters ?? null diff --git a/client/src/services/stardust/nodeInfoService.ts b/client/src/services/stardust/nodeInfoService.ts index 00bb001d4..b2b025239 100644 --- a/client/src/services/stardust/nodeInfoService.ts +++ b/client/src/services/stardust/nodeInfoService.ts @@ -1,11 +1,9 @@ import { INodeInfoBaseToken, IRent } from "@iota/sdk-wasm/web"; -import { StardustApiClient } from "./stardust/stardustApiClient"; +import { StardustApiClient } from "../stardust/stardustApiClient"; import { ServiceFactory } from "~factories/serviceFactory"; import { INodeInfoResponse as IStardustInfoResponse } from "~models/api/stardust/INodeInfoResponse"; -import { INodeInfoResponse as INovaInfoResponse } from "~models/api/nova/INodeInfoResponse"; -import { NOVA, STARDUST } from "~models/config/protocolVersion"; +import { STARDUST } from "~models/config/protocolVersion"; import { NetworkService } from "~services/networkService"; -import { NovaApiClient } from "./nova/novaApiClient"; /** * The reduced stardust node info fields relevant for Explorer. @@ -30,20 +28,20 @@ export interface IStardustNodeInfo { } /** - * Service to handle base token info. + * Service to handle base token info on stardust. */ export class NodeInfoService { /** * Cache of the base taken infos. */ - private _cache: { [network: string]: IStardustNodeInfo | INovaInfoResponse } = {}; + private _cache: { [network: string]: IStardustNodeInfo } = {}; /** * Get the base token info by network. * @param network The name of the network. * @returns The base token info. */ - public get(network: string): IStardustNodeInfo | INovaInfoResponse { + public get(network: string): IStardustNodeInfo { return this._cache[network]; } @@ -53,7 +51,6 @@ export class NodeInfoService { public async buildCache(): Promise { const networksService = ServiceFactory.get("network"); const stardustNetworks = networksService.networks().filter(n => n.protocolVersion === STARDUST); - const novaNetworks = networksService.networks().filter(n => n.protocolVersion === NOVA); for (const networkDetails of stardustNetworks) { const apiClient = ServiceFactory.get(`api-client-${STARDUST}`); @@ -65,18 +62,6 @@ export class NodeInfoService { this._cache[network] = { baseToken, protocolVersion, bech32Hrp, rentStructure }; } } - - // TODO Split this into separate services - for (const networkDetails of novaNetworks) { - const apiClient = ServiceFactory.get(`api-client-${NOVA}`); - const network = networkDetails.network; - const response: INovaInfoResponse = await apiClient.nodeInfo({ network }); - const { baseToken, status, protocolParameters } = response; - - if (baseToken && status && protocolParameters) { - this._cache[network] = response; - } - } } } From 28eb72d2395c8cbc598500362172534d2334cdd2 Mon Sep 17 00:00:00 2001 From: Mario Sarcevic Date: Wed, 13 Dec 2023 15:39:19 +0100 Subject: [PATCH 8/9] fix: Fix imports and build --- client/src/app/AppUtils.tsx | 2 +- .../models/api/nova/feed/IFeedBlockData.ts | 2 +- client/src/services/nova/novaFeedClient.ts | 43 ++++++++++++++++--- .../services/stardust/stardustFeedClient.ts | 6 +-- client/tsconfig.json | 3 -- 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/client/src/app/AppUtils.tsx b/client/src/app/AppUtils.tsx index 42abd023e..67f6c6ba0 100644 --- a/client/src/app/AppUtils.tsx +++ b/client/src/app/AppUtils.tsx @@ -4,7 +4,7 @@ import NetworkContext from "./context/NetworkContext"; import { INetwork } from "~models/config/INetwork"; import { ALPHANET, CHRYSALIS_MAINNET, DEVNET, LEGACY_MAINNET, MAINNET, NetworkType, SHIMMER, TESTNET } from "~models/config/networkType"; import { IOTA_UI, Theme } from "~models/config/uiTheme"; -import { IStardustNodeInfo } from "~services/nodeInfoService"; +import { IStardustNodeInfo } from "~services/stardust/nodeInfoService"; export const networkContextWrapper = ( currentNetwork: string | undefined, diff --git a/client/src/models/api/nova/feed/IFeedBlockData.ts b/client/src/models/api/nova/feed/IFeedBlockData.ts index fae7eeb09..0a8983b06 100644 --- a/client/src/models/api/nova/feed/IFeedBlockData.ts +++ b/client/src/models/api/nova/feed/IFeedBlockData.ts @@ -4,7 +4,7 @@ export interface IFeedBlockData { /** * The block id. */ - blockId?: string; + blockId: string; /** * The block. diff --git a/client/src/services/nova/novaFeedClient.ts b/client/src/services/nova/novaFeedClient.ts index 03a440c75..b17206daf 100644 --- a/client/src/services/nova/novaFeedClient.ts +++ b/client/src/services/nova/novaFeedClient.ts @@ -8,6 +8,8 @@ import { IFeedBlockData } from "~/models/api/nova/feed/IFeedBlockData"; import { IFeedSubscribeRequest } from "~/models/api/nova/feed/IFeedSubscribeRequest"; import { IFeedUpdate } from "~/models/api/nova/feed/IFeedUpdate"; import { INodeInfoResponse } from "~/models/api/nova/INodeInfoResponse"; +import { IFeedBlockMetadata } from "~/models/api/stardust/feed/IFeedBlockMetadata"; +import { IFeedUnsubscribeRequest } from "~/models/api/stardust/feed/IFeedUnsubscribeRequest"; import { INetwork } from "~/models/config/INetwork"; import { NetworkService } from "../networkService"; import { NodeInfoService } from "./nodeInfoService"; @@ -54,7 +56,7 @@ export class NovaFeedClient { this._networkConfig = theNetworkConfig; this._nodeInfo = nodeInfo } else { - console.error("[FeedClient] Couldn't initialize client for network", networkId); + console.error("[NovaFeedClient] Couldn't initialize client for network", networkId); } } @@ -63,7 +65,9 @@ export class NovaFeedClient { * @param onBlockDataCallback the callback for block data updates. */ public subscribeBlocks( - onBlockDataCallback?: (blockData: IFeedBlockData) => void + onBlockDataCallback?: (blockData: IFeedBlockData) => void, + // TODO Support metadata update + onMetadataUpdatedCallback?: (metadataUpdate: { [id: string]: IFeedBlockMetadata }) => void ) { this.socket = io(this.endpoint, { upgrade: true, transports: ["websocket"] }); @@ -97,7 +101,7 @@ export class NovaFeedClient { if (update.subscriptionId === this.blockSubscriptionId) { if (update.block) { const block: IFeedBlockData = this.buildFeedBlockData(update.block); - console.log("[NovaFeed] New block", block); + console.log("[NovaFeedClient] New block", block); onBlockDataCallback?.(block); } } @@ -110,15 +114,43 @@ export class NovaFeedClient { } } + /** + * Perform a request to unsubscribe to block feed events. + */ + public async unsubscribeBlocks(): Promise { + let success = false; + try { + if (this.blockSubscriptionId && this._networkConfig?.network && this.socket) { + const unsubscribeRequest: IFeedUnsubscribeRequest = { + network: this._networkConfig.network, + subscriptionId: this.blockSubscriptionId, + feedSelect: "block" + }; + + this.socket.on("unsubscribe", () => { }); + this.socket.emit("unsubscribe", unsubscribeRequest); + success = true; + } + } catch { + success = false; + console.error("[NovaFeedClient] Could not unsubscribe blocks"); + } finally { + this.socket?.disconnect(); + this.blockSubscriptionId = undefined; + this.socket = null; + } + + return success; + } + /** * Build the block data object. * @param block The item source. * @returns The feed item. */ private buildFeedBlockData(block: Block): IFeedBlockData { + const blockId = "unknown" // TODO Figure out how to use Protocol parameters from SDK to build blockId - - // let blockId = "unknown" // // const latestProtocolParameters = this._nodeInfo?.protocolParameters.at(-1)?.parameters ?? null // if (latestProtocolParameters) { @@ -127,6 +159,7 @@ export class NovaFeedClient { // } return { + blockId, block }; } diff --git a/client/src/services/stardust/stardustFeedClient.ts b/client/src/services/stardust/stardustFeedClient.ts index 1dba5eeae..070628e36 100644 --- a/client/src/services/stardust/stardustFeedClient.ts +++ b/client/src/services/stardust/stardustFeedClient.ts @@ -80,7 +80,7 @@ export class StardustFeedClient { if (theNetworkConfig) { this._networkConfig = theNetworkConfig; } else { - console.error("[FeedClient] Couldn't initialize client for network", networkId); + console.error("[StardustFeedClient] Couldn't initialize client for network", networkId); } this.setupCacheTrimJob(); @@ -232,7 +232,7 @@ export class StardustFeedClient { } } catch { success = false; - console.error("[FeedClient] Could not unsubscribe blocks"); + console.error("[StardustFeedClient] Could not unsubscribe blocks"); } finally { this.socket?.disconnect(); this.blockSubscriptionId = undefined; @@ -261,7 +261,7 @@ export class StardustFeedClient { } } catch { success = false; - console.error("[FeedClient] Could not unsubscribe milestones"); + console.error("[StardustFeedClient] Could not unsubscribe milestones"); } finally { this.socket?.disconnect(); this.milestoneSubscriptionId = undefined; diff --git a/client/tsconfig.json b/client/tsconfig.json index d14a138eb..9f084cf5f 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -43,9 +43,6 @@ ], "~features/*": [ "src/features/*" - ], - "~nova/*": [ - "src/services/nova/*" ] } }, From 2b2c0e89f6655dac5c053b2508bc32c490bd3968 Mon Sep 17 00:00:00 2001 From: Mario Sarcevic Date: Wed, 13 Dec 2023 15:40:47 +0100 Subject: [PATCH 9/9] fix: Fix NetworkSwitcher enum --- client/src/app/components/NetworkSwitcher.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/app/components/NetworkSwitcher.tsx b/client/src/app/components/NetworkSwitcher.tsx index b8afdfe77..f718cf09a 100644 --- a/client/src/app/components/NetworkSwitcher.tsx +++ b/client/src/app/components/NetworkSwitcher.tsx @@ -5,13 +5,14 @@ import MainnetIcon from "~assets/mainnet.svg?react"; import { NetworkSwitcherProps } from "./NetworkSwitcherProps"; import { getNetworkOrder } from "~helpers/networkHelper"; import { MAINNET } from "~models/config/networkType"; -import { CHRYSALIS, LEGACY, STARDUST } from "~models/config/protocolVersion"; +import { CHRYSALIS, LEGACY, NOVA, STARDUST } from "~models/config/protocolVersion"; import "./NetworkSwitcher.scss"; const PROTOCOL_VERIONS_TO_LABEL = { [LEGACY]: "Legacy", [CHRYSALIS]: "Chrysalis", - [STARDUST]: "Stardust" + [STARDUST]: "Stardust", + [NOVA]: "Nova" }; /**