mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-06-13 16:11:03 +00:00
Add finalized block info (#104)
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
import { Types } from '@dotstats/common';
|
||||||
|
|
||||||
|
export default class Block {
|
||||||
|
public static readonly ZERO = new Block(0 as Types.BlockNumber, '' as Types.BlockHash);
|
||||||
|
|
||||||
|
public readonly number: Types.BlockNumber;
|
||||||
|
public readonly hash: Types.BlockHash;
|
||||||
|
|
||||||
|
constructor(number: Types.BlockNumber, hash: Types.BlockHash) {
|
||||||
|
this.number = number;
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
gt(other: Block): boolean {
|
||||||
|
return this.number > other.number;
|
||||||
|
}
|
||||||
|
|
||||||
|
eq(other: Block): boolean {
|
||||||
|
return this.number === other.number && this.hash === other.hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import * as EventEmitter from 'events';
|
|||||||
import Node from './Node';
|
import Node from './Node';
|
||||||
import Feed from './Feed';
|
import Feed from './Feed';
|
||||||
import FeedSet from './FeedSet';
|
import FeedSet from './FeedSet';
|
||||||
|
import Block from './Block';
|
||||||
import { Maybe, Types, FeedMessage, NumStats } from '@dotstats/common';
|
import { Maybe, Types, FeedMessage, NumStats } from '@dotstats/common';
|
||||||
|
|
||||||
const BLOCK_TIME_HISTORY = 10;
|
const BLOCK_TIME_HISTORY = 10;
|
||||||
@@ -14,6 +15,7 @@ export default class Chain {
|
|||||||
public readonly label: Types.ChainLabel;
|
public readonly label: Types.ChainLabel;
|
||||||
|
|
||||||
public height = 0 as Types.BlockNumber;
|
public height = 0 as Types.BlockNumber;
|
||||||
|
public finalized = Block.ZERO;
|
||||||
public blockTimestamp = 0 as Types.Timestamp;
|
public blockTimestamp = 0 as Types.Timestamp;
|
||||||
|
|
||||||
private blockTimes = new NumStats<Types.Milliseconds>(BLOCK_TIME_HISTORY);
|
private blockTimes = new NumStats<Types.Milliseconds>(BLOCK_TIME_HISTORY);
|
||||||
@@ -43,11 +45,13 @@ export default class Chain {
|
|||||||
});
|
});
|
||||||
|
|
||||||
node.events.on('block', () => this.updateBlock(node));
|
node.events.on('block', () => this.updateBlock(node));
|
||||||
|
node.events.on('finalized', () => this.updateFinalized(node));
|
||||||
node.events.on('stats', () => this.feeds.broadcast(Feed.stats(node)));
|
node.events.on('stats', () => this.feeds.broadcast(Feed.stats(node)));
|
||||||
node.events.on('hardware', () => this.feeds.broadcast(Feed.hardware(node)));
|
node.events.on('hardware', () => this.feeds.broadcast(Feed.hardware(node)));
|
||||||
node.events.on('location', (location) => this.feeds.broadcast(Feed.locatedNode(node, location)));
|
node.events.on('location', (location) => this.feeds.broadcast(Feed.locatedNode(node, location)));
|
||||||
|
|
||||||
this.updateBlock(node);
|
this.updateBlock(node);
|
||||||
|
this.updateFinalized(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addFeed(feed: Feed) {
|
public addFeed(feed: Feed) {
|
||||||
@@ -58,9 +62,11 @@ export default class Chain {
|
|||||||
|
|
||||||
feed.sendMessage(Feed.timeSync());
|
feed.sendMessage(Feed.timeSync());
|
||||||
feed.sendMessage(Feed.bestBlock(this.height, this.blockTimestamp, this.averageBlockTime));
|
feed.sendMessage(Feed.bestBlock(this.height, this.blockTimestamp, this.averageBlockTime));
|
||||||
|
feed.sendMessage(Feed.bestFinalizedBlock(this.finalized));
|
||||||
|
|
||||||
for (const node of this.nodes.values()) {
|
for (const node of this.nodes.values()) {
|
||||||
feed.sendMessage(Feed.addedNode(node));
|
feed.sendMessage(Feed.addedNode(node));
|
||||||
|
feed.sendMessage(Feed.finalized(node));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,9 +87,11 @@ export default class Chain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateBlock(node: Node) {
|
private updateBlock(node: Node) {
|
||||||
if (node.height > this.height) {
|
const height = node.best.number;
|
||||||
|
|
||||||
|
if (height > this.height) {
|
||||||
// New best block
|
// New best block
|
||||||
const { height, blockTimestamp } = node;
|
const { blockTimestamp } = node;
|
||||||
|
|
||||||
if (this.blockTimestamp) {
|
if (this.blockTimestamp) {
|
||||||
this.updateAverageBlockTime(height, blockTimestamp);
|
this.updateAverageBlockTime(height, blockTimestamp);
|
||||||
@@ -100,14 +108,24 @@ export default class Chain {
|
|||||||
this.feeds.broadcast(Feed.bestBlock(this.height, this.blockTimestamp, this.averageBlockTime));
|
this.feeds.broadcast(Feed.bestBlock(this.height, this.blockTimestamp, this.averageBlockTime));
|
||||||
|
|
||||||
console.log(`[${this.label}] New block ${this.height}`);
|
console.log(`[${this.label}] New block ${this.height}`);
|
||||||
} else if (node.height === this.height) {
|
} else if (height === this.height) {
|
||||||
// Caught up to best block
|
// Caught up to best block
|
||||||
node.propagationTime = (node.blockTimestamp - this.blockTimestamp) as Types.PropagationTime;
|
node.propagationTime = (node.blockTimestamp - this.blockTimestamp) as Types.PropagationTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.feeds.broadcast(Feed.imported(node));
|
this.feeds.broadcast(Feed.imported(node));
|
||||||
|
|
||||||
console.log(`[${this.label}] ${node.name} imported ${node.height}, block time: ${node.blockTime / 1000}s, average: ${node.average / 1000}s | latency ${node.latency}`);
|
console.log(`[${this.label}] ${node.name} imported ${height}, block time: ${node.blockTime / 1000}s, average: ${node.average / 1000}s | latency ${node.latency}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateFinalized(node: Node) {
|
||||||
|
if (node.finalized.gt(this.finalized)) {
|
||||||
|
this.finalized = node.finalized;
|
||||||
|
|
||||||
|
this.feeds.broadcast(Feed.bestFinalizedBlock(this.finalized));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.feeds.broadcast(Feed.finalized(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateAverageBlockTime(height: Types.BlockNumber, now: Types.Timestamp) {
|
private updateAverageBlockTime(height: Types.BlockNumber, now: Types.Timestamp) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as WebSocket from 'ws';
|
|||||||
import * as EventEmitter from 'events';
|
import * as EventEmitter from 'events';
|
||||||
import Node from './Node';
|
import Node from './Node';
|
||||||
import Chain from './Chain';
|
import Chain from './Chain';
|
||||||
|
import Block from './Block';
|
||||||
import { VERSION, timestamp, Maybe, FeedMessage, Types, idGenerator } from '@dotstats/common';
|
import { VERSION, timestamp, Maybe, FeedMessage, Types, idGenerator } from '@dotstats/common';
|
||||||
import { Location } from './location';
|
import { Location } from './location';
|
||||||
|
|
||||||
@@ -42,6 +43,13 @@ export default class Feed {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bestFinalizedBlock(block: Block): FeedMessage.Message {
|
||||||
|
return {
|
||||||
|
action: Actions.BestFinalized,
|
||||||
|
payload: [block.number, block.hash]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static addedNode(node: Node): FeedMessage.Message {
|
public static addedNode(node: Node): FeedMessage.Message {
|
||||||
return {
|
return {
|
||||||
action: Actions.AddedNode,
|
action: Actions.AddedNode,
|
||||||
@@ -70,6 +78,13 @@ export default class Feed {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static finalized(node: Node): FeedMessage.Message {
|
||||||
|
return {
|
||||||
|
action: Actions.FinalizedBlock,
|
||||||
|
payload: [node.id, node.finalized.number, node.finalized.hash]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static stats(node: Node): FeedMessage.Message {
|
public static stats(node: Node): FeedMessage.Message {
|
||||||
return {
|
return {
|
||||||
action: Actions.NodeStats,
|
action: Actions.NodeStats,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Maybe, Types, timestamp } from '@dotstats/common';
|
import { Maybe, Types, timestamp } from '@dotstats/common';
|
||||||
|
|
||||||
export class MeanList<T extends number> {
|
export default class MeanList<T extends number> {
|
||||||
private periodCount = 0;
|
private periodCount = 0;
|
||||||
private periodSum = 0;
|
private periodSum = 0;
|
||||||
private meanIndex = 0;
|
private meanIndex = 0;
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import * as EventEmitter from 'events';
|
|||||||
import { noop, timestamp, idGenerator, Maybe, Types, NumStats } from '@dotstats/common';
|
import { noop, timestamp, idGenerator, Maybe, Types, NumStats } from '@dotstats/common';
|
||||||
import { parseMessage, getBestBlock, Message, BestBlock, SystemInterval } from './message';
|
import { parseMessage, getBestBlock, Message, BestBlock, SystemInterval } from './message';
|
||||||
import { locate, Location } from './location';
|
import { locate, Location } from './location';
|
||||||
import { MeanList } from './MeanList';
|
import MeanList from './MeanList';
|
||||||
|
import Block from './Block';
|
||||||
|
|
||||||
const BLOCK_TIME_HISTORY = 10;
|
const BLOCK_TIME_HISTORY = 10;
|
||||||
const MEMORY_RECORDS = 20;
|
const MEMORY_RECORDS = 20;
|
||||||
@@ -32,8 +33,8 @@ export default class Node {
|
|||||||
public location: Maybe<Location> = null;
|
public location: Maybe<Location> = null;
|
||||||
public lastMessage: Types.Timestamp;
|
public lastMessage: Types.Timestamp;
|
||||||
public config: string;
|
public config: string;
|
||||||
public best = '' as Types.BlockHash;
|
public best = Block.ZERO;
|
||||||
public height = 0 as Types.BlockNumber;
|
public finalized = Block.ZERO;
|
||||||
public latency = 0 as Types.Milliseconds;
|
public latency = 0 as Types.Milliseconds;
|
||||||
public blockTime = 0 as Types.Milliseconds;
|
public blockTime = 0 as Types.Milliseconds;
|
||||||
public blockTimestamp = 0 as Types.Timestamp;
|
public blockTimestamp = 0 as Types.Timestamp;
|
||||||
@@ -190,7 +191,7 @@ export default class Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public blockDetails(): Types.BlockDetails {
|
public blockDetails(): Types.BlockDetails {
|
||||||
return [this.height, this.best, this.blockTime, this.blockTimestamp, this.propagationTime];
|
return [this.best.number, this.best.hash, this.blockTime, this.blockTimestamp, this.propagationTime];
|
||||||
}
|
}
|
||||||
|
|
||||||
public nodeLocation(): Maybe<Types.NodeLocation> {
|
public nodeLocation(): Maybe<Types.NodeLocation> {
|
||||||
@@ -234,7 +235,16 @@ export default class Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onSystemInterval(message: SystemInterval) {
|
private onSystemInterval(message: SystemInterval) {
|
||||||
const { peers, txcount, cpu, memory, bandwidth_download: download, bandwidth_upload: upload } = message;
|
const {
|
||||||
|
peers,
|
||||||
|
txcount,
|
||||||
|
cpu,
|
||||||
|
memory,
|
||||||
|
bandwidth_download: download,
|
||||||
|
bandwidth_upload: upload,
|
||||||
|
finalized_height: finalized,
|
||||||
|
finalized_hash: finalizedHash
|
||||||
|
} = message;
|
||||||
|
|
||||||
if (this.peers !== peers || this.txcount !== txcount) {
|
if (this.peers !== peers || this.txcount !== txcount) {
|
||||||
this.peers = peers;
|
this.peers = peers;
|
||||||
@@ -243,6 +253,12 @@ export default class Node {
|
|||||||
this.events.emit('stats');
|
this.events.emit('stats');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (finalized != null && finalizedHash != null && finalized > this.finalized.number) {
|
||||||
|
this.finalized = new Block(finalized, finalizedHash);
|
||||||
|
|
||||||
|
this.events.emit('finalized');
|
||||||
|
}
|
||||||
|
|
||||||
if (cpu != null && memory != null) {
|
if (cpu != null && memory != null) {
|
||||||
const cpuChange = this.cpu.push(cpu);
|
const cpuChange = this.cpu.push(cpu);
|
||||||
const memChange = this.memory.push(memory);
|
const memChange = this.memory.push(memory);
|
||||||
@@ -279,11 +295,10 @@ export default class Node {
|
|||||||
private updateBestBlock(update: BestBlock) {
|
private updateBestBlock(update: BestBlock) {
|
||||||
const { height, ts: time, best } = update;
|
const { height, ts: time, best } = update;
|
||||||
|
|
||||||
if (this.best !== best && this.height <= height) {
|
if (this.best.hash !== best && this.best.number <= height) {
|
||||||
const blockTime = this.getBlockTime(time);
|
const blockTime = this.getBlockTime(time);
|
||||||
|
|
||||||
this.best = best;
|
this.best = new Block(height, best);
|
||||||
this.height = height;
|
|
||||||
this.blockTimestamp = timestamp();
|
this.blockTimestamp = timestamp();
|
||||||
this.lastBlockAt = time;
|
this.lastBlockAt = time;
|
||||||
this.blockTimes.push(blockTime);
|
this.blockTimes.push(blockTime);
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ export interface SystemInterval extends BestBlock {
|
|||||||
status: 'Idle' | string; // TODO: 'Idle' | ...?
|
status: 'Idle' | string; // TODO: 'Idle' | ...?
|
||||||
bandwidth_upload: Maybe<Types.BytesPerSecond>;
|
bandwidth_upload: Maybe<Types.BytesPerSecond>;
|
||||||
bandwidth_download: Maybe<Types.BytesPerSecond>;
|
bandwidth_download: Maybe<Types.BytesPerSecond>;
|
||||||
|
finalized_height: Maybe<Types.BlockNumber>;
|
||||||
|
finalized_hash: Maybe<Types.BlockHash>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeStart extends BestBlock {
|
export interface NodeStart extends BestBlock {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function sortedInsert<T>(item: T, into: Array<T>, compare: Compare<T>): n
|
|||||||
*
|
*
|
||||||
* @return {number} index of the element, `-1` if not found
|
* @return {number} index of the element, `-1` if not found
|
||||||
*/
|
*/
|
||||||
export function sortedIndexOf<T>(item:T, within: Array<T>, compare: Compare<T>): number {
|
export function sortedIndexOf<T>(item: T, within: Array<T>, compare: Compare<T>): number {
|
||||||
if (within.length === 0) {
|
if (within.length === 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-12
@@ -13,6 +13,7 @@ import {
|
|||||||
NodeHardware,
|
NodeHardware,
|
||||||
NodeLocation,
|
NodeLocation,
|
||||||
BlockNumber,
|
BlockNumber,
|
||||||
|
BlockHash,
|
||||||
BlockDetails,
|
BlockDetails,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
Milliseconds,
|
Milliseconds,
|
||||||
@@ -22,18 +23,20 @@ import {
|
|||||||
export const Actions = {
|
export const Actions = {
|
||||||
FeedVersion : 0x00 as 0x00,
|
FeedVersion : 0x00 as 0x00,
|
||||||
BestBlock : 0x01 as 0x01,
|
BestBlock : 0x01 as 0x01,
|
||||||
AddedNode : 0x02 as 0x02,
|
BestFinalized : 0x02 as 0x02,
|
||||||
RemovedNode : 0x03 as 0x03,
|
AddedNode : 0x03 as 0x03,
|
||||||
LocatedNode : 0x04 as 0x04,
|
RemovedNode : 0x04 as 0x04,
|
||||||
ImportedBlock : 0x05 as 0x05,
|
LocatedNode : 0x05 as 0x05,
|
||||||
NodeStats : 0x06 as 0x06,
|
ImportedBlock : 0x06 as 0x06,
|
||||||
NodeHardware : 0x07 as 0x07,
|
FinalizedBlock : 0x07 as 0x07,
|
||||||
TimeSync : 0x08 as 0x08,
|
NodeStats : 0x08 as 0x08,
|
||||||
AddedChain : 0x09 as 0x09,
|
NodeHardware : 0x09 as 0x09,
|
||||||
RemovedChain : 0x0A as 0x0A,
|
TimeSync : 0x0A as 0x0A,
|
||||||
SubscribedTo : 0x0B as 0x0B,
|
AddedChain : 0x0B as 0x0B,
|
||||||
UnsubscribedFrom : 0x0C as 0x0C,
|
RemovedChain : 0x0C as 0x0C,
|
||||||
Pong : 0x0D as 0x0D,
|
SubscribedTo : 0x0D as 0x0D,
|
||||||
|
UnsubscribedFrom : 0x0E as 0x0E,
|
||||||
|
Pong : 0x0F as 0x0F,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Action = typeof Actions[keyof typeof Actions];
|
export type Action = typeof Actions[keyof typeof Actions];
|
||||||
@@ -54,6 +57,11 @@ export namespace Variants {
|
|||||||
payload: [BlockNumber, Timestamp, Maybe<Milliseconds>];
|
payload: [BlockNumber, Timestamp, Maybe<Milliseconds>];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BestFinalizedBlockMessage extends MessageBase {
|
||||||
|
action: typeof Actions.BestFinalized;
|
||||||
|
payload: [BlockNumber, BlockHash];
|
||||||
|
}
|
||||||
|
|
||||||
export interface AddedNodeMessage extends MessageBase {
|
export interface AddedNodeMessage extends MessageBase {
|
||||||
action: typeof Actions.AddedNode;
|
action: typeof Actions.AddedNode;
|
||||||
payload: [NodeId, NodeDetails, NodeStats, NodeHardware, BlockDetails, Maybe<NodeLocation>];
|
payload: [NodeId, NodeDetails, NodeStats, NodeHardware, BlockDetails, Maybe<NodeLocation>];
|
||||||
@@ -74,6 +82,11 @@ export namespace Variants {
|
|||||||
payload: [NodeId, BlockDetails];
|
payload: [NodeId, BlockDetails];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FinalizedBlockMessage extends MessageBase {
|
||||||
|
action: typeof Actions.FinalizedBlock;
|
||||||
|
payload: [NodeId, BlockNumber, BlockHash];
|
||||||
|
}
|
||||||
|
|
||||||
export interface NodeStatsMessage extends MessageBase {
|
export interface NodeStatsMessage extends MessageBase {
|
||||||
action: typeof Actions.NodeStats;
|
action: typeof Actions.NodeStats;
|
||||||
payload: [NodeId, NodeStats];
|
payload: [NodeId, NodeStats];
|
||||||
@@ -118,10 +131,12 @@ export namespace Variants {
|
|||||||
export type Message =
|
export type Message =
|
||||||
| Variants.FeedVersionMessage
|
| Variants.FeedVersionMessage
|
||||||
| Variants.BestBlockMessage
|
| Variants.BestBlockMessage
|
||||||
|
| Variants.BestFinalizedBlockMessage
|
||||||
| Variants.AddedNodeMessage
|
| Variants.AddedNodeMessage
|
||||||
| Variants.RemovedNodeMessage
|
| Variants.RemovedNodeMessage
|
||||||
| Variants.LocatedNodeMessage
|
| Variants.LocatedNodeMessage
|
||||||
| Variants.ImportedBlockMessage
|
| Variants.ImportedBlockMessage
|
||||||
|
| Variants.FinalizedBlockMessage
|
||||||
| Variants.NodeStatsMessage
|
| Variants.NodeStatsMessage
|
||||||
| Variants.NodeHardwareMessage
|
| Variants.NodeHardwareMessage
|
||||||
| Variants.TimeSyncMessage
|
| Variants.TimeSyncMessage
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ import * as FeedMessage from './feed';
|
|||||||
export { Types, FeedMessage };
|
export { Types, FeedMessage };
|
||||||
|
|
||||||
// Increment this if breaking changes were made to types in `feed.ts`
|
// Increment this if breaking changes were made to types in `feed.ts`
|
||||||
export const VERSION: Types.FeedVersion = 20 as Types.FeedVersion;
|
export const VERSION: Types.FeedVersion = 21 as Types.FeedVersion;
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ export default class App extends React.Component<{}, State> {
|
|||||||
blocknumber: true,
|
blocknumber: true,
|
||||||
blockhash: true,
|
blockhash: true,
|
||||||
blocktime: true,
|
blocktime: true,
|
||||||
|
finalized: false,
|
||||||
|
finalizedhash: false,
|
||||||
blockpropagation: true,
|
blockpropagation: true,
|
||||||
blocklasttime: false
|
blocklasttime: false
|
||||||
},
|
},
|
||||||
@@ -48,6 +50,7 @@ export default class App extends React.Component<{}, State> {
|
|||||||
this.state = {
|
this.state = {
|
||||||
status: 'offline',
|
status: 'offline',
|
||||||
best: 0 as Types.BlockNumber,
|
best: 0 as Types.BlockNumber,
|
||||||
|
finalized: 0 as Types.BlockNumber,
|
||||||
blockTimestamp: 0 as Types.Timestamp,
|
blockTimestamp: 0 as Types.Timestamp,
|
||||||
blockAverage: null,
|
blockAverage: null,
|
||||||
timeDiff: 0 as Types.Milliseconds,
|
timeDiff: 0 as Types.Milliseconds,
|
||||||
|
|||||||
@@ -110,6 +110,14 @@ export class Connection {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Actions.BestFinalized: {
|
||||||
|
const [finalized /*, hash */] = message.payload;
|
||||||
|
|
||||||
|
this.state = this.update({ finalized });
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case Actions.AddedNode: {
|
case Actions.AddedNode: {
|
||||||
const [id, nodeDetails, nodeStats, nodeHardware, blockDetails, location] = message.payload;
|
const [id, nodeDetails, nodeStats, nodeHardware, blockDetails, location] = message.payload;
|
||||||
const pinned = this.pins.has(nodeDetails[0]);
|
const pinned = this.pins.has(nodeDetails[0]);
|
||||||
@@ -144,6 +152,14 @@ export class Connection {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Actions.FinalizedBlock: {
|
||||||
|
const [id, height, hash] = message.payload;
|
||||||
|
|
||||||
|
nodes.mut(id, (node) => node.updateFinalized(height, hash));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case Actions.NodeStats: {
|
case Actions.NodeStats: {
|
||||||
const [id, nodeStats] = message.payload;
|
const [id, nodeStats] = message.payload;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Tile, Ago, List, Map, Settings } from '../';
|
|||||||
import { PersistentObject, PersistentSet } from '../../persist';
|
import { PersistentObject, PersistentSet } from '../../persist';
|
||||||
|
|
||||||
import blockIcon from '../../icons/package.svg';
|
import blockIcon from '../../icons/package.svg';
|
||||||
|
import finalizedIcon from '../../icons/milestone.svg';
|
||||||
import blockTimeIcon from '../../icons/history.svg';
|
import blockTimeIcon from '../../icons/history.svg';
|
||||||
import lastTimeIcon from '../../icons/watch.svg';
|
import lastTimeIcon from '../../icons/watch.svg';
|
||||||
import listIcon from '../../icons/list-alt-regular.svg';
|
import listIcon from '../../icons/list-alt-regular.svg';
|
||||||
@@ -51,13 +52,14 @@ export class Chain extends React.Component<Chain.Props, Chain.State> {
|
|||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { appState } = this.props;
|
const { appState } = this.props;
|
||||||
const { best, blockTimestamp, blockAverage } = appState;
|
const { best, finalized, blockTimestamp, blockAverage } = appState;
|
||||||
const { display: currentTab } = this.state;
|
const { display: currentTab } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Chain">
|
<div className="Chain">
|
||||||
<div className="Chain-header">
|
<div className="Chain-header">
|
||||||
<Tile icon={blockIcon} title="Best Block">#{formatNumber(best)}</Tile>
|
<Tile icon={blockIcon} title="Best Block">#{formatNumber(best)}</Tile>
|
||||||
|
<Tile icon={finalizedIcon} title="Finalized Block">#{formatNumber(finalized)}</Tile>
|
||||||
<Tile icon={blockTimeIcon} title="Average Time">{ blockAverage == null ? '-' : secondsWithPrecision(blockAverage / 1000) }</Tile>
|
<Tile icon={blockTimeIcon} title="Average Time">{ blockAverage == null ? '-' : secondsWithPrecision(blockAverage / 1000) }</Tile>
|
||||||
<Tile icon={lastTimeIcon} title="Last Block"><Ago when={blockTimestamp} /></Tile>
|
<Tile icon={lastTimeIcon} title="Last Block"><Ago when={blockTimestamp} /></Tile>
|
||||||
<div className="Chain-tabs">
|
<div className="Chain-tabs">
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import nodeTypeIcon from '../../icons/terminal.svg';
|
|||||||
import peersIcon from '../../icons/broadcast.svg';
|
import peersIcon from '../../icons/broadcast.svg';
|
||||||
import transactionsIcon from '../../icons/inbox.svg';
|
import transactionsIcon from '../../icons/inbox.svg';
|
||||||
import blockIcon from '../../icons/package.svg';
|
import blockIcon from '../../icons/package.svg';
|
||||||
|
import finalizedIcon from '../../icons/milestone.svg';
|
||||||
import blockHashIcon from '../../icons/file-binary.svg';
|
import blockHashIcon from '../../icons/file-binary.svg';
|
||||||
import blockTimeIcon from '../../icons/history.svg';
|
import blockTimeIcon from '../../icons/history.svg';
|
||||||
import propagationTimeIcon from '../../icons/dashboard.svg';
|
import propagationTimeIcon from '../../icons/dashboard.svg';
|
||||||
@@ -233,6 +234,20 @@ export class Row extends React.Component<Row.Props, Row.State> {
|
|||||||
setting: 'blockhash',
|
setting: 'blockhash',
|
||||||
render: ({ hash }) => <Truncate position="right" text={hash} copy={true} />
|
render: ({ hash }) => <Truncate position="right" text={hash} copy={true} />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Finalized Block',
|
||||||
|
icon: finalizedIcon,
|
||||||
|
width: 88,
|
||||||
|
setting: 'finalized',
|
||||||
|
render: ({ finalized }) => `#${formatNumber(finalized)}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Finalized Block Hash',
|
||||||
|
icon: blockHashIcon,
|
||||||
|
width: 154,
|
||||||
|
setting: 'finalizedhash',
|
||||||
|
render: ({ finalizedHash }) => <Truncate position="right" text={finalizedHash} copy={true} />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Block Time',
|
label: 'Block Time',
|
||||||
icon: blockTimeIcon,
|
icon: blockTimeIcon,
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ export class Truncate extends React.Component<Truncate.Props, {}> {
|
|||||||
public render() {
|
public render() {
|
||||||
const { text, position, copy } = this.props;
|
const { text, position, copy } = this.props;
|
||||||
|
|
||||||
|
if (!text) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip text={text} position={position} copy={copy} className="Row-Tooltip">
|
<Tooltip text={text} position={position} copy={copy} className="Row-Tooltip">
|
||||||
<div className="Row-truncate">{text}</div>
|
<div className="Row-truncate">{text}</div>
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ export class Node {
|
|||||||
public blockTimestamp: Types.Timestamp;
|
public blockTimestamp: Types.Timestamp;
|
||||||
public propagationTime: Maybe<Types.PropagationTime>;
|
public propagationTime: Maybe<Types.PropagationTime>;
|
||||||
|
|
||||||
|
public finalized = 0 as Types.BlockNumber;
|
||||||
|
public finalizedHash = '' as Types.BlockHash;
|
||||||
|
|
||||||
public lat: Maybe<Types.Latitude>;
|
public lat: Maybe<Types.Latitude>;
|
||||||
public lon: Maybe<Types.Longitude>;
|
public lon: Maybe<Types.Longitude>;
|
||||||
public city: Maybe<Types.City>;
|
public city: Maybe<Types.City>;
|
||||||
@@ -106,6 +109,11 @@ export class Node {
|
|||||||
this.trigger();
|
this.trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updateFinalized(height: Types.BlockNumber, hash: Types.BlockHash) {
|
||||||
|
this.finalized = height;
|
||||||
|
this.finalizedHash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
public updateLocation(location: Types.NodeLocation) {
|
public updateLocation(location: Types.NodeLocation) {
|
||||||
const [lat, lon, city] = location;
|
const [lat, lon, city] = location;
|
||||||
|
|
||||||
@@ -158,6 +166,8 @@ export namespace State {
|
|||||||
download: boolean;
|
download: boolean;
|
||||||
blocknumber: boolean;
|
blocknumber: boolean;
|
||||||
blockhash: boolean;
|
blockhash: boolean;
|
||||||
|
finalized: boolean;
|
||||||
|
finalizedhash: boolean;
|
||||||
blocktime: boolean;
|
blocktime: boolean;
|
||||||
blockpropagation: boolean;
|
blockpropagation: boolean;
|
||||||
blocklasttime: boolean;
|
blocklasttime: boolean;
|
||||||
@@ -167,6 +177,7 @@ export namespace State {
|
|||||||
export interface State {
|
export interface State {
|
||||||
status: 'online' | 'offline' | 'upgrade-requested';
|
status: 'online' | 'offline' | 'upgrade-requested';
|
||||||
best: Types.BlockNumber;
|
best: Types.BlockNumber;
|
||||||
|
finalized: Types.BlockNumber;
|
||||||
blockTimestamp: Types.Timestamp;
|
blockTimestamp: Types.Timestamp;
|
||||||
blockAverage: Maybe<Types.Milliseconds>;
|
blockAverage: Maybe<Types.Milliseconds>;
|
||||||
timeDiff: Types.Milliseconds;
|
timeDiff: Types.Milliseconds;
|
||||||
|
|||||||
Reference in New Issue
Block a user