Skip to content

Commit

Permalink
Set RPC node HTTP endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
thesan committed May 31, 2024
1 parent 6985973 commit 90436ef
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 42 deletions.
2 changes: 2 additions & 0 deletions packages/ui/.env.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# TESTNET Endpoints
REACT_APP_TESTNET_NODE_SOCKET=wss://rpc.joystream.org:9944
REACT_APP_TESTNET_NODE_HTTP_RPC=https://rpc.joystream.org
REACT_APP_TESTNET_QUERY_NODE=https://query.joystream.org/graphql
REACT_APP_TESTNET_QUERY_NODE_SOCKET=wss://query.joystream.org/graphql
REACT_APP_TESTNET_MEMBERSHIP_FAUCET_URL=https://faucet.joystream.org/member-faucet/register
REACT_APP_TESTNET_BACKEND=http://localhost:3000

# MAINNET Endpoints
REACT_APP_MAINNET_NODE_SOCKET=wss://rpc.joystream.org:9944
REACT_APP_MAINNET_NODE_HTTP_RPC=https://rpc.joystream.org
REACT_APP_MAINNET_QUERY_NODE=https://query.joystream.org/graphql
REACT_APP_MAINNET_QUERY_NODE_SOCKET=wss://query.joystream.org/graphql
REACT_APP_MAINNET_MEMBERSHIP_FAUCET_URL=https://faucet.joystream.org/member-faucet/register
Expand Down
10 changes: 10 additions & 0 deletions packages/ui/src/app/config/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface NetworkEndpoints {
}

const TESTNET_NODE_SOCKET = process.env.REACT_APP_TESTNET_NODE_SOCKET
const TESTNET_NODE_HTTP_RPC = process.env.REACT_APP_TESTNET_NODE_HTTP_RPC
const TESTNET_QUERY_NODE = process.env.REACT_APP_TESTNET_QUERY_NODE
const TESTNET_QUERY_NODE_SOCKET = process.env.REACT_APP_TESTNET_QUERY_NODE_SOCKET
const TESTNET_MEMBERSHIP_FAUCET_URL = process.env.REACT_APP_TESTNET_MEMBERSHIP_FAUCET_URL
Expand All @@ -20,6 +21,7 @@ export const IS_TESTNET_DEFINED =
TESTNET_NODE_SOCKET && TESTNET_QUERY_NODE && TESTNET_QUERY_NODE_SOCKET && TESTNET_MEMBERSHIP_FAUCET_URL

const MAINNET_NODE_SOCKET = process.env.REACT_APP_MAINNET_NODE_SOCKET
const MAINNET_NODE_HTTP_RPC = process.env.REACT_APP_MAINNET_NODE_HTTP_RPC
const MAINNET_QUERY_NODE = process.env.REACT_APP_MAINNET_QUERY_NODE
const MAINNET_QUERY_NODE_SOCKET = process.env.REACT_APP_MAINNET_QUERY_NODE_SOCKET
const MAINNET_MEMBERSHIP_FAUCET_URL = process.env.REACT_APP_MAINNET_MEMBERSHIP_FAUCET_URL
Expand Down Expand Up @@ -58,6 +60,13 @@ const NODE_RPC_ENDPOINT: PredefinedEndpoint = {
'local-mocks': 'ws://127.0.0.1:9944',
}

const NODE_HTTP_RPC_ENDPOINT: PredefinedEndpoint = {
mainnet: MAINNET_NODE_HTTP_RPC,
local: 'http://127.0.0.1:9933', // TODO: check
testnet: TESTNET_NODE_HTTP_RPC,
'local-mocks': 'http://127.0.0.1:9933', // TODO: check
}

const BACKEND_ENDPOINT: PredefinedEndpoint = {
mainnet: MAINNET_BACKEND,
local: 'http://localhost:3000',
Expand All @@ -67,6 +76,7 @@ const BACKEND_ENDPOINT: PredefinedEndpoint = {

export const pickEndpoints = (network: NetworkType): Partial<NetworkEndpoints> => ({
nodeRpcEndpoint: NODE_RPC_ENDPOINT[network],
nodeHttpRpcEndpoint: NODE_HTTP_RPC_ENDPOINT[network],
queryNodeEndpoint: QUERY_NODE_ENDPOINT[network],
queryNodeEndpointSubscription: QUERY_NODE_ENDPOINT_SUBSCRIPTION[network],
membershipFaucetEndpoint: MEMBERSHIP_FAUCET_ENDPOINT[network],
Expand Down
98 changes: 74 additions & 24 deletions packages/ui/src/app/pages/Settings/SettingsNetworkTab.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { random } from 'lodash'
import React, { useState, useEffect } from 'react'
import { useForm, FormProvider } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
Expand Down Expand Up @@ -35,15 +36,18 @@ export const SettingsNetworkTab = () => {
const [endpoints, fetchNetworkEndpoints] = useNetworkEndpoints()

const form = useForm()
const [customFaucetEndpoint, customRpcEndpoint, customQueryEndpoint, customBackendEndpoint] = form.watch([
'settings.customFaucetEndpoint',
'settings.customRpcEndpoint',
'settings.customQueryEndpoint',
'settings.customBackendEndpoint',
])
const [, storeCustomEndpoints] = useLocalStorage<Omit<NetworkEndpoints, 'nodeHttpRpcEndpoint'>>('custom_endpoint')
const [customFaucetEndpoint, customWsRpcEndpoint, customHttpRpcEndpoint, customQueryEndpoint, customBackendEndpoint] =
form.watch([
'settings.customFaucetEndpoint',
'settings.customWsRpcEndpoint',
'settings.customHttpRpcEndpoint',
'settings.customQueryEndpoint',
'settings.customBackendEndpoint',
])
const [, storeCustomEndpoints] = useLocalStorage<NetworkEndpoints>('custom_endpoint')
const [isValidFaucetEndpoint, setIsValidFaucetEndpoint] = useState(true)
const [isValidRpcEndpoint, setIsValidRpcEndpoint] = useState(true)
const [isValidWsRpcEndpoint, setIsValidWsRpcEndpoint] = useState(true)
const [isValidHttpRpcEndpoint, setIsValidHttpRpcEndpoint] = useState(true)
const [isValidQueryEndpoint, setIsValidQueryEndpoint] = useState(true)
const [isValidBackendEndpoint, setIsValidBackendEndpoint] = useState(true)
const [customSaveStatus, setCustomSaveStatus] = useState<'Init' | 'Saving' | 'Done'>('Init')
Expand All @@ -57,7 +61,8 @@ export const SettingsNetworkTab = () => {

useEffect(() => {
if (network === 'custom') {
form.setValue('settings.customRpcEndpoint', endpoints.nodeRpcEndpoint)
form.setValue('settings.customWsRpcEndpoint', endpoints.nodeRpcEndpoint)
form.setValue('settings.customHttpRpcEndpoint', endpoints.nodeHttpRpcEndpoint)
form.setValue('settings.customQueryEndpoint', endpoints.queryNodeEndpoint)
form.setValue('settings.customFaucetEndpoint', endpoints.membershipFaucetEndpoint ?? '')
form.setValue('settings.customBackendEndpoint', endpoints.backendEndpoint ?? '')
Expand All @@ -66,14 +71,16 @@ export const SettingsNetworkTab = () => {

useEffect(() => {
if (
isValidRpcEndpoint &&
isValidWsRpcEndpoint &&
isValidHttpRpcEndpoint &&
isValidQueryEndpoint &&
isValidFaucetEndpoint &&
isValidBackendEndpoint &&
customSaveStatus === 'Done'
) {
storeCustomEndpoints({
nodeRpcEndpoint: customRpcEndpoint,
nodeRpcEndpoint: customWsRpcEndpoint,
nodeHttpRpcEndpoint: customHttpRpcEndpoint,
queryNodeEndpoint: customQueryEndpoint,
queryNodeEndpointSubscription: customQueryEndpoint.replace(/^http?/, 'ws'),
membershipFaucetEndpoint: customFaucetEndpoint || undefined,
Expand All @@ -82,11 +89,12 @@ export const SettingsNetworkTab = () => {
})
window.location.reload()
}
}, [isValidFaucetEndpoint, isValidRpcEndpoint, isValidQueryEndpoint, customSaveStatus])
}, [isValidFaucetEndpoint, isValidWsRpcEndpoint, isValidQueryEndpoint, customSaveStatus])

const saveSettings = async () => {
if (
!isValidRPCUrl(customRpcEndpoint) ||
!isValidWsUrl(customWsRpcEndpoint) ||
!isValidHttpUrl(customHttpRpcEndpoint) ||
!isValidQNUrl(customQueryEndpoint) ||
!isValidFaucetUrl(customFaucetEndpoint) ||
!isValidBackendUrl(customBackendEndpoint)
Expand All @@ -97,7 +105,8 @@ export const SettingsNetworkTab = () => {
setCustomSaveStatus('Saving')

await Promise.all([
checkEndpoint(customRpcEndpoint, checkRpcEndpoint, setIsValidRpcEndpoint),
checkEndpoint(customWsRpcEndpoint, checkWsRpcEndpoint, setIsValidWsRpcEndpoint),
checkEndpoint(customHttpRpcEndpoint, checkHttpRpcEndpoint, setIsValidHttpRpcEndpoint),
checkEndpoint(customQueryEndpoint, checkGQLEndpoint, setIsValidQueryEndpoint),
checkEndpoint(customFaucetEndpoint, checkFaucetEndpoint, setIsValidFaucetEndpoint),
checkEndpoint(customBackendEndpoint, checkGQLEndpoint, setIsValidBackendEndpoint),
Expand Down Expand Up @@ -127,17 +136,36 @@ export const SettingsNetworkTab = () => {
</SettingsWarningInformation>

<InputComponent
label={t('customRPCNode')}
validation={isValidRPCUrl(customRpcEndpoint) && isValidRpcEndpoint ? undefined : 'invalid'}
label={t('customWsRpcNode')}
validation={isValidWsUrl(customWsRpcEndpoint) && isValidWsRpcEndpoint ? undefined : 'invalid'}
message={cond(
[() => !isValidRPCUrl(customRpcEndpoint), 'This RPC endpoint must start with ws or wss'],
[() => !isValidWsUrl(customWsRpcEndpoint), 'This WS RPC endpoint must start with ws or wss'],
[
() => !isValidRpcEndpoint,
() => !isValidWsRpcEndpoint,
'Connection Error. Sometimes it fails due to network speed. Please try to check once more',
]
)}
>
<InputText id="field-custom-rpcnode" placeholder="Paste RPC node" name="settings.customRpcEndpoint" />
<InputText
id="field-custom-wsrpcnode"
placeholder="Paste WS RPC node"
name="settings.customWsRpcEndpoint"
/>
</InputComponent>

<InputComponent
label={t('customHttpRpcNode')}
validation={isValidHttpUrl(customHttpRpcEndpoint) && isValidHttpRpcEndpoint ? undefined : 'invalid'}
message={cond(
[() => !isValidHttpUrl(customHttpRpcEndpoint), 'This HTTP RPC endpoint must start with http or https'],
[() => !isValidHttpRpcEndpoint, 'Connection Error']
)}
>
<InputText
id="field-custom-httprpcnode"
placeholder="Paste HTTP RPC node"
name="settings.customHttpRpcEndpoint"
/>
</InputComponent>

<InputComponent
Expand Down Expand Up @@ -201,8 +229,10 @@ export const SettingsNetworkTab = () => {
)}
<NetworkInfo
detailsTitle={t('networkDetails')}
networkAddress={endpoints.nodeRpcEndpoint}
queryNodeAddress={endpoints.queryNodeEndpoint}
networkWsAddress={endpoints.nodeRpcEndpoint}
networkHttpAddress={endpoints.nodeHttpRpcEndpoint}
queryNodeHttpAddress={endpoints.queryNodeEndpoint}
queryNodeWsAddress={endpoints.queryNodeEndpointSubscription}
faucetAddress={endpoints.membershipFaucetEndpoint}
backendAddress={endpoints.backendEndpoint}
/>
Expand All @@ -226,7 +256,8 @@ type IsValidOptions = { prefix?: 'https?' | 'wss?'; isRequired?: boolean }
const isValid = (url: string, { prefix = 'https?', isRequired = true }: IsValidOptions = {}) =>
(isRequired === false && url === '') || RegExp(String.raw`${prefix}://\w+/?`, 'i').test(url)

const isValidRPCUrl = (url: string) => isValid(url, { prefix: 'wss?' })
const isValidWsUrl = (url: string) => isValid(url, { prefix: 'wss?' })
const isValidHttpUrl = (url: string) => isValid(url, { prefix: 'https?' })
const isValidQNUrl = (url: string) => isValid(url)
const isValidFaucetUrl = (url: string) => isValid(url, { isRequired: false })
const isValidBackendUrl = (url: string) => isValid(url, { isRequired: false })
Expand All @@ -241,8 +272,8 @@ const checkEndpoint = async (
return isValid
}

const checkRpcEndpoint = async (endpoint: string) => {
// check RPC endpoint
const checkWsRpcEndpoint = async (endpoint: string) => {
// check WS RPC endpoint
try {
return await new Promise<boolean>((resolve) => {
const ws = new WebSocket(endpoint)
Expand All @@ -261,6 +292,25 @@ const checkRpcEndpoint = async (endpoint: string) => {
}
}

const checkHttpRpcEndpoint = async (endpoint: string) => {
// check HTTP RPC endpoint
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: random(Number.MAX_SAFE_INTEGER),
jsonrpc: '2.0',
method: 'system_name',
params: [],
}),
})
return response.status < 400 && (await response.json()).result === 'Joystream Node'
} catch {
return false
}
}

const checkGQLEndpoint = async (endpoint: string) => {
// check GraphQL endpoint
try {
Expand Down
32 changes: 25 additions & 7 deletions packages/ui/src/common/components/NetworkInfo/NetworkInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,42 @@ import { TextMedium } from '@/common/components/typography'

export interface NetworkInfoProps {
detailsTitle: string
networkAddress: string
queryNodeAddress: string
networkWsAddress: string
networkHttpAddress: string
queryNodeHttpAddress: string
queryNodeWsAddress: string
faucetAddress?: string
backendAddress?: string
}

const NetworkInfo: React.FC<NetworkInfoProps> = React.memo(
({ detailsTitle, networkAddress, queryNodeAddress, faucetAddress, backendAddress }) => {
({
detailsTitle,
networkWsAddress,
networkHttpAddress,
queryNodeHttpAddress,
queryNodeWsAddress,
faucetAddress,
backendAddress,
}) => {
const { t } = useTranslation('settings')
return (
<SettingsInformation title={detailsTitle} icon={<WarnedIcon />}>
<ColumnGapBlock gap={3}>
<TextMedium lighter>{t('networkAddress')}</TextMedium>
<CopyText copyText={networkAddress} />
<TextMedium lighter>{t('networkSubscriptionAddress')}</TextMedium>
<CopyText copyText={networkWsAddress} />
</ColumnGapBlock>
<ColumnGapBlock gap={3}>
<TextMedium lighter>{t('QueryNodeAddress')}</TextMedium>
<CopyText copyText={queryNodeAddress} />
<TextMedium lighter>{t('networkHTTPAddress')}</TextMedium>
<CopyText copyText={networkHttpAddress} />
</ColumnGapBlock>
<ColumnGapBlock gap={3}>
<TextMedium lighter>{t('QueryNodeHttpAddress')}</TextMedium>
<CopyText copyText={queryNodeHttpAddress} />
</ColumnGapBlock>
<ColumnGapBlock gap={3}>
<TextMedium lighter>{t('QueryNodeSubscriptionAddress')}</TextMedium>
<CopyText copyText={queryNodeWsAddress} />
</ColumnGapBlock>
<ColumnGapBlock gap={3}>
<TextMedium lighter>{t('faucet')}</TextMedium>
Expand Down
17 changes: 10 additions & 7 deletions packages/ui/src/common/providers/network-endpoints/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DEFAULT_NETWORK, NetworkEndpoints, pickEndpoints } from '@/app/config'
import { Loading } from '@/common/components/Loading'
import { useLocalStorage } from '@/common/hooks/useLocalStorage'
import { useNetwork } from '@/common/hooks/useNetwork'
import { Optional } from '@/common/types'
import { objectEquals } from '@/common/utils'

import { NetworkEndpointsContext } from './context'
Expand All @@ -21,17 +22,19 @@ const EndpointsSchema = Yup.object().shape({
backendEndpoint: Yup.string(),
})

// HACK Pioneer should store the actual HTTP-RPC endpoint.
const endpointReducer: Reducer<NetworkEndpoints | undefined, Omit<NetworkEndpoints, 'nodeHttpRpcEndpoint'>> = (
// HACK will approximately guess the http endpoint
const endpointReducer: Reducer<NetworkEndpoints | undefined, Optional<NetworkEndpoints, 'nodeHttpRpcEndpoint'>> = (
_,
newEndpoints
): NetworkEndpoints => ({
...newEndpoints,
nodeHttpRpcEndpoint: newEndpoints.nodeRpcEndpoint
.replace('ws:', 'http:')
.replace('wss:', 'https:')
.replace('ws-rpc', 'http-rpc')
.replace(':9944', ''),
nodeHttpRpcEndpoint:
newEndpoints.nodeHttpRpcEndpoint ??
newEndpoints.nodeRpcEndpoint
.replace('ws:', 'http:')
.replace('wss:', 'https:')
.replace('ws-rpc', 'http-rpc')
.replace(':9944', ''),
})

export const NetworkEndpointsProvider = ({ children }: Props) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/common/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export type AnyKeys = {
}

export type AnyObject = Record<any, any>

export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
11 changes: 7 additions & 4 deletions packages/ui/src/services/i18n/dict/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
"notifications": "Notifications",
"selectNetwork": "Select Network",
"networkDetails": "Network Details",
"networkAddress": "Network: ",
"QueryNodeAddress": "Query Node: ",
"networkSubscriptionAddress": "Network subscription: ",
"networkHTTPAddress": "Network HTTP endpoint: ",
"QueryNodeHttpAddress": "Query Node: ",
"QueryNodeSubscriptionAddress": "Query Node subscription: ",
"faucet": "Faucet: ",
"backend": "Backend: ",
"chainInfo": "Chain Information",
"rpcBlockheight": "RPC Block Height: ",
"qnBlockheight": "Query Node Block Height: ",
"customFaucet": "FAUCET",
"customRPCNode": "RPC NODE",
"customWsRpcNode": "RPC NODE SUBSCRIPTION",
"customHttpRpcNode": "RPC NODE HTTP ENDPOINT",
"customQueryNode": "QUERY NODE",
"customBackend": "BACKEND"
}
}

0 comments on commit 90436ef

Please sign in to comment.