diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 57f17c9a..ae3fc70d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -19,7 +19,7 @@ jobs: runs-on: self-hosted-hoprnet-small strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2783baa8..d7466ea2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -32,7 +32,7 @@ jobs: - name: Setup Node.js uses: hoprnet/hopr-workflows/actions/setup-node-js@master with: - node-version: ${{ vars.NODE_VERSION }} + node-version: 22.x - name: Setup GCP id: gcp diff --git a/.npmrc b/.npmrc index 98599bcb..b5b60949 100644 --- a/.npmrc +++ b/.npmrc @@ -1,4 +1,4 @@ -@hoprnet:registry=https://europe-west3-npm.pkg.dev/hoprassociation/npm/ +//@hoprnet:registry=https://europe-west3-npm.pkg.dev/hoprassociation/npm/ registry=https://registry.npmjs.org/ //registry.npmjs.org/:always-auth=false \ No newline at end of file diff --git a/.yarnrc b/.yarnrc index 38418f4e..7dd8b421 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1 +1 @@ -"@hoprnet:registry" "https://europe-west3-npm.pkg.dev/hoprassociation/npm/" +//"@hoprnet:registry" "https://europe-west3-npm.pkg.dev/hoprassociation/npm/" diff --git a/Dockerfile b/Dockerfile index a155b70a..df6048d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=linux/amd64 node:20-bullseye-slim AS deps +FROM --platform=linux/amd64 node:22-bullseye-slim AS deps SHELL ["/bin/bash", "-lc"] @@ -17,7 +17,7 @@ COPY yarn.lock . RUN jq .version package.json -r > /app/version.txt RUN yarn --frozen-lockfile --network-timeout 1000000 -FROM --platform=linux/amd64 node:20-bullseye-slim AS build +FROM --platform=linux/amd64 node:22-bullseye-slim AS build WORKDIR /app diff --git a/package.json b/package.json index dc7da7ac..c2800bfd 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", "@fontsource/roboto": "^5.0.0", - "@hoprnet/hopr-sdk": "3.0.0-pr.148-20250709144009", + "@hoprnet/hopr-sdk": "3.0.1", "@metamask/jazzicon": "^2.0.0", "@mui/icons-material": "^5.11.16", "@mui/material": "^5.13.0", diff --git a/src/components/ConnectNode/modal.tsx b/src/components/ConnectNode/modal.tsx index ffed2dd3..d7f7c4b9 100644 --- a/src/components/ConnectNode/modal.tsx +++ b/src/components/ConnectNode/modal.tsx @@ -291,6 +291,12 @@ function ConnectNodeModal(props: ConnectNodeModalProps) { apiEndpoint: formattedApiEndpoint, }), ); + dispatch( + nodeActionsAsync.getChannelsCorruptedThunk({ + apiEndpoint, + apiToken: apiToken ? apiToken : '', + }), + ); dispatch( nodeActionsAsync.getConfigurationThunk({ apiToken, diff --git a/src/pages/node/configuration.tsx b/src/pages/node/configuration.tsx index 1abe8334..870df489 100644 --- a/src/pages/node/configuration.tsx +++ b/src/pages/node/configuration.tsx @@ -2,6 +2,7 @@ import { useEffect, useState, KeyboardEvent } from 'react'; import { useAppDispatch, useAppSelector } from '../../store'; import { formatEther } from 'viem'; import { rounder, rounder2 } from '../../utils/functions'; +import { exportToFile } from '../../utils/helpers'; import yaml from 'js-yaml'; // HOPR Components @@ -10,11 +11,13 @@ import { TableExtended } from '../../future-hopr-lib-components/Table/columed-da import Section from '../../future-hopr-lib-components/Section'; import Button from '../../future-hopr-lib-components/Button'; import CodeCopyBox from '../../components/Code/CodeCopyBox'; +import IconButton from '../../future-hopr-lib-components/Button/IconButton'; // Mui import { Paper, Switch } from '@mui/material'; import styled from '@emotion/styled'; import { appActions } from '../../store/slices/app'; +import GetAppIcon from '@mui/icons-material/GetApp'; const NotificationsContainer = styled.div` display: flex; @@ -52,6 +55,7 @@ function SettingsPage() { const strategy = useAppSelector((store) => store.node.configuration.data?.hopr?.strategy); const configuration = useAppSelector((store) => store.node.configuration.data); const ticketPrice = useAppSelector((store) => store.node.ticketPrice.data); + const myNodeAddress = useAppSelector((store) => store.node.addresses.data.native); const [strategiesString, set_strategiesString] = useState(null); const [configurationString, set_configurationString] = useState(null); const [localNotificationSettings, set_localNotificationSettings] = useState(); @@ -103,19 +107,40 @@ function SettingsPage() { }, ]; - // console.log('configs', configs); + let strategiesString = yaml.dump(strategyTMP); - // TODO: update this block to the new structure - // for (const config of configs) { - // if (config.value) { - // const tickets = calculateTickets(config.value, ticketPrice); - // result = updateStrategyString(result, config.path[1], config.value, tickets); - // } - // } + // * Add ! in front of the strategy name to make yaml copy-paste friendly + const strategiesSet = []; + if (strategyTMP.hopr.strategy.strategies) { + for (let i = 0; i < strategyTMP.hopr.strategy.strategies.length; i++) { + const strategyName = Object.keys(strategyTMP.hopr.strategy.strategies[i])[0]; + strategiesSet.push(strategyName); + try { + const strategyDetails = strategyTMP.hopr.strategy.strategies[i][strategyName]; + const strategyDetailsKeys = Object.keys(strategyDetails); + for (const key of strategyDetailsKeys) { + const strategyValue = strategyDetails[key]; + if (typeof strategyValue !== 'string') continue; + if ( + strategyValue.includes(' wxHOPR') || + strategyValue.includes('>') || + strategyValue.includes('<') || + strategyValue.includes('=') + ) { + strategiesString = strategiesString.replace(`${key}: ${strategyValue}`, `${key}: "${strategyValue}"`); + } + } + } catch (e) { + console.warn(`Error while processing strategy details for ${strategyName}`, e); + } + } + } + strategiesSet.forEach((strategyName) => { + strategiesString = strategiesString.replace(`- ${strategyName}:`, `- !${strategyName}`); + }); + // ********************************************************************** - const result = yaml.dump(strategyTMP); - - set_strategiesString(result); + set_strategiesString(strategiesString); } catch (e) { console.warn('Error while counting strategies against current ticket price.', e); } @@ -142,6 +167,12 @@ function SettingsPage() { } } + const handleExport = () => { + if (strategiesString) { + exportToFile(strategiesString, `strategies-${myNodeAddress}.yaml`, 'text/yaml'); + } + }; + return (
- Strategies + + Strategies + } + tooltipText={ + + EXPORT +
+ strategies +
+ } + onClick={handleExport} + /> + {strategiesString && ( store.auth.loginData); @@ -62,6 +64,7 @@ function InfoPage() { const indexerLastLogChecksum = useAppSelector((store) => store.node.info.data?.indexerLastLogChecksum); // >=2.2.0 const ticketPrice = useAppSelector((store) => store.node.ticketPrice.data); const minimumNetworkProbability = useAppSelector((store) => store.node.probability.data); + const channelsCorrupted = useAppSelector((store) => store.node.channels.corrupted.data.length > 0); useEffect(() => { fetchInfoData(); @@ -98,6 +101,12 @@ function InfoPage() { apiToken: apiToken ? apiToken : '', }), ); + dispatch( + nodeActionsAsync.getChannelsCorruptedThunk({ + apiEndpoint, + apiToken: apiToken ? apiToken : '', + }), + ); dispatch( nodeActionsAsync.getAddressesThunk({ apiEndpoint, @@ -237,70 +246,65 @@ function InfoPage() { + Possible statuses: +
  • Unknown: Node has just been started recently
  • +
  • Red: No connection
  • +
  • Orange: low-quality connection
  • +
  • Yellow/Green: High-quality node
  • + + } > - Sync process + Connectivity status
    - {nodeSync && typeof nodeSync === 'number' ? : '-'} + + {info?.connectivityStatus} + - Indexer data source + Sync process - {indexerDataSource || '-'} + {nodeSync && typeof nodeSync === 'number' ? : '-'} - Blockchain network + Indexer data source - {info?.chain} + {indexerDataSource || '-'} - Hopr network - - - {info?.network} - - - - - Possible statuses: -
  • Unknown: Node has just been started recently
  • -
  • Red: No connection
  • -
  • Orange: low-quality connection
  • -
  • Yellow/Green: High-quality node
  • - - } - > - Connectivity status + Provider address
    - {info?.connectivityStatus} + {channelsCorrupted ? ( + Faulty RPC | {info?.provider} + ) : ( + info?.provider + )} @@ -328,13 +332,24 @@ function InfoPage() { - Provider address + Hopr network - {info?.provider} + {info?.network} + + + + + Blockchain network + + + {info?.chain} diff --git a/src/router.tsx b/src/router.tsx index 6124c81d..bddcb333 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -311,6 +311,12 @@ const LayoutEnhanced = () => { apiToken: apiToken ? apiToken : '', }), ); + dispatch( + nodeActionsAsync.getChannelsCorruptedThunk({ + apiEndpoint, + apiToken: apiToken ? apiToken : '', + }), + ); dispatch( nodeActionsAsync.getTicketStatisticsThunk({ apiEndpoint, diff --git a/src/store/slices/node/actionsAsync.ts b/src/store/slices/node/actionsAsync.ts index 9cb63ce3..ed7a9166 100644 --- a/src/store/slices/node/actionsAsync.ts +++ b/src/store/slices/node/actionsAsync.ts @@ -41,6 +41,7 @@ import { type GetMinimumNetworkProbabilityResponseType, type OpenSessionPayloadType, type CloseSessionPayloadType, + type GetChannelsCorruptedResponseType, } from '@hoprnet/hopr-sdk'; import { parseMetrics } from '../../../utils/metrics'; import { RootState } from '../..'; @@ -60,6 +61,7 @@ const { getChannel, getChannelTickets, getChannels, + getChannelsCorrupted, getConfiguration, getEntryNodes, getInfo, @@ -256,6 +258,33 @@ const getChannelsThunk = createAsyncThunk( + 'node/getChannelsCorrupted', + async (payload, { rejectWithValue, dispatch }) => { + try { + const channelsCorrupted = await getChannelsCorrupted(payload); + return channelsCorrupted; + } catch (e) { + if (e instanceof sdkApiError) { + return rejectWithValue(e); + } + return rejectWithValue({ status: JSON.stringify(e) }); + } + }, + { + condition: (_payload, { getState }) => { + const isFetching = getState().node.channels.corrupted.isFetching; + if (isFetching) { + return false; + } + }, + }, +); + const getConfigurationThunk = createAsyncThunk< GetConfigurationResponseType | undefined, BasePayloadType, @@ -949,12 +978,13 @@ export const createAsyncReducer = (builder: ActionReducerMapBuilder { if (action.meta.arg.apiEndpoint !== state.apiEndpoint) return; - if (action.payload) { - console.log('getChannelsThunk', action.payload); - state.channels.data = action.payload; - if (action.payload.outgoing.length > 0) { + const channels = action.payload; + if (channels) { + console.log('getChannels', channels); + state.channels.data = channels; + if (channels.outgoing.length > 0) { let balance = BigInt(0); - action.payload.outgoing.forEach((channel) => (balance += parseEther(channel.balance))); + channels.outgoing.forEach((channel) => (balance += parseEther(channel.balance))); state.balances.data.channels = { value: balance.toString(), formatted: formatEther(balance), @@ -991,46 +1021,46 @@ export const createAsyncReducer = (builder: ActionReducerMapBuilder { + builder.addCase(getChannelsThunk.rejected, (state, action) => { state.channels.isFetching = false; }); + //getChannelsCorrupted + builder.addCase(getChannelsCorruptedThunk.pending, (state) => { + state.channels.corrupted.isFetching = true; + }); + builder.addCase(getChannelsCorruptedThunk.fulfilled, (state, action) => { + if (action.meta.arg.apiEndpoint !== state.apiEndpoint) return; + state.channels.corrupted.data = action.payload?.channelIds || []; + state.channels.corrupted.isFetching = false; + }); + builder.addCase(getChannelsCorruptedThunk.rejected, (state, action) => { + state.channels.corrupted.isFetching = false; + }); //openChannel builder.addCase(openChannelThunk.pending, (state, action) => { const peerAddress = action.meta.arg.destination; @@ -1055,7 +1097,6 @@ export const createAsyncReducer = (builder: ActionReducerMapBuilder { - if (action.meta.arg.apiEndpoint !== state.apiEndpoint) return; const peerAddress = action.meta.arg.destination; if (!peerAddress) return; state.channels.parsed.outgoingOpening[peerAddress] = false; @@ -1385,6 +1426,7 @@ export const actionsAsync = { getAddressesThunk, getBalancesThunk, getChannelsThunk, + getChannelsCorruptedThunk, getConfigurationThunk, getPeersThunk, getPeerInfoThunk, diff --git a/src/store/slices/node/initialState.ts b/src/store/slices/node/initialState.ts index 055a5733..962d7306 100644 --- a/src/store/slices/node/initialState.ts +++ b/src/store/slices/node/initialState.ts @@ -108,6 +108,10 @@ type InitialState = { [peerAddress: string]: boolean; }; }; + corrupted: { + data: String[]; + isFetching: boolean; + }; isFetching: boolean; alreadyFetched: boolean; }; @@ -309,6 +313,10 @@ export const initialState: InitialState = { outgoing: {}, outgoingOpening: {}, }, + corrupted: { + data: [], + isFetching: false, + }, isFetching: false, alreadyFetched: false, }, diff --git a/yarn.lock b/yarn.lock index ef65f271..f3ca2ee1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -719,10 +719,10 @@ resolved "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz" integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== -"@hoprnet/hopr-sdk@3.0.0-pr.148-20250709144009": - version "3.0.0-pr.148-20250709144009" - resolved "https://europe-west3-npm.pkg.dev/hoprassociation/npm/@hoprnet/hopr-sdk/-/@hoprnet/hopr-sdk-3.0.0-pr.148-20250709144009.tgz#cf1e07610a95c9e32102444ccad5355cbdd43d28" - integrity sha512-Z6UYZdATZ6bJnhYSKlSU4CmL6fFkIaEHf4PpKdxg5+9I3CTp4AU6vDC3fFXFTabY/57xaOrwWug2J8oQYrEITg== +"@hoprnet/hopr-sdk@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@hoprnet/hopr-sdk/-/hopr-sdk-3.0.1.tgz#77b3aae2d3151443e6ba41d73a990a3aa0134c1d" + integrity sha512-yY3AS5eO2Om7uX4pRK2tpPlvsnQ63t0tSSQVUPXEPMSkfFy+DKZqqu4335GthTHnDHn5Lziien3tWEqpE7w0vw== dependencies: debug "^4.3.4" isomorphic-ws "^5.0.0"