Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feat: Add infrastructure for nova mqtt feed #920

Merged
merged 14 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/package-lock.json

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

44 changes: 29 additions & 15 deletions api/src/initServices.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -241,19 +242,32 @@ function initStardustServices(networkConfig: INetwork): void {
function initNovaServices(networkConfig: INetwork): void {
logger.verbose(`Initializing Nova services for ${networkConfig.network}`);

const mqttInstance = new NovaMqttClient(
{ nodes: [networkConfig.feedEndpoint], brokerOptions: { useWs: true }, ignoreNodeHealth: true }
);
ServiceFactory.register(
`mqtt-${networkConfig.network}`,
() => mqttInstance
);
// 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
}).then(novaClient => {
ServiceFactory.register(
`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
);
});
});
}

/**
Expand Down
8 changes: 8 additions & 0 deletions api/src/models/api/nova/INodeInfoResponse.ts
Original file line number Diff line number Diff line change
@@ -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;

9 changes: 9 additions & 0 deletions api/src/models/api/nova/feed/IFeedUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Block } from "@iota/sdk-nova";

type IFeedBlockUpdate = Block;

export interface IFeedUpdate {
subscriptionId: string;
block?: IFeedBlockUpdate;
}

13 changes: 12 additions & 1 deletion api/src/routes/feed/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -67,6 +68,16 @@ export async function subscribe(
)
);
}
} else if (networkConfig.protocolVersion === NOVA) {
const service = ServiceFactory.get<NovaFeed>(`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."
Expand Down
5 changes: 4 additions & 1 deletion api/src/routes/feed/unsubscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<StardustFeed>(`feed-${request.network}`);
service?.unsubscribeBlocks(request.subscriptionId);
} else {
return {
error: "Network protocol not supported for feed."
Expand Down
13 changes: 9 additions & 4 deletions api/src/routes/node/info.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -16,18 +17,22 @@ import { ValidationHelper } from "../../utils/validationHelper";
export async function info(
_: IConfiguration,
request: INetworkBoundGetRequest
): Promise<INodeInfoResponse> {
): Promise<IStardustNodeInfoResponse | INovaNodeInfoResponse> {
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 !== STARDUST) {
if (
networkConfig.protocolVersion !== STARDUST &&
networkConfig.protocolVersion !== NOVA
) {
return {};
}

const nodeService = ServiceFactory.get<NodeInfoService>(`node-info-${request.network}`);

return nodeService.getNodeInfo();
}

39 changes: 32 additions & 7 deletions api/src/services/nova/feed/novaFeed.ts
Original file line number Diff line number Diff line change
@@ -1,7 +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.
Expand All @@ -12,32 +14,55 @@ export class NovaFeed {
* The block feed subscribers (downstream).
*/
protected readonly blockSubscribers: {
[id: string]: (data: Record<string, unknown>) => Promise<void>;
[id: string]: (data: IFeedUpdate) => Promise<void>;
};

/**
* Mqtt service for data (upstream).
*/
private readonly _mqttClient: Client;

/**
* The network in context.
*/
private readonly network: string;

/**
* Creates a new instance of NovaFeed.
* @param networkId The network id.
*/
constructor(networkId: string) {
logger.debug("[NovaFeed] Constructing a Nova Feed");
this.blockSubscribers = {};
this._mqttClient = ServiceFactory.get<Client>(`mqtt-${networkId}`);
this.network = networkId;
this._mqttClient = ServiceFactory.get<Client>(`client-${networkId}`);
const nodeInfoService = ServiceFactory.get<NodeInfoService>(`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}`);
}
}

/**
* 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<void>): Promise<void> {
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.
*/
Expand All @@ -46,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<Record<string, unknown>> = {
block
};
Expand Down
44 changes: 44 additions & 0 deletions api/src/services/nova/nodeInfoService.ts
Original file line number Diff line number Diff line change
@@ -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<NodeInfoService> {
const apiClient = ServiceFactory.get<Client>(`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;
}
}
2 changes: 1 addition & 1 deletion client/package-lock.json

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

2 changes: 2 additions & 0 deletions client/script/postinstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ 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"

# identity
cp "$NODE_MODULES/@iota/identity-wasm/web/identity_wasm_bg.wasm" "$TARGET/identity_wasm_bg.wasm"
Expand Down
4 changes: 2 additions & 2 deletions client/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<RouteComponentProps<AppRouteProps>> = (
Expand Down Expand Up @@ -48,7 +48,7 @@ const App: React.FC<RouteComponentProps<AppRouteProps>> = (
const identityResolverEnabled = networkConfig?.identityResolverEnabled ?? true;
const currentNetworkName = networkConfig?.network;
const isShimmer = isShimmerUiTheme(networkConfig?.uiTheme);
const nodeService = ServiceFactory.get<NodeInfoService>("node-info");
const nodeService = ServiceFactory.get<NodeInfoServiceStardust>("node-info-stardust");
const nodeInfo = networkConfig?.network ? nodeService.get(networkConfig?.network) : null;
const withNetworkContext = networkContextWrapper(currentNetworkName, nodeInfo, networkConfig?.uiTheme);
scrollToTop();
Expand Down
4 changes: 2 additions & 2 deletions client/src/app/AppUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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/stardust/nodeInfoService";

export const networkContextWrapper = (
currentNetwork: string | undefined,
nodeInfo: IReducedNodeInfo | null,
nodeInfo: IStardustNodeInfo | null,
uiTheme: Theme | undefined
) => function withNetworkContext(wrappedComponent: ReactNode) {
return currentNetwork && nodeInfo ? (
Expand Down
5 changes: 3 additions & 2 deletions client/src/app/components/NetworkSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
};

/**
Expand Down
Loading