diff --git a/src/app/App.tsx b/src/app/App.tsx index da67f06..2d91a88 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -14,6 +14,7 @@ import { IPublicNodeStatus } from "../models/websocket/IPublicNodeStatus"; import { ISyncStatus } from "../models/websocket/ISyncStatus"; import { WebSocketTopic } from "../models/websocket/webSocketTopic"; import { AuthService } from "../services/authService"; +import { DashboardConfigService } from "../services/dashboardConfigService"; import { EventAggregator } from "../services/eventAggregator"; import { LocalStorageService } from "../services/localStorageService"; import { MetricsService } from "../services/metricsService"; @@ -53,6 +54,11 @@ class App extends AsyncComponent { */ private readonly _storageService: LocalStorageService; + /** + * The dashboard config service. + */ + private readonly _dashboardConfigService: DashboardConfigService; + /** * The metrics service. */ @@ -79,14 +85,14 @@ class App extends AsyncComponent { private _alias?: string; /** - * The lastest milestone index. + * The lastest committed slot. */ - private _lmi?: string; + private _latestCommitmentSlot?: string; /** - * The confirmed milestone index. + * The latest finalized slot. */ - private _cmi?: string; + private _latestFinalizedSlot?: string; /** * The time of the last status update. @@ -111,6 +117,7 @@ class App extends AsyncComponent { super(props); this._themeService = ServiceFactory.get("theme"); this._authService = ServiceFactory.get("auth"); + this._dashboardConfigService = ServiceFactory.get("dashboard-config"); this._metricsService = ServiceFactory.get("metrics"); this._storageService = ServiceFactory.get("local-storage"); @@ -160,12 +167,12 @@ class App extends AsyncComponent { WebSocketTopic.SyncStatus, data => { if (data) { - const lmi = data.latestCommitmentSlot ? data.latestCommitmentSlot.toString() : ""; - const smi = data.latestFinalizedSlot ? data.latestFinalizedSlot.toString() : ""; + const latestCommitmentSlot = data.latestCommitmentSlot ? data.latestCommitmentSlot.toString() : ""; + const latestFinalizedSlot = data.latestFinalizedSlot ? data.latestFinalizedSlot.toString() : ""; - if (lmi !== this._lmi || smi !== this._cmi) { - this._cmi = smi; - this._lmi = lmi; + if (latestCommitmentSlot !== this._latestCommitmentSlot || latestFinalizedSlot !== this._latestFinalizedSlot) { + this._latestCommitmentSlot = latestCommitmentSlot; + this._latestFinalizedSlot = latestFinalizedSlot; this.updateTitle(); } } @@ -386,8 +393,8 @@ class App extends AsyncComponent { if (this._alias) { title += ` (${this._alias})`; } - if (this._lmi && this._cmi) { - title += ` ${this._cmi} / ${this._lmi}`; + if (this._latestCommitmentSlot && this._latestFinalizedSlot) { + title += ` ${this._latestFinalizedSlot} / ${this._latestCommitmentSlot}`; } document.title = title; diff --git a/src/app/components/layout/InfoPanel.tsx b/src/app/components/layout/InfoPanel.tsx index 4ab10bb..b55bd2a 100644 --- a/src/app/components/layout/InfoPanel.tsx +++ b/src/app/components/layout/InfoPanel.tsx @@ -15,12 +15,12 @@ class InfoPanel extends Component { * @returns The node to render. */ public render(): ReactNode { - let cmi = ""; - let lmi = ""; + let latestFinalizedSlot = ""; + let latestCommitmentSlot = ""; if (this.props.caption === SYNC_STATUS_CAPTION && this.props.value) { - const milestone = this.props.value.split("/"); - cmi = milestone[0]; - lmi = milestone[1]; + const slots = this.props.value.split("/"); + latestFinalizedSlot = slots[0]; + latestCommitmentSlot = slots[1]; } return (
@@ -38,7 +38,7 @@ class InfoPanel extends Component { { this.props.value ?
- {cmi} / {lmi} + {latestFinalizedSlot} / {latestCommitmentSlot}
: "-" } diff --git a/src/app/routes/Visualizer.scss b/src/app/routes/Visualizer.scss index 854c36c..a123ef3 100644 --- a/src/app/routes/Visualizer.scss +++ b/src/app/routes/Visualizer.scss @@ -112,15 +112,23 @@ } } - .vertex-state--solid { - background-color: #4caaff; + .vertex-state--unknown { + background-color: #9aadce; + } + + .vertex-state--pending { + background-color: #ecdf1e; } - .vertex-state--unsolid { + .vertex-state--accepted { background-color: #8fe6fa; } - .vertex-state--referenced { + .vertex-state--confirmed { + background-color: #2260e7; + } + + .vertex-state--finalized { background-color: #61e884; } @@ -128,19 +136,11 @@ background-color: #c061e8; } - .vertex-state--conflicting { - background-color: #ff8b5c; - } - - .vertex-state--milestone { + .vertex-state--validation { background-color: #d92121; } - .vertex-state--unknown { - background-color: #9aadce; - } - .vertex-state--tip { - background-color: #ffca62; + background-color: #ff8b5c; } } diff --git a/src/app/routes/Visualizer.tsx b/src/app/routes/Visualizer.tsx index 3aa932e..383a579 100644 --- a/src/app/routes/Visualizer.tsx +++ b/src/app/routes/Visualizer.tsx @@ -7,26 +7,21 @@ import { ReactComponent as CloseIcon } from "../../assets/close.svg"; import { ReactComponent as PauseIcon } from "../../assets/pause.svg"; import { ReactComponent as PlayIcon } from "../../assets/play.svg"; import { ServiceFactory } from "../../factories/serviceFactory"; +import { BLOCK_BODY_TYPE_BASIC, BLOCK_BODY_TYPE_VALIDATION } from "../../models/tangle/blockBodyTypes"; +import { PAYLOAD_TYPE_CANDIDACY_ANNOUNCEMENT, PAYLOAD_TYPE_SIGNED_TRANSACTION, PAYLOAD_TYPE_TAGGED_DATA } from "../../models/tangle/payloadTypes"; import { IVertex } from "../../models/visualizer/IVertex"; -import { IVerticesCounts } from "../../models/visualizer/IVerticesCounts"; import { IGossipMetrics } from "../../models/websocket/IGossipMetrics"; import { WebSocketTopic } from "../../models/websocket/webSocketTopic"; +import { DashboardConfigService } from "../../services/dashboardConfigService"; import { EventAggregator } from "../../services/eventAggregator"; import { MetricsService } from "../../services/metricsService"; import { TangleService } from "../../services/tangleService"; import { ThemeService } from "../../services/themeService"; import { VisualizerService } from "../../services/visualizerService"; -import { FormatHelper } from "../../utils/formatHelper"; import AsyncComponent from "../components/layout/AsyncComponent"; import "./Visualizer.scss"; import { VisualizerState } from "./VisualizerState"; -/** - * The global type for the payload. - */ -const TAGGED_DATA_PAYLOAD_TYPE = 5; -const TRANSACTION_PAYLOAD_TYPE = 6; -const MILESTONE_PAYLOAD_TYPE = 7; /** * Visualizer panel. @@ -36,14 +31,22 @@ class Visualizer extends AsyncComponent { * Map the vetex states to colors. */ private static readonly STATE_COLOR_MAP: { [id: string]: number } = { - Solid: 0x4CAAFFFF, - Unsolid: 0x8FE6FAFF, - Referenced: 0x61E884FF, - Conflicting: 0xFF8B5CFF, - Transaction: 0xC061E8FF, - Milestone: 0xD92121FF, - Tip: 0xFFCA62FF, - Unknown: 0x9AADCEFF + unknown: 0x9AADCEFF, + pending: 0xECDF1EFF, + accepted: 0x8FE6FAFF, + confirmed: 0x2260E7FF, + finalized: 0x61E884FF, + transaction: 0xC061E8FF, + validation: 0xD92121FF, + tip: 0xFF8B5CFF + }; + + private static readonly BLOCK_STATE_TITLE_MAP: { [id: string]: string } = { + unknown: "Unknown", + pending: "Pending", + accepted: "Accepted", + confirmed: "Confirmed", + finalized: "Finalized" }; /** @@ -89,6 +92,11 @@ class Visualizer extends AsyncComponent { */ private readonly _vizualizerService: VisualizerService; + /** + * The dashboard config service. + */ + private readonly _dashboardConfigService: DashboardConfigService; + /** * The metrics service. */ @@ -129,6 +137,7 @@ class Visualizer extends AsyncComponent { this._graphElement = null; this._resize = () => this.resize(); this._vizualizerService = ServiceFactory.get("visualizer"); + this._dashboardConfigService = ServiceFactory.get("dashboard-config"); this._metricsService = ServiceFactory.get("metrics"); this._tangleService = ServiceFactory.get("tangle"); this._themeService = ServiceFactory.get("theme"); @@ -137,10 +146,10 @@ class Visualizer extends AsyncComponent { bps: "-", total: "-", tips: "-", - referenced: "-", + accepted: "-", + confirmed: "-", + finalized: "-", transactions: "-", - conflicting: "-", - solid: "-", isActive: true, theme: this._themeService.get() }; @@ -167,20 +176,21 @@ class Visualizer extends AsyncComponent { this.setState({ total: counts.total.toString(), tips: counts.tips.toString(), - referenced: counts.total > 0 - ? `${(counts.referenced / counts.total * 100).toFixed(2)}%` + accepted: counts.total > 0 + ? `${(counts.accepted / counts.total * 100).toFixed(2)}%` : "-", - transactions: counts.total > 0 - ? `${(counts.transactions / counts.total * 100).toFixed(2)}%` + confirmed: counts.total > 0 + ? `${(counts.confirmed / counts.total * 100).toFixed(2)}%` : "-", - conflicting: counts.total > 0 - ? `${(counts.conflicting / counts.total * 100).toFixed(2)}%` + finalized: counts.total > 0 + ? `${(counts.finalized / counts.total * 100).toFixed(2)}%` : "-", - solid: counts.total > 0 ? `${(counts.solid / counts.total * 100).toFixed(2)}%` : "-" + transactions: counts.total > 0 + ? `${(counts.transactions / counts.total * 100).toFixed(2)}%` + : "-" }); } - }, - (referencedId, excludedIds, counts) => this.referenceVertex(referencedId, excludedIds, counts) + } ); this._gossipMetricsSubscription = this._metricsService.subscribe( @@ -276,65 +286,65 @@ class Visualizer extends AsyncComponent { {this.state.tips}
- Referenced + Accepted
- {this.state.referenced} + {this.state.accepted}
- Transactions + Confirmed
- {this.state.transactions} + {this.state.confirmed}
- Conflicting + Finalized
- {this.state.conflicting} + {this.state.finalized}
- Solid + Transactions
- {this.state.solid} + {this.state.transactions}
-
-
Solid
+
+
Pending
-
-
Unsolid
+
+
Accepted
-
-
Referenced
+
+
Confirmed
+
+
+
+
Finalized
Transaction
-
-
Conflicting
+
+
Validation
-
-
Milestone
+
+
Tip
Unknown
-
-
-
Tip
-
{this.state.selected && this._graphElement && ( @@ -347,11 +357,11 @@ class Visualizer extends AsyncComponent {
-

{this.state.selected.state}{this.state.selected.title}

+

{this.state.selected.blockStateTitle}{this.state.selected.payloadTitle}

- - {this.state.selected.vertex.fullId} - + { + this.calculateBlockLink(this.state.selected.vertex) === "" + ?
{this.state.selected.vertex.fullId}
+ : + + {this.state.selected.vertex.fullId} + + }
)} - {this.state.selected.payload && - this.state.selected.payload.type === TRANSACTION_PAYLOAD_TYPE && ( + {this.state.selected.block?.body?.type === BLOCK_BODY_TYPE_BASIC && + this.state.selected.block.body.payload?.type === PAYLOAD_TYPE_TAGGED_DATA && (
- Total -
-
- {this.state.selected.payload.amount} -
-
- )} - {this.state.selected.payload && - this.state.selected.payload.type === MILESTONE_PAYLOAD_TYPE && ( - -
- Index + Tag UTF8
- {this.state.selected.payload.index} + {Converter.hexToUtf8(this.state.selected.block?.body.payload.tag)}
- Date + Tag Hex
- {FormatHelper.date(this.state.selected.payload.timestamp, false)} + {this.state.selected.block?.body.payload.tag}
)} - {this.state.selected.payload && - this.state.selected.payload.type === TAGGED_DATA_PAYLOAD_TYPE && - this.state.selected.payload.tag && ( + {this.state.selected.block?.body?.type === BLOCK_BODY_TYPE_BASIC && + this.state.selected.block.body.payload?.type === PAYLOAD_TYPE_SIGNED_TRANSACTION && ( +
+ )} + {this.state.selected.block?.body?.type === BLOCK_BODY_TYPE_BASIC && + this.state.selected.block.body.payload?.type === PAYLOAD_TYPE_CANDIDACY_ANNOUNCEMENT && (
- Tag UTF8 + Candidate
- {Converter.hexToUtf8(this.state.selected.payload.tag)} -
-
- Tag Hex -
-
- {this.state.selected.payload.tag} + {this.state.selected.block?.header.issuerId}
)} + + {this.state.selected.block?.body?.type === BLOCK_BODY_TYPE_VALIDATION && + +
+ Validator +
+
+ {this.state.selected.block?.header.issuerId} +
+
+ Highest Supported Version +
+
+ {this.state.selected.block?.body.highestSupportedVersion} +
+
+ Protocol Parameters Hash +
+
+ {this.state.selected.block?.body.protocolParametersHash} +
+
}
@@ -456,7 +479,7 @@ class Visualizer extends AsyncComponent { this._graphics.node(node => Viva.Graph.View.webglSquare( this.calculateSize(node.data), - `#${Visualizer.STATE_COLOR_MAP[this.calculateState(node.data)].toString(16)}` + `#${Visualizer.STATE_COLOR_MAP[this.calculateVertexState(node.data)].toString(16)}` )); this._graphics.link(() => Viva.Graph.View.webglLine( @@ -546,7 +569,7 @@ class Visualizer extends AsyncComponent { if (node) { const nodeUI = this._graphics.getNodeUI(id); if (nodeUI) { - nodeUI.color = Visualizer.STATE_COLOR_MAP[this.calculateState(node.data)]; + nodeUI.color = Visualizer.STATE_COLOR_MAP[this.calculateVertexState(node.data)]; nodeUI.size = this.calculateSize(node.data); } } @@ -560,29 +583,61 @@ class Visualizer extends AsyncComponent { * @param vertex The vertex to calculate the state for. * @returns The state. */ - private calculateState(vertex?: IVertex): string { + private calculateVertexState(vertex?: IVertex): string { if (!vertex?.parents) { - return "Unknown"; + return "unknown"; } - if (vertex.isMilestone) { - return "Milestone"; + + if (vertex.isValidationBlock) { + return "validation"; } + if (vertex.isTip) { - return "Tip"; + return "tip"; } - if (vertex.isConflicting) { - return "Conflicting"; + + if (vertex.isFinalized || vertex.isConfirmed) { + if (vertex.isBasicBlockSignedTransaction) { + return "transaction"; + } + + if (vertex.isFinalized) { + return "finalized"; + } + + return "confirmed"; + } + + if (vertex.isAccepted) { + return "accepted"; } - if (vertex.isReferenced) { - if (vertex.isTransaction) { - return "Transaction"; + + return "pending"; + } + + /** + * Calculate the state for the block. + * @param vertex The vertex to calculate the state for. + * @returns The block state. + */ + private calculateBlockState(vertex?: IVertex): string { + if (!vertex?.parents) { + return "unknown"; + } + + if (vertex.isFinalized || vertex.isConfirmed) { + if (vertex.isFinalized) { + return "finalized"; } - return "Referenced"; + + return "confirmed"; } - if (vertex.isSolid) { - return "Solid"; + + if (vertex.isAccepted) { + return "accepted"; } - return "Unsolid"; + + return "pending"; } /** @@ -594,7 +649,7 @@ class Visualizer extends AsyncComponent { if (!vertex?.parents) { return 10; } - if (vertex.isSelected || vertex.isMilestone) { + if (vertex.isSelected) { return 30; } return 20; @@ -622,52 +677,6 @@ class Visualizer extends AsyncComponent { } } - /** - * Update the referenced information. - * @param referencedId The vertex that has been referenced. - * @param excludedIds Excluded ids. - * @param counts The visualizer counts. - */ - private referenceVertex(referencedId: string, excludedIds: string[], counts: IVerticesCounts): void { - if (this._graph) { - const startNode = this._graph.getNode(referencedId); - - if (startNode) { - const seenBackwards: Viva.Graph.INode[] = []; - this.dfsIterator( - startNode, - nodeId => { - if (this._graph) { - const parent = this._graph.getNode(nodeId); - if (!parent?.data) { - return true; - } - - if (!parent.data.isReferenced && !parent.data.isConflicting) { - if (excludedIds.includes(parent.data.shortId)) { - counts.conflicting++; - parent.data.isConflicting = true; - this.updateVertex(parent.data); - return false; - } - - counts.referenced++; - parent.data.isReferenced = true; - this.updateVertex(parent.data); - return false; - } - } - - return true; - }, - undefined, - false, - seenBackwards - ); - } - } - } - /** * Walk the graph. * @param startNode The node to start with. @@ -776,32 +785,47 @@ class Visualizer extends AsyncComponent { this.setState({ selected: { vertex: node?.data, - state: this.calculateState(node.data) + vertexState: this.calculateVertexState(node.data), + blockStateTitle: Visualizer.BLOCK_STATE_TITLE_MAP[this.calculateBlockState(node.data)] } }, async () => { if (node.data?.fullId) { - const payload = node.data.payload; - let payloadTitle = ""; - - if (payload) { - if (payload.type === TRANSACTION_PAYLOAD_TYPE) { - payloadTitle = " - Transaction"; - } else if (payload.type === MILESTONE_PAYLOAD_TYPE) { - payloadTitle = ""; - } else if (payload.type === TAGGED_DATA_PAYLOAD_TYPE) { - payloadTitle = " - Tagged data"; + const block = await this._tangleService.block(node.data.fullId); + let payloadTitle = " - Unknown"; + + if (block?.body) { + switch (block?.body.type) { + case BLOCK_BODY_TYPE_BASIC: + switch (block?.body.payload?.type) { + case PAYLOAD_TYPE_TAGGED_DATA: + payloadTitle = " - Tagged data"; + break; + case PAYLOAD_TYPE_SIGNED_TRANSACTION: + payloadTitle = " - Signed transaction"; + break; + case PAYLOAD_TYPE_CANDIDACY_ANNOUNCEMENT: + payloadTitle = " - Candidacy announcement"; + break; + default: + break; + } + break; + case BLOCK_BODY_TYPE_VALIDATION: + payloadTitle = " - Validation block"; + break; + default: + break; } - } else if (node.data.isMilestone) { - payloadTitle = " - Checkpoint"; } this.setState({ selected: { vertex: node?.data, - state: this.calculateState(node.data), - payload, - title: payloadTitle + vertexState: this.calculateVertexState(node.data), + blockStateTitle: Visualizer.BLOCK_STATE_TITLE_MAP[this.calculateBlockState(node.data)], + block, + payloadTitle } }); } @@ -817,10 +841,12 @@ class Visualizer extends AsyncComponent { * @returns The url for the block. */ private calculateBlockLink(vertex?: IVertex): string { - return vertex?.fullId - ? `${window.location.protocol}//${window.location.host}` + - `${process.env.PUBLIC_URL}/explorer/block/${vertex.fullId}` - : ""; + const explorerURL = this._dashboardConfigService.getExplorerURL(); + if (explorerURL === "" || !vertex?.fullId) { + return ""; + } + + return `${explorerURL}/block/${vertex.fullId}`; } /** diff --git a/src/app/routes/VisualizerState.ts b/src/app/routes/VisualizerState.ts index 5916e67..e890fc9 100644 --- a/src/app/routes/VisualizerState.ts +++ b/src/app/routes/VisualizerState.ts @@ -1,6 +1,4 @@ -import { IMilestonePayload } from "../../models/payloads/IMilestonePayload"; -import { ITaggedDataPayload } from "../../models/payloads/ITaggedDataPayload"; -import { ITransactionPayload } from "../../models/payloads/ITransactionPayload"; +import { IBlock } from "../../models/tangle/IBlock"; import { IVertex } from "../../models/visualizer/IVertex"; export interface VisualizerState { @@ -20,24 +18,24 @@ export interface VisualizerState { tips: string; /** - * Referenced. + * Accepted. */ - referenced: string; + accepted: string; /** - * Transactions. + * Confirmed. */ - transactions: string; + confirmed: string; /** - * Conflicting. + * Finalized. */ - conflicting: string; + finalized: string; /** - * Solid. + * Transactions. */ - solid: string; + transactions: string; /** * Is the rendering active. @@ -54,19 +52,24 @@ export interface VisualizerState { vertex: IVertex; /** - * Select item state. + * Select item vertex state. + */ + vertexState: string; + + /** + * Select item block state title. */ - state: string; + blockStateTitle?: string; /** - * Select item title. + * Select item payload title. */ - title?: string; + payloadTitle?: string; /** - * Select item payload. + * Select item block. */ - payload?: ITransactionPayload | ITaggedDataPayload | IMilestonePayload; + block?: IBlock; }; /** diff --git a/src/assets/iota-core/themes/dark/banner.svg b/src/assets/iota-core/themes/dark/banner.svg index b9d13ac..20feab8 100644 --- a/src/assets/iota-core/themes/dark/banner.svg +++ b/src/assets/iota-core/themes/dark/banner.svg @@ -6,7 +6,7 @@ fill="none" version="1.1" id="svg2530" - sodipodi:docname="banner2.svg" + sodipodi:docname="banner.svg" xml:space="preserve" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" @@ -25,13 +25,13 @@ showgrid="false" inkscape:zoom="1.9230067" inkscape:cx="137.54502" - inkscape:cy="36.921348" - inkscape:window-width="960" + inkscape:cy="37.441367" + inkscape:window-width="1920" inkscape:window-height="993" inkscape:window-x="0" inkscape:window-y="25" - inkscape:window-maximized="0" - inkscape:current-layer="svg2530" />IOTA-CORE + style="fill:#9aadce;fill-opacity:1" /> diff --git a/src/assets/iota-core/themes/light/banner.svg b/src/assets/iota-core/themes/light/banner.svg index 91635b0..0d4f489 100644 --- a/src/assets/iota-core/themes/light/banner.svg +++ b/src/assets/iota-core/themes/light/banner.svg @@ -6,7 +6,7 @@ fill="none" version="1.1" id="svg2530" - sodipodi:docname="banner2.svg" + sodipodi:docname="banner.svg" xml:space="preserve" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" @@ -25,13 +25,13 @@ showgrid="false" inkscape:zoom="1.9230067" inkscape:cx="65.262383" - inkscape:cy="37.701377" - inkscape:window-width="960" - inkscape:window-height="992" + inkscape:cy="37.961386" + inkscape:window-width="1920" + inkscape:window-height="993" inkscape:window-x="0" inkscape:window-y="25" - inkscape:window-maximized="0" - inkscape:current-layer="svg2530" />IOTA-CORE + style="fill:#485776;fill-opacity:1" /> diff --git a/src/index.tsx b/src/index.tsx index 9a460a4..af8f9e4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,6 +7,7 @@ import { ServiceFactory } from "./factories/serviceFactory"; import "./index.scss"; import { IBrandConfiguration } from "./models/IBrandConfiguration"; import { AuthService } from "./services/authService"; +import { DashboardConfigService } from "./services/dashboardConfigService"; import { EventAggregator } from "./services/eventAggregator"; import { LocalStorageService } from "./services/localStorageService"; import { MetricsService } from "./services/metricsService"; @@ -58,6 +59,10 @@ async function initServices(): Promise { themeService.initialize(); ServiceFactory.register("theme", () => themeService); + const dashboardConfigService = new DashboardConfigService(); + await dashboardConfigService.initialize(); + ServiceFactory.register("dashboard-config", () => dashboardConfigService); + const nodeConfigService = new NodeConfigService(); await nodeConfigService.initialize(); ServiceFactory.register("node-config", () => nodeConfigService); diff --git a/src/models/IClient.ts b/src/models/IClient.ts index f2d8d35..743bd9c 100644 --- a/src/models/IClient.ts +++ b/src/models/IClient.ts @@ -1,5 +1,7 @@ +import { HexEncodedString } from "./hexEncodedTypes"; import type { INodeInfo } from "./info/INodeInfo"; import { IPeer } from "./peers/IPeer"; +import { IBlock } from "./tangle/IBlock"; /** * Client interface definition for API communication. */ @@ -9,6 +11,12 @@ export interface IClient { * @returns The node information. */ info(): Promise; + /** + * Get the block data by id. + * @param blockId The block to get the data for. + * @returns The block data. + */ + block(blockId: HexEncodedString): Promise; /** * Add a new peer. * @param multiAddress The address of the peer to add. diff --git a/src/models/clients/singleNodeClient.ts b/src/models/clients/singleNodeClient.ts index 916ab89..04b9df9 100644 --- a/src/models/clients/singleNodeClient.ts +++ b/src/models/clients/singleNodeClient.ts @@ -1,10 +1,12 @@ // Copyright 2020 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 import { Converter } from "@iota/util.js"; +import { HexEncodedString } from "../hexEncodedTypes"; import { IClient } from "../IClient"; import { INodeInfo } from "../info/INodeInfo"; import { IResponse } from "../IResponse"; import { IPeer } from "../peers/IPeer"; +import { IBlock } from "../tangle/IBlock"; import { ClientError } from "./clientError"; import type { SingleNodeClientOptions } from "./singleNodeClientOptions"; @@ -95,6 +97,15 @@ export class SingleNodeClient implements IClient { return this.fetchJson(this._coreApiPath, "get", "info"); } + /** + * Get the block data by id. + * @param blockId The block to get the data for. + * @returns The block data. + */ + public async block(blockId: HexEncodedString): Promise { + return this.fetchJson(this._coreApiPath, "get", `blocks/${blockId}`); + } + /** * Add a new peer. * @param multiAddress The address of the peer to add. diff --git a/src/models/payloads/IMilestonePayload.ts b/src/models/payloads/IMilestonePayload.ts deleted file mode 100644 index 9eee315..0000000 --- a/src/models/payloads/IMilestonePayload.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ITypeBase } from "../ITypeBase"; -/** - * The global type for the payload. - */ -export declare const MILESTONE_PAYLOAD_TYPE = 7; -/** - * Milestone payload. - */ -export interface IMilestonePayload extends ITypeBase<7> { - /** - * The index name. - */ - index: number; - /** - * The timestamp of the milestone. - */ - timestamp: number; -} diff --git a/src/models/payloads/ITransactionPayload.ts b/src/models/payloads/ITransactionPayload.ts deleted file mode 100644 index 5ef2d6c..0000000 --- a/src/models/payloads/ITransactionPayload.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ITypeBase } from "../ITypeBase"; -/** - * The global type for the payload. - */ -export declare const TRANSACTION_PAYLOAD_TYPE = 6; -/** - * Transaction payload. - */ -export interface ITransactionPayload extends ITypeBase<6> { - /** - * The balance on the input side. - */ - amount: number; -} diff --git a/src/models/payloads/payloadTypes.ts b/src/models/payloads/payloadTypes.ts deleted file mode 100644 index 83ec7db..0000000 --- a/src/models/payloads/payloadTypes.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { IMilestonePayload } from "./IMilestonePayload"; -import type { ITaggedDataPayload } from "./ITaggedDataPayload"; -import type { ITransactionPayload } from "./ITransactionPayload"; -/** - * All of the payload types. - */ -export declare type PayloadTypes = ITransactionPayload | IMilestonePayload | ITaggedDataPayload; diff --git a/src/models/tangle/IBlock.ts b/src/models/tangle/IBlock.ts new file mode 100644 index 0000000..91158fe --- /dev/null +++ b/src/models/tangle/IBlock.ts @@ -0,0 +1,10 @@ +import { BlockBodyTypes } from "./blockBodyTypes"; +import { IBlockHeader } from "./IBlockHeader"; + +/** + * Block. + */ +export interface IBlock { + header: IBlockHeader; + body: BlockBodyTypes; +} diff --git a/src/models/tangle/IBlockBodyBasic.ts b/src/models/tangle/IBlockBodyBasic.ts new file mode 100644 index 0000000..73727e7 --- /dev/null +++ b/src/models/tangle/IBlockBodyBasic.ts @@ -0,0 +1,17 @@ +import { ITypeBase } from "../ITypeBase"; +import { PayloadTypes } from "./payloadTypes"; + +/** + * Basic block body. + */ +export interface IBlockBodyBasic extends ITypeBase<0> { + /** + * The inner payload of the block. Can be nil. + */ + payload: PayloadTypes | null; + + /** + * The maximum amount of mana that can be burned in this block. + */ + maxBurnedMana: number; +} diff --git a/src/models/tangle/IBlockBodyValidation.ts b/src/models/tangle/IBlockBodyValidation.ts new file mode 100644 index 0000000..57f6808 --- /dev/null +++ b/src/models/tangle/IBlockBodyValidation.ts @@ -0,0 +1,17 @@ +import { HexEncodedString } from "../hexEncodedTypes"; +import type { ITypeBase } from "../ITypeBase"; + +/** + * Validation block body. + */ +export interface IBlockBodyValidation extends ITypeBase<1> { + /** + * The highest version of the protocol that is supported by the validator. + */ + highestSupportedVersion: number; + + /** + * The hash of the protocol parameters for the HighestSupportedVersion. + */ + protocolParametersHash: HexEncodedString; +} diff --git a/src/models/tangle/IBlockHeader.ts b/src/models/tangle/IBlockHeader.ts new file mode 100644 index 0000000..6a93e71 --- /dev/null +++ b/src/models/tangle/IBlockHeader.ts @@ -0,0 +1,11 @@ +import { HexEncodedString } from "../hexEncodedTypes"; + +/** + * Block Header. + */ +export interface IBlockHeader { + /** + * The ID of the issuer. + */ + issuerId: HexEncodedString; +} diff --git a/src/models/tangle/IPayloadCandidacyAnnouncement.ts b/src/models/tangle/IPayloadCandidacyAnnouncement.ts new file mode 100644 index 0000000..c930fe9 --- /dev/null +++ b/src/models/tangle/IPayloadCandidacyAnnouncement.ts @@ -0,0 +1,6 @@ +import type { ITypeBase } from "../ITypeBase"; + +/** + * Candidacy announcement payload. + */ +export type IPayloadCandidacyAnnouncement = ITypeBase<2>; diff --git a/src/models/tangle/IPayloadSignedTransaction.ts b/src/models/tangle/IPayloadSignedTransaction.ts new file mode 100644 index 0000000..a727770 --- /dev/null +++ b/src/models/tangle/IPayloadSignedTransaction.ts @@ -0,0 +1,6 @@ +import type { ITypeBase } from "../ITypeBase"; + +/** + * Signed Transaction payload. + */ +export type IPayloadSignedTransaction = ITypeBase<1>; diff --git a/src/models/payloads/ITaggedDataPayload.ts b/src/models/tangle/IPayloadTaggedData.ts similarity index 59% rename from src/models/payloads/ITaggedDataPayload.ts rename to src/models/tangle/IPayloadTaggedData.ts index b481d79..38fd13a 100644 --- a/src/models/payloads/ITaggedDataPayload.ts +++ b/src/models/tangle/IPayloadTaggedData.ts @@ -1,13 +1,10 @@ import { HexEncodedString } from "../hexEncodedTypes"; import type { ITypeBase } from "../ITypeBase"; -/** - * The global type for the payload. - */ -export declare const TAGGED_DATA_PAYLOAD_TYPE = 5; + /** * Tagged data payload. */ -export interface ITaggedDataPayload extends ITypeBase<5> { +export interface IPayloadTaggedData extends ITypeBase<0> { /** * The tag to use to categorize the data. */ diff --git a/src/models/tangle/blockBodyTypes.ts b/src/models/tangle/blockBodyTypes.ts new file mode 100644 index 0000000..7203ee9 --- /dev/null +++ b/src/models/tangle/blockBodyTypes.ts @@ -0,0 +1,13 @@ +import { IBlockBodyBasic } from "./IBlockBodyBasic"; +import { IBlockBodyValidation } from "./IBlockBodyValidation"; + +/** + * The global types for the block bodies. + */ +export const BLOCK_BODY_TYPE_BASIC = 0; +export const BLOCK_BODY_TYPE_VALIDATION = 1; + +/** + * All of the block body types. + */ +export declare type BlockBodyTypes = IBlockBodyBasic | IBlockBodyValidation; diff --git a/src/models/tangle/payloadTypes.ts b/src/models/tangle/payloadTypes.ts new file mode 100644 index 0000000..d1150f8 --- /dev/null +++ b/src/models/tangle/payloadTypes.ts @@ -0,0 +1,15 @@ +import { IPayloadCandidacyAnnouncement } from "./IPayloadCandidacyAnnouncement"; +import type { IPayloadSignedTransaction } from "./IPayloadSignedTransaction"; +import type { IPayloadTaggedData } from "./IPayloadTaggedData"; + +/** + * The global types for the payloads. + */ +export const PAYLOAD_TYPE_TAGGED_DATA = 0; +export const PAYLOAD_TYPE_SIGNED_TRANSACTION = 1; +export const PAYLOAD_TYPE_CANDIDACY_ANNOUNCEMENT = 2; + +/** + * All of the payload types. + */ +export declare type PayloadTypes = IPayloadTaggedData | IPayloadSignedTransaction | IPayloadCandidacyAnnouncement; diff --git a/src/models/visualizer/IVertex.ts b/src/models/visualizer/IVertex.ts index 7ad6cec..c793ba7 100644 --- a/src/models/visualizer/IVertex.ts +++ b/src/models/visualizer/IVertex.ts @@ -1,7 +1,3 @@ -import { IMilestonePayload } from "../payloads/IMilestonePayload"; -import { ITaggedDataPayload } from "../payloads/ITaggedDataPayload"; -import { ITransactionPayload } from "../payloads/ITransactionPayload"; - export interface IVertex { /** * What is the id for the vertex. @@ -13,43 +9,61 @@ export interface IVertex { */ shortId: string; + /** + * Slot of the block. + */ + slot?: number; + /** * Parent Ids. */ parents?: string; - payload?: IMilestonePayload | ITaggedDataPayload | ITransactionPayload; - /** - * Is the block solid. + * Block State. */ - isSolid?: boolean; + blockState?: string; /** - * Is it a transaction. + * Is the block a basic block tagged data. */ - isTransaction?: boolean; + isBasicBlockTaggedData?: boolean; /** - * Is the block conflicting. + * Is the block a basic block signed transaction. */ - isConflicting?: boolean; + isBasicBlockSignedTransaction?: boolean; /** - * Is the block referenced. + * Is the block a basic block candidacy announcement. */ - isReferenced?: boolean; + isBasicBlockCandidacyAnnouncement?: boolean; /** - * Is it a milestone. + * Is the block a validation block. */ - isMilestone?: boolean; + isValidationBlock?: boolean; /** - * Is it a tip. + * Is the block a tip. */ isTip?: boolean; + /** + * Is the block accepted. + */ + isAccepted?: boolean; + + /** + * Is the block confirmed. + */ + isConfirmed?: boolean; + + /** + * Is the block finalized. + */ + isFinalized?: boolean; + /** * Is it selected. */ diff --git a/src/models/visualizer/IVerticesCounts.ts b/src/models/visualizer/IVerticesCounts.ts index 9a31917..08cfce9 100644 --- a/src/models/visualizer/IVerticesCounts.ts +++ b/src/models/visualizer/IVerticesCounts.ts @@ -4,21 +4,21 @@ export interface IVerticesCounts { */ total: number; /** - * How many solid vertices. + * How many accepted vertices. */ - solid: number; + accepted: number; /** - * How many referenced vertices. + * How many confirmed vertices. */ - referenced: number; + confirmed: number; /** - * How many transaction vertices. + * How many finalized vertices. */ - transactions: number; + finalized: number; /** - * How many conflicting vertices. + * How many transaction vertices. */ - conflicting: number; + transactions: number; /** * How many tip vertices. */ diff --git a/src/models/websocket/ICommitment.ts b/src/models/websocket/ICommitment.ts deleted file mode 100644 index 46e1778..0000000 --- a/src/models/websocket/ICommitment.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint-disable camelcase */ -export interface IVisualizerCommitmentInfo { - slot: number; - commitmentId: string; - rmc: string; -} diff --git a/src/models/websocket/IVisualizerBlockStateInfo.ts b/src/models/websocket/IVisualizerBlockStateInfo.ts new file mode 100644 index 0000000..faa6c78 --- /dev/null +++ b/src/models/websocket/IVisualizerBlockStateInfo.ts @@ -0,0 +1,5 @@ +/* eslint-disable camelcase */ +export interface IVisualizerBlockStateInfo { + id: string; + blockState: string; +} diff --git a/src/models/websocket/IVisualizerConfirmationInfo.ts b/src/models/websocket/IVisualizerConfirmationInfo.ts deleted file mode 100644 index 9ab6860..0000000 --- a/src/models/websocket/IVisualizerConfirmationInfo.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable camelcase */ -export interface IVisualizerConfirmationInfo { - ids: string[]; - excludedIds?: string[]; -} diff --git a/src/models/websocket/IVisualizerMetaInfo.ts b/src/models/websocket/IVisualizerMetaInfo.ts deleted file mode 100644 index aef9b0b..0000000 --- a/src/models/websocket/IVisualizerMetaInfo.ts +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable camelcase */ -export interface IVisualizerMetaInfo { - id: string; -} diff --git a/src/models/websocket/IVisualizerVertex.ts b/src/models/websocket/IVisualizerVertex.ts index 29289db..70b677b 100644 --- a/src/models/websocket/IVisualizerVertex.ts +++ b/src/models/websocket/IVisualizerVertex.ts @@ -2,14 +2,10 @@ export interface IVisualizerVertex { id: string; parents: string; - isSolid: boolean; - isAccepted: boolean; - isReferenced: boolean; - isConflicting: boolean; - isTransaction: boolean; - isMilestone: boolean; + blockState: string; + isBasicBlockTaggedData: boolean; + isBasicBlockSignedTransaction: boolean; + isBasicBlockCandidacyAnnouncement: boolean; + isValidationBlock: boolean; isTip: boolean; - - // info set by the visualizer itself - isSelected: boolean; } diff --git a/src/models/websocket/webSocketTopic.ts b/src/models/websocket/webSocketTopic.ts index d2068f3..324687f 100644 --- a/src/models/websocket/webSocketTopic.ts +++ b/src/models/websocket/webSocketTopic.ts @@ -7,8 +7,6 @@ export enum WebSocketTopic { NetworkMetrics = 5, DatabaseSizeMetric = 6, VisualizerVertex = 7, - VisualizerSolidInfo = 8, - VisualizerConfirmationInfo = 9, - VisualizerCommitmentInfo = 10, - VisualizerTipInfo = 11, + VisualizerBlockStateInfo = 8, + VisualizerTipInfo = 9, } diff --git a/src/services/dashboardConfigService.ts b/src/services/dashboardConfigService.ts new file mode 100644 index 0000000..fcae13c --- /dev/null +++ b/src/services/dashboardConfigService.ts @@ -0,0 +1,61 @@ +import { ServiceFactory } from "../factories/serviceFactory"; +import { FetchHelper } from "../utils/fetchHelper"; +import { AuthService } from "./authService"; +/** + * Service to handle getting confiuration from the dashboard backend. + */ +export class DashboardConfigService { + /** + * The explorer URL. + */ + private _explorerURL: string; + + /** + * The auth service. + */ + private readonly _authService: AuthService; + + /** + * Create a new instance of DashboardConfigService. + */ + constructor() { + this._authService = ServiceFactory.get("auth"); + this._explorerURL = ""; + } + + /** + * Initialise DashboardConfigService. + */ + public async initialize(): Promise { + try { + this._explorerURL = await this.getExplorerURLBackend(); + } catch {} + } + + /** + * Get the explorer URL. + * @returns The explorer URL. + */ + public getExplorerURL(): string { + return this._explorerURL; + } + + /** + * Get the explorer URL from the backend. + * @returns The explorer URL. + */ + private async getExplorerURLBackend(): Promise { + const headers = this._authService.buildAuthHeaders(); + + const response = await FetchHelper.json( + `${window.location.protocol}//${window.location.host}`, + "/dashboard/settings", + "get", + undefined, + headers); + + return response.explorerUrl; + } +} diff --git a/src/services/nodeConfigService.ts b/src/services/nodeConfigService.ts index 96ee370..5794b11 100644 --- a/src/services/nodeConfigService.ts +++ b/src/services/nodeConfigService.ts @@ -41,7 +41,7 @@ export class NodeConfigService { } /** - * Get the netwoork id. + * Get the network id. * @returns The network id. */ public getNetworkId(): string { @@ -50,7 +50,7 @@ export class NodeConfigService { /** * Set the network id. - * @param networkId The new blind mode. + * @param networkId The new network id. */ public setNetworkId(networkId: string): void { this._networkId = networkId; diff --git a/src/services/tangleService.ts b/src/services/tangleService.ts index 1f74408..71321e9 100644 --- a/src/services/tangleService.ts +++ b/src/services/tangleService.ts @@ -2,6 +2,7 @@ import { ServiceFactory } from "../factories/serviceFactory"; import { SingleNodeClient } from "../models/clients/singleNodeClient"; import { IClient } from "../models/IClient"; import { INodeInfo } from "../models/info/INodeInfo"; +import { IBlock } from "../models/tangle/IBlock"; import { AuthService } from "./authService"; /** * Service to handle api requests. @@ -34,6 +35,18 @@ export class TangleService { return this._nodeInfo; } + /** + * Get the block payload. + * @param blockId The block to get. + * @returns The response data. + */ + public async block(blockId: string): Promise { + try { + const client = this.buildClient(); + return await client.block(blockId); + } catch {} + } + /** * Add a peer. * @param peerAddress The peer address. diff --git a/src/services/visualizerService.ts b/src/services/visualizerService.ts index 90bcc4a..04927e0 100644 --- a/src/services/visualizerService.ts +++ b/src/services/visualizerService.ts @@ -2,12 +2,12 @@ import { ServiceFactory } from "../factories/serviceFactory"; import { IVertex } from "../models/visualizer/IVertex"; import { IVerticesCounts } from "../models/visualizer/IVerticesCounts"; import { VisualizerVertexOperation } from "../models/visualizer/visualizerVertexOperation"; -import { IVisualizerCommitmentInfo } from "../models/websocket/ICommitment"; -import { IVisualizerConfirmationInfo } from "../models/websocket/IVisualizerConfirmationInfo"; -import { IVisualizerMetaInfo } from "../models/websocket/IVisualizerMetaInfo"; +import { ISyncStatus } from "../models/websocket/ISyncStatus"; +import { IVisualizerBlockStateInfo } from "../models/websocket/IVisualizerBlockStateInfo"; import { IVisualizerTipInfo } from "../models/websocket/IVisualizerTipInfo"; import { IVisualizerVertex } from "../models/websocket/IVisualizerVertex"; import { WebSocketTopic } from "../models/websocket/webSocketTopic"; +import { DataHelper } from "../utils/dataHelper"; import { WebSocketService } from "./webSocketService"; /** @@ -56,11 +56,6 @@ export class VisualizerService { */ private _countsCallback?: (counts: IVerticesCounts) => void; - /** - * The referenced callback. - */ - private _referencedCallback?: (id: string, excluded: string[], count: IVerticesCounts) => void; - /** * Create a new instance of VisualizerService. */ @@ -71,10 +66,10 @@ export class VisualizerService { this._verticesLimit = 5000; this._counts = { total: 0, - solid: 0, - referenced: 0, + accepted: 0, + confirmed: 0, + finalized: 0, transactions: 0, - conflicting: 0, tips: 0 }; this._webSocketService = ServiceFactory.get("web-socket"); @@ -84,42 +79,34 @@ export class VisualizerService { * The callback triggered with vertex updates. * @param vertexCallback The vertex callback. * @param countsCallback The counts callback. - * @param referencedCallback The referenced callback. */ public subscribe( vertexCallback: (vertex: IVertex, operation: VisualizerVertexOperation) => void, - countsCallback: (counts: IVerticesCounts) => void, - referencedCallback: (id: string, excluded: string[], count: IVerticesCounts) => void): void { + countsCallback: (counts: IVerticesCounts) => void): void { this._subscriptions.push( + this._webSocketService.subscribe( + WebSocketTopic.SyncStatus, + false, + data => this.updateSyncStatus(data) + ), this._webSocketService.subscribe( WebSocketTopic.VisualizerVertex, false, data => this.updateVertices(data) ), - this._webSocketService.subscribe( - WebSocketTopic.VisualizerCommitmentInfo, - false, - data => this.updateCommitmentInfo(data) - ), this._webSocketService.subscribe( WebSocketTopic.VisualizerTipInfo, false, data => this.updateTipInfo(data) ), - this._webSocketService.subscribe( - WebSocketTopic.VisualizerConfirmationInfo, - false, - data => this.updateConfirmationInfo(data) - ), - this._webSocketService.subscribe( - WebSocketTopic.VisualizerSolidInfo, + this._webSocketService.subscribe( + WebSocketTopic.VisualizerBlockStateInfo, false, - data => this.updateSolidInfo(data) + data => this.updateBlockStateInfo(data) )); this._vertexCallback = vertexCallback; this._countsCallback = countsCallback; - this._referencedCallback = referencedCallback; } /** @@ -135,13 +122,45 @@ export class VisualizerService { // reset counts this._counts.total = 0; - this._counts.solid = 0; - this._counts.referenced = 0; + this._counts.accepted = 0; + this._counts.confirmed = 0; + this._counts.finalized = 0; this._counts.transactions = 0; - this._counts.conflicting = 0; this._counts.tips = 0; } + /** + * Updates the sync status of the visualizer. + * @param data The sync status data. + */ + private updateSyncStatus(data?: ISyncStatus) { + if (data) { + for (const vertex of Object.values(this._vertices)) { + if (vertex.isFinalized) { + // already finalized + continue; + } + + if (!vertex.isAccepted && !vertex.isConfirmed) { + // not accepted or confirmed + continue; + } + + if (vertex.slot !== undefined && vertex.slot <= data.latestFinalizedSlot) { + vertex.isFinalized = true; + this._counts.finalized++; + + if (this._vertexCallback) { + this._vertexCallback(vertex, "update"); + } + if (this._countsCallback) { + this._countsCallback(this._counts); + } + } + } + } + } + /** * Add a new vertex. * @param vert The vertex to add. @@ -154,55 +173,36 @@ export class VisualizerService { let op: VisualizerVertexOperation = "add"; - if (vertex) { - op = "update"; - // can only go from unsolid to solid - if (!vertex.isSolid && vert.isSolid) { - this._counts.solid++; - } - if (!vertex.isReferenced && vert.isReferenced) { - this._counts.referenced++; - } - if (!vertex.isConflicting && vert.isConflicting) { - this._counts.conflicting++; - } - if (!vertex.isTip && vert.isTip) { - this._counts.tips++; - } - } else { - if (vert.isSolid) { - this._counts.solid++; - } - if (vert.isReferenced) { - this._counts.referenced++; - } - if (vert.isTransaction) { + if (!vertex) { + if (vert.isBasicBlockSignedTransaction) { this._counts.transactions++; } - if (vert.isConflicting) { - this._counts.conflicting++; - } - if (vert.isTip) { - this._counts.tips++; - } - this._verticesOrder.push(shortVertId); this.checkLimit(); vertex = { fullId: vert.id, - shortId: shortVertId + shortId: shortVertId, + slot: DataHelper.computeSlotIndex(vert.id) }; + } else { + op = "update"; } vertex.parents = vert.parents; - vertex.isSolid = vert.isSolid; - vertex.isReferenced = vert.isReferenced; - vertex.isTransaction = vert.isTransaction; - vertex.isConflicting = vert.isConflicting; - vertex.isMilestone = vert.isMilestone; + vertex.blockState = vert.blockState; + vertex.isBasicBlockTaggedData = vert.isBasicBlockTaggedData; + vertex.isBasicBlockSignedTransaction = vert.isBasicBlockSignedTransaction; + vertex.isBasicBlockCandidacyAnnouncement = vert.isBasicBlockCandidacyAnnouncement; + vertex.isValidationBlock = vert.isValidationBlock; + if (!vertex.isTip && vert.isTip) { + this._counts.tips++; + } else if (vertex.isTip && !vert.isTip) { + this._counts.tips--; + } vertex.isTip = vert.isTip; - vertex.isSelected = vert.isSelected; + + this.updateVertexBlockStateInfo(vertex, vert.blockState); this._vertices[shortVertId] = vertex; @@ -251,21 +251,22 @@ export class VisualizerService { } let vertex = this._vertices[vertexId]; if (vertex) { - if (vertex.isSolid) { - this._counts.solid--; + if (vertex.isAccepted) { + this._counts.accepted--; } - if (vertex.isReferenced) { - this._counts.referenced--; + if (vertex.isConfirmed) { + this._counts.confirmed--; } - if (vertex.isTransaction) { - this._counts.transactions--; + if (vertex.isFinalized) { + this._counts.finalized--; } - if (vertex.isConflicting) { - this._counts.conflicting--; + if (vertex.isBasicBlockSignedTransaction) { + this._counts.transactions--; } if (vertex.isTip) { this._counts.tips--; } + delete this._vertices[vertexId]; } else { vertex = { shortId: vertexId }; @@ -286,7 +287,11 @@ export class VisualizerService { if (data) { const vertex = this._vertices[data.id]; if (vertex) { - this._counts.tips += data.isTip ? 1 : (vertex.isTip ? -1 : 0); + if (!vertex.isTip && data.isTip) { + this._counts.tips++; + } else if (vertex.isTip && !data.isTip) { + this._counts.tips--; + } vertex.isTip = data.isTip; if (this._vertexCallback) { this._vertexCallback(vertex, "update"); @@ -298,61 +303,52 @@ export class VisualizerService { } } - /** - * Update the milestone information. - * @param data The milestone info data. - */ - private updateCommitmentInfo(data?: IVisualizerCommitmentInfo) { - if (data) { - const vertex = this._vertices[data.commitmentId]; - if (vertex) { - vertex.isMilestone = true; - if (this._vertexCallback) { - this._vertexCallback(vertex, "update"); - } - } - } - } - - /** - * Update the confirmed information. - * @param data The confirmed info data. - */ - private updateConfirmationInfo(data?: IVisualizerConfirmationInfo) { - if (data) { - for (const id of data.ids) { - const vertex = this._vertices[id]; - if (vertex && !vertex.isReferenced) { - if (this._referencedCallback) { - this._referencedCallback(id, data.excludedIds ?? [], this._counts); + private updateVertexBlockStateInfo(vertex: IVertex, blockState: string): boolean { + if (vertex) { + let updated = false; + switch (blockState) { + case "accepted": + if (!vertex.isAccepted) { + this._counts.accepted++; + updated = true; + vertex.isAccepted = true; } - - if (this._countsCallback) { - this._countsCallback(this._counts); + break; + case "confirmed": + if (!vertex.isConfirmed) { + this._counts.confirmed++; + updated = true; + vertex.isConfirmed = true; } - } + break; + default: + break; } + + return updated; } + + return false; } /** * Update the solid information. * @param data The solid info data. */ - private updateSolidInfo(data?: IVisualizerMetaInfo) { + private updateBlockStateInfo(data?: IVisualizerBlockStateInfo) { if (data) { const vertex = this._vertices[data.id]; - if (vertex && !vertex.isSolid) { - vertex.isSolid = true; - this._counts.solid++; - if (this._vertexCallback) { - this._vertexCallback(vertex, "update"); - } + if (!this.updateVertexBlockStateInfo(vertex, data.blockState)) { + return; + } - if (this._countsCallback) { - this._countsCallback(this._counts); - } + if (this._vertexCallback) { + this._vertexCallback(vertex, "update"); + } + + if (this._countsCallback) { + this._countsCallback(this._counts); } } } diff --git a/src/utils/dataHelper.ts b/src/utils/dataHelper.ts index fb2dc04..9c5b8dc 100644 --- a/src/utils/dataHelper.ts +++ b/src/utils/dataHelper.ts @@ -1,9 +1,35 @@ +import { HexEncodedString } from "../models/hexEncodedTypes"; import { IPeer } from "../models/peers/IPeer"; /** * Class to help with processing of data. */ export class DataHelper { + /** + * Computes a slotIndex from a block, transaction or slotCommitment Id. + * @param id The block, transaction or slotCommitment Id. + * @returns The slotIndex. + */ + public static computeSlotIndex( + id: HexEncodedString + ): number { + const numberString = id.slice(-8); + const chunks = []; + + for ( + let charsLength = numberString.length, i = 0; + i < charsLength; + i += 2 + ) { + chunks.push(numberString.slice(i, i + 2)); + } + const separated = chunks.map(n => Number.parseInt(n, 16)); + const buf = Uint8Array.from(separated).buffer; + const view = new DataView(buf); + + return view.getUint32(0, true); + } + /** * Format the address for the peer. * @param peer The peer.