Skip to content

Commit 8db03a3

Browse files
fix: node stability improvements & design fixes (#663)
* optimized node status updating for start/stop * improved status handling, added stopping status * added starting status to signify starting but before pulling podman images * if user stops all services, stop node package too * added lowPeerCount, synchronized * added noConnection logic * updated sidebar item to handle updated node status * fixed bug where starting/stopping second node with same name, affected the first node * fix status for sidebar * fixed node version retrieval * continuously retrieve version, show empty string when stopped * added CL check * removed query node version, added CL check for sync status * added updateNodePackage to mark nodePackage after successful initial sync * refactored state for node package * made similar refactoring to node screen * refactored sidebarNodeItemWrapper * added context, and hooks to centralize queries in one place * added getSyncData * const consistency * changed consts, fixed client card status * fixed client card styling * added url param for remote execution * block scoped, more strict sync * added removing node state * added support for slots from consensus * node status fix * fixed synchronized state for client card * fixed sync progress, prepare to move complex sync logic into getSyncData * fixed synced client card, apply same complex isSyncing logic check for individual nodes * improved sync logic * improved sync logic * added percent to progress bar on clientcard * support non-ethereum clients * fixed lastRunningTimestampMs * math floor for % * stronger nodescreen latest synced block logic * prioritize synchornized over running for node screen * added same syncData logic from nodePackage to sidebar, better offline handling * check if isSyncing for catchingUp * banner shows up now after synchronization * fixed service name * flexible support for node packages with 2 services * dynamic port support for OP nodes * added stopping state to node package * moved controller version into devmode * added starting state to header * added l2executionEndpoint, mouse states * show number of notification items that are unread * removed cursor, added text highlighting * changed beta label style, padding * single clientcard support * fixed link colors * decreased shadow opacity, dont display tabs when just 1 * changed to show other node types, removed other * changed to black40/white40 * added useTheme to use correct artworks * show minutes and seconds for podman install * added notification deeplink to preferences * fixed checkbox to spec * start work on update modal * completed update modal * updated strings * added skip button to update callout * backdrop click on modal closes the modal * refactor across components * add support for labels + progressbar * dont show update for nodepackage screen * fixed syncing status for optimism * add httpPort to l2RpcUrl * optimism_syncStatus support * added peer and latestBlock support for op-node * add specId support for executeTranslations * enable card height for OP * removed lastblock related logic from node package, only needed on nodescreen * simplified polling to rely on running status(?) * fixed test to support new status * added special case for reth to check latestBlock when syncing * besu should check latestBlock too * wait longer after node start for containers to download --------- Co-authored-by: Johns Gresham <[email protected]>
1 parent ebdb380 commit 8db03a3

File tree

89 files changed

+2116
-1161
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+2116
-1161
lines changed

assets/locales/en/genericComponents.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"AboutSecondsRemaining": "About {{seconds}} seconds remaining",
3+
"AboutMinutesAndSecondsRemaining": "{{minutes}} minutes and {{remainingSeconds}} seconds remaining",
34
"CheckForUpdates": "Check for updates...",
45
"Confirm": "Confirm",
56
"FinishingUp": "Finishing up...",
@@ -48,7 +49,12 @@
4849
"Syncing": "Syncing",
4950
"CatchingUp": "Catching up...",
5051
"InProgress": "In progress...",
52+
"Starting": "Starting",
53+
"Starting...": "Starting...",
5154
"Stopped": "Stopped",
55+
"Stopping": "Stopping",
56+
"Stopping...": "Stopping...",
57+
"Removing": "Removing",
5258
"CurrentlyOffline": "Currently offline",
5359
"PleaseReconnect": "Please reconnect to the internet",
5460
"PodmanIsNotInstalled": "Podman is not installed",

assets/locales/en/translation.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
"Peers": "peers",
1919
"created": "created",
2020
"initializing": "initializing",
21-
"checkingForUpdates": "checking for updates",
2221
"download": "download",
2322
"downloading": "downloading",
2423
"downloaded": "downloaded",
@@ -145,6 +144,8 @@
145144
"loadingDotDotDot": "loading...",
146145
"Preferences": "Preferences",
147146
"ShowAdvancedOptions": "Show advanced options",
147+
"ShowOtherNodeTypes": "Show other node types",
148+
"HideOtherNodeTypes": "Hide other node types",
148149
"SaveChanges": "Save changes",
149150
"Continue": "Continue",
150151
"Back": "Back",
@@ -174,5 +175,10 @@
174175
"CurrentPodman": "Your current Podman installation ({{currentPodmanVersion}}) is incompatible with NiceNode and requires version {{requiredPodmanVersion}} or higher for it to run.",
175176
"PodmanIsRequiredComponent": "Podman is a required component for NiceNode to run the many client options. Podman facilitates the running of containers within a virtualised Linux environment and will operate in the background.",
176177
"DownloadAndUpdate": "Download and update",
177-
"PodmanUpdate": "Podman update"
178+
"PodmanUpdate": "Podman update",
179+
"CheckingForUpdates": "Checking for updates...",
180+
"RunningLatestVersion": "You are running the latest version",
181+
"SuccessfullyUpdated": "Successfully updated",
182+
"Done": "Done",
183+
"Close": "Close"
178184
}

src/common/NodeSpecs/arbitrum/arbitrum-v1.0.0.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"specId": "arbitrum",
33
"version": "1.0.0",
44
"displayName": "Arbitrum One",
5-
"displayTagline": "Non-Validating Node",
5+
"displayTagline": "Ethereum L2 - Arbitrum",
66
"execution": {
77
"executionTypes": ["nodePackage"],
88
"defaultExecutionType": "nodePackage",

src/common/NodeSpecs/base/base-v1.0.0.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"specId": "base",
3-
"version": "1.0.0",
4-
"displayName": "Base",
3+
"version": "1.0.1",
4+
"displayTagline": "Ethereum L2 - Base",
55
"execution": {
66
"executionTypes": ["nodePackage"],
77
"services": [

src/common/NodeSpecs/op-node/op-node-v1.0.0.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"defaultConfig": {
1010
"l1": "",
1111
"l1Beacon": "",
12-
"l2": "http://host.containers.internal:8553",
12+
"executionEndpoint": "http://host.containers.internal",
1313
"l1TrustRpc": "Enabled",
1414
"httpPort": "8549",
1515
"p2pPorts": "9222",
@@ -110,13 +110,13 @@
110110
"defaultValue": "",
111111
"infoDescription": "Provider with historical blob API enabled or a local node http://host.containers.internal:5052"
112112
},
113-
"l2": {
113+
"executionEndpoint": {
114114
"displayName": "Optimism RPC Endpoint (local node or provider)",
115115
"cliConfigPrefix": "--l2 ",
116116
"uiControl": {
117117
"type": "text"
118118
},
119-
"defaultValue": "http://host.containers.internal:8553",
119+
"defaultValue": "http://host.containers.internal",
120120
"addNodeFlow": "advanced",
121121
"infoDescription": "engine and eth namespace required",
122122
"documentation": "https://github.com/jgresham/superhack/blob/main/README.md#op-node-settings"

src/common/node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export enum NodeStatus {
2222
running = 'running',
2323
stopping = 'stopping',
2424
stopped = 'stopped',
25+
removing = 'removing',
2526
errorRunning = 'error running',
2627
errorStarting = 'error starting',
2728
errorStopping = 'error stopping',

src/main/ipc.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ import { checkPorts } from './ports';
5757
import { getBenchmarks } from './state/benchmark';
5858
import { getAppClientId } from './state/eventReporting';
5959
import { getNodeLibrary, getNodePackageLibrary } from './state/nodeLibrary';
60-
import { getUserNodePackages } from './state/nodePackages';
60+
import {
61+
getUserNodePackages,
62+
updateNodePackageProperties,
63+
} from './state/nodePackages';
6164
import { getNodes, getUserNodes, updateNodeProperties } from './state/nodes';
6265
import {
6366
addNotification,
@@ -145,6 +148,12 @@ export const initialize = () => {
145148
ipcMain.handle('stopNodePackage', (_event, nodeId: NodeId) => {
146149
return stopNodePackage(nodeId, NodeStoppedBy.user);
147150
});
151+
ipcMain.handle(
152+
'updateNodePackage',
153+
(_event, nodeId: NodeId, propertiesToUpdate: any) => {
154+
return updateNodePackageProperties(nodeId, propertiesToUpdate);
155+
},
156+
);
148157
ipcMain.handle(
149158
'addNode',
150159
(_event, nodeSpec: NodeSpecification, storageLocation?: string) => {

src/main/nodeManager.ts

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,8 @@ export const getNodeStartCommand = (nodeId: NodeId): string => {
113113

114114
export const startNode = async (nodeId: NodeId) => {
115115
const node = nodeStore.getNode(nodeId);
116-
if (!node) {
117-
throw new Error(`Unable to start node ${nodeId}. Node not found.`);
118-
}
116+
if (!node) throw new Error(`Unable to start node ${nodeId}. Node not found.`);
117+
119118
logger.info(`Starting node ${JSON.stringify(node)}`);
120119
node.status = NodeStatus.starting;
121120
node.lastStartedTimestampMs = Date.now();
@@ -124,30 +123,24 @@ export const startNode = async (nodeId: NodeId) => {
124123

125124
try {
126125
if (isDockerNode(node)) {
127-
const dockerNode = node;
128-
// startPodmanNode(dockerNode);
129-
const containerIds = await startPodmanNode(dockerNode);
130-
dockerNode.runtime.processIds = containerIds;
131-
dockerNode.status = NodeStatus.running;
126+
const containerIds = await startPodmanNode(node);
127+
node.runtime.processIds = containerIds;
128+
node.status = NodeStatus.running;
132129
setLastRunningTime(nodeId, 'nodeService');
133-
if (getSetPortHasChanged(dockerNode)) {
134-
checkNodePortsAndNotify(dockerNode);
135-
}
136-
nodeStore.updateNode(dockerNode);
130+
if (getSetPortHasChanged(node)) checkNodePortsAndNotify(node);
137131
}
138132
} catch (err) {
139133
logger.error(err);
140134
node.status = NodeStatus.errorStarting;
135+
} finally {
141136
nodeStore.updateNode(node);
142-
throw err;
143137
}
144138
};
145139

146140
export const stopNode = async (nodeId: NodeId, stoppedBy: NodeStoppedBy) => {
147141
const node = nodeStore.getNode(nodeId);
148-
if (!node) {
149-
throw new Error(`Unable to stop node ${nodeId}. Node not found.`);
150-
}
142+
if (!node) throw new Error(`Unable to stop node ${nodeId}. Node not found.`);
143+
151144
logger.info(`Stopping node ${JSON.stringify(node)}`);
152145
node.status = NodeStatus.stopping;
153146
node.lastStoppedTimestampMs = Date.now();
@@ -156,14 +149,12 @@ export const stopNode = async (nodeId: NodeId, stoppedBy: NodeStoppedBy) => {
156149

157150
try {
158151
if (isDockerNode(node)) {
159-
const containerIds = await stopPodmanNode(node);
160-
logger.info(`${containerIds} stopped`);
152+
await stopPodmanNode(node);
161153
node.status = NodeStatus.stopped;
162-
nodeStore.updateNode(node);
163154
}
155+
} catch (err) {
156+
logger.error(err);
164157
} finally {
165-
// don't catch the error, but mark node as stopped
166-
// todoo: fix this, but for testing it is useful
167158
node.status = NodeStatus.stopped;
168159
nodeStore.updateNode(node);
169160
}
@@ -204,6 +195,8 @@ export const removeNode = async (
204195
);
205196
}
206197
const node = nodeStore.getNode(nodeId);
198+
node.status = NodeStatus.removing;
199+
nodeStore.updateNode(node);
207200

208201
// if docker, remove container
209202
if (isDockerNode(node)) {

src/main/nodePackageManager.ts

Lines changed: 36 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -135,62 +135,45 @@ export const addNodePackage = async (
135135

136136
export const startNodePackage = async (nodeId: NodeId) => {
137137
const node = nodePackageStore.getNodePackage(nodeId);
138-
if (!node) {
138+
if (!node)
139139
throw new Error(`Unable to start node package ${nodeId}. Not found.`);
140-
}
141-
logger.info(`Starting node ${JSON.stringify(node)}`);
142-
let nodePackageStatus = NodeStatus.starting;
143-
node.status = nodePackageStatus;
140+
141+
logger.info(`Starting node package ${JSON.stringify(node)}`);
142+
node.status = NodeStatus.starting;
144143
node.lastStartedTimestampMs = Date.now();
145144
node.stoppedBy = undefined;
146145
nodePackageStore.updateNodePackage(node);
147-
let allServicesStarted = true;
148-
149-
const isEthereumPackage = node.spec.specId === 'ethereum';
150146

151147
const startService = async (service: any) => {
152148
try {
153149
await startNode(service.node.id);
154150
} catch (e) {
155151
logger.error(`Unable to start node service: ${JSON.stringify(service)}`);
156-
nodePackageStatus = NodeStatus.errorStarting;
157-
allServicesStarted = false;
158-
// try to start all services, or stop other services?
159-
// todo: set as partially started?
160-
// throw e;
152+
node.status = NodeStatus.errorStarting;
153+
nodePackageStore.updateNodePackage(node);
154+
return Promise.reject(e);
161155
}
162156
};
163157

164-
// Make sure Ethereum clients run sequentially so that the correct
165-
// engine and other ports can be assigned correctly
166-
if (isEthereumPackage) {
158+
if (node.services.length === 2) {
159+
//TODO: support clients that don't have the following serviceIds
167160
const executionClient = node.services.find(
168-
(service) => service.serviceId === 'executionClient',
161+
(s) => s.serviceId === 'executionClient',
169162
);
170163
const consensusClient = node.services.find(
171-
(service) => service.serviceId === 'consensusClient',
164+
(s) => s.serviceId === 'consensusClient',
172165
);
173166

174-
if (executionClient) {
175-
await startService(executionClient);
176-
}
177-
178-
if (consensusClient) {
179-
await startService(consensusClient);
180-
}
167+
if (executionClient) await startService(executionClient);
168+
if (consensusClient) await startService(consensusClient);
181169
} else {
182-
const startPromises = node.services.map(startService);
183-
await Promise.all(startPromises);
170+
await Promise.all(node.services.map(startService));
184171
}
185172

186-
// If all node services start without error, the package is considered running
187-
if (allServicesStarted) {
188-
nodePackageStatus = NodeStatus.running;
173+
if (node.status !== NodeStatus.errorStarting) {
174+
node.status = NodeStatus.running;
189175
setLastRunningTime(nodeId, 'node');
190176
}
191-
192-
// set node status
193-
node.status = nodePackageStatus;
194177
nodePackageStore.updateNodePackage(node);
195178
};
196179

@@ -199,31 +182,30 @@ export const stopNodePackage = async (
199182
stoppedBy: NodeStoppedBy,
200183
) => {
201184
const node = nodePackageStore.getNodePackage(nodeId);
202-
if (!node) {
185+
if (!node)
203186
throw new Error(`Unable to stop node package ${nodeId}. Not found.`);
204-
}
205-
logger.info(`Stopping node ${JSON.stringify(node)}`);
206-
let nodePackageStatus = NodeStatus.stopping;
207-
node.status = nodePackageStatus;
187+
188+
logger.info(`Stopping node package ${JSON.stringify(node)}`);
189+
node.status = NodeStatus.stopping;
208190
node.lastStoppedTimestampMs = Date.now();
209191
node.stoppedBy = stoppedBy;
210192
nodePackageStore.updateNodePackage(node);
211193

212-
nodePackageStatus = NodeStatus.stopped;
213-
for (let i = 0; i < node.services.length; i++) {
214-
const service = node.services[i];
215-
try {
216-
await stopNode(service.node.id, stoppedBy);
217-
} catch (e) {
218-
logger.error(`Unable to stop node service: ${JSON.stringify(service)}`);
219-
nodePackageStatus = NodeStatus.errorStopping;
220-
// try to start all services, or stop other services?
221-
// what to do here?
222-
// throw e;
223-
}
194+
await Promise.all(
195+
node.services.map(async (service) => {
196+
try {
197+
await stopNode(service.node.id, stoppedBy);
198+
} catch (e) {
199+
logger.error(`Unable to stop node service: ${JSON.stringify(service)}`);
200+
node.status = NodeStatus.errorStopping;
201+
nodePackageStore.updateNodePackage(node);
202+
}
203+
}),
204+
);
205+
206+
if (node.status !== NodeStatus.errorStopping) {
207+
node.status = NodeStatus.stopped;
224208
}
225-
// set node status
226-
node.status = nodePackageStatus;
227209
nodePackageStore.updateNodePackage(node);
228210
};
229211

@@ -252,6 +234,8 @@ export const removeNodePackage = async (
252234
);
253235
}
254236
const node = nodePackageStore.getNodePackage(nodeId);
237+
node.status = NodeStatus.removing;
238+
nodePackageStore.updateNodePackage(node);
255239
for (let i = 0; i < node.services.length; i++) {
256240
const service = node.services[i];
257241
try {

0 commit comments

Comments
 (0)