From 408b565935a59759af6d3e252ceae6981fb60eb6 Mon Sep 17 00:00:00 2001 From: Vojtech Simetka Date: Wed, 27 Jul 2022 08:45:03 +0200 Subject: [PATCH] feat: make blockchain JSON RPC configurable from settings (#494) * feat: make blockchain JSON RPC configurable from settings * chore: expose the settings directly * feat: restart bee node when blockchain RPC changes, add notification and error logging --- src/pages/gift-code/index.tsx | 4 +- src/pages/settings/index.tsx | 85 +++++++++++++++++-------- src/pages/top-up/GiftCardFund.tsx | 4 +- src/pages/top-up/GiftCardTopUpIndex.tsx | 4 +- src/pages/top-up/Swap.tsx | 4 +- src/pages/top-up/index.tsx | 4 +- src/providers/Bee.tsx | 4 +- src/providers/Settings.tsx | 61 ++++++++++++++---- src/providers/TopUp.tsx | 30 ++------- src/utils/net.ts | 4 +- 10 files changed, 126 insertions(+), 78 deletions(-) diff --git a/src/pages/gift-code/index.tsx b/src/pages/gift-code/index.tsx index 99203fa..ca42e13 100644 --- a/src/pages/gift-code/index.tsx +++ b/src/pages/gift-code/index.tsx @@ -13,6 +13,7 @@ import { Loading } from '../../components/Loading' import { SwarmButton } from '../../components/SwarmButton' import { Context as BeeContext } from '../../providers/Bee' import { Context as TopUpContext } from '../../providers/TopUp' +import { Context as SettingsContext } from '../../providers/Settings' import { createGiftWallet } from '../../utils/desktop' import { ResolvedWallet } from '../../utils/wallet' import { Token } from '../../models/Token' @@ -21,7 +22,8 @@ const GIFT_WALLET_FUND_DAI_AMOUNT = Token.fromDecimal('0.1', 18) const GIFT_WALLET_FUND_BZZ_AMOUNT = Token.fromDecimal('0.5', 16) export default function Index(): ReactElement { - const { giftWallets, addGiftWallet, provider } = useContext(TopUpContext) + const { giftWallets, addGiftWallet } = useContext(TopUpContext) + const { provider } = useContext(SettingsContext) const { balance } = useContext(BeeContext) const [loading, setLoading] = useState(false) diff --git a/src/pages/settings/index.tsx b/src/pages/settings/index.tsx index 21355a6..4170dc3 100644 --- a/src/pages/settings/index.tsx +++ b/src/pages/settings/index.tsx @@ -2,11 +2,27 @@ import CircularProgress from '@material-ui/core/CircularProgress' import { ReactElement, useContext } from 'react' import ExpandableList from '../../components/ExpandableList' import ExpandableListItemInput from '../../components/ExpandableListItemInput' +import { Context as BeeContext } from '../../providers/Bee' import { Context as SettingsContext } from '../../providers/Settings' +import config from '../../config' +import { useSnackbar } from 'notistack' export default function SettingsPage(): ReactElement { - const { apiUrl, apiDebugUrl, setApiUrl, setDebugApiUrl, lockedApiSettings, config, isLoading } = - useContext(SettingsContext) + const { + apiUrl, + apiDebugUrl, + setApiUrl, + setDebugApiUrl, + lockedApiSettings, + cors, + dataDir, + ensResolver, + providerUrl, + isLoading, + setAndPersistJsonRpcProvider, + } = useContext(SettingsContext) + const { refresh } = useContext(BeeContext) + const { enqueueSnackbar } = useSnackbar() if (isLoading) { return ( @@ -16,31 +32,46 @@ export default function SettingsPage(): ReactElement { ) } - // Run within Bee Desktop, display read only config - if (config) { - return ( - - - - - - - {config['swap-endpoint'] && ( - - )} - - ) - } - return ( - - - - + <> + + + + { + setAndPersistJsonRpcProvider(value) + .then(() => { + refresh() + enqueueSnackbar('Settings changed, restarting bee node...', { variant: 'success' }) + }) + .catch(error => { + console.log(error) //eslint-disable-line + enqueueSnackbar(`Failed to change RPC endpoint. Error: ${error}`, { variant: 'success' }) + }) + }} + /> + + {config.BEE_DESKTOP_ENABLED && ( + + + + + + )} + ) } diff --git a/src/pages/top-up/GiftCardFund.tsx b/src/pages/top-up/GiftCardFund.tsx index e239746..51d9ee1 100644 --- a/src/pages/top-up/GiftCardFund.tsx +++ b/src/pages/top-up/GiftCardFund.tsx @@ -12,7 +12,7 @@ import { ProgressIndicator } from '../../components/ProgressIndicator' import { SwarmButton } from '../../components/SwarmButton' import { SwarmDivider } from '../../components/SwarmDivider' import { Context as BeeContext } from '../../providers/Bee' -import { Context as TopUpContext } from '../../providers/TopUp' +import { Context as SettingsContext } from '../../providers/Settings' import { ROUTES } from '../../routes' import { sleepMs } from '../../utils' import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop' @@ -23,7 +23,7 @@ import { BeeModes } from '@ethersphere/bee-js' export function GiftCardFund(): ReactElement { const isBeeDesktop = config.BEE_DESKTOP_ENABLED const { nodeAddresses, balance, nodeInfo } = useContext(BeeContext) - const { provider, providerUrl } = useContext(TopUpContext) + const { provider, providerUrl } = useContext(SettingsContext) const [loading, setLoading] = useState(false) const [wallet, setWallet] = useState(null) diff --git a/src/pages/top-up/GiftCardTopUpIndex.tsx b/src/pages/top-up/GiftCardTopUpIndex.tsx index f46faff..ae5ceb3 100644 --- a/src/pages/top-up/GiftCardTopUpIndex.tsx +++ b/src/pages/top-up/GiftCardTopUpIndex.tsx @@ -4,7 +4,7 @@ import { useSnackbar } from 'notistack' import { ReactElement, useContext, useState } from 'react' import ArrowRight from 'remixicon-react/ArrowRightLineIcon' import { useNavigate } from 'react-router' -import { Context as TopUpContext } from '../../providers/TopUp' +import { Context as SettingsContext } from '../../providers/Settings' import { HistoryHeader } from '../../components/HistoryHeader' import { ProgressIndicator } from '../../components/ProgressIndicator' import { SwarmButton } from '../../components/SwarmButton' @@ -16,7 +16,7 @@ import { ROUTES } from '../../routes' import { Rpc } from '../../utils/rpc' export function GiftCardTopUpIndex(): ReactElement { - const { provider } = useContext(TopUpContext) + const { provider } = useContext(SettingsContext) const [loading, setLoading] = useState(false) const [giftCode, setGiftCode] = useState('') diff --git a/src/pages/top-up/Swap.tsx b/src/pages/top-up/Swap.tsx index 778f228..471b779 100644 --- a/src/pages/top-up/Swap.tsx +++ b/src/pages/top-up/Swap.tsx @@ -17,7 +17,7 @@ import { SwarmTextInput } from '../../components/SwarmTextInput' import { BzzToken } from '../../models/BzzToken' import { DaiToken } from '../../models/DaiToken' import { Context as BeeContext } from '../../providers/Bee' -import { Context as TopUpContext } from '../../providers/TopUp' +import { Context as SettingsContext } from '../../providers/Settings' import { ROUTES } from '../../routes' import { sleepMs } from '../../utils' import { getBzzPriceAsDai, performSwap, restartBeeNode, upgradeToLightNode } from '../../utils/desktop' @@ -37,7 +37,7 @@ export function Swap({ header }: Props): ReactElement { const [userInputSwap, setUserInputSwap] = useState(null) const [price, setPrice] = useState(DaiToken.fromDecimal('0.6', 18)) - const { providerUrl } = useContext(TopUpContext) + const { providerUrl } = useContext(SettingsContext) const { balance, nodeAddresses, nodeInfo } = useContext(BeeContext) const isBeeDesktop = config.BEE_DESKTOP_ENABLED diff --git a/src/pages/top-up/index.tsx b/src/pages/top-up/index.tsx index 18da23d..905d22b 100644 --- a/src/pages/top-up/index.tsx +++ b/src/pages/top-up/index.tsx @@ -11,7 +11,7 @@ import { HistoryHeader } from '../../components/HistoryHeader' import { SwarmButton } from '../../components/SwarmButton' import { ROUTES } from '../../routes' import { Context as BeeContext } from '../../providers/Bee' -import { Context as TopUpContext } from '../../providers/TopUp' +import { Context as SettingsContext } from '../../providers/Settings' import config from '../../config' import { BeeModes } from '@ethersphere/bee-js' import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop' @@ -40,7 +40,7 @@ export default function TopUp(): ReactElement { const styles = useStyles() const isBeeDesktop = config.BEE_DESKTOP_ENABLED const { balance, nodeInfo } = useContext(BeeContext) - const { providerUrl } = useContext(TopUpContext) + const { providerUrl } = useContext(SettingsContext) const [loading, setLoading] = useState(false) const { enqueueSnackbar } = useSnackbar() diff --git a/src/providers/Bee.tsx b/src/providers/Bee.tsx index 89fa77c..b76f222 100644 --- a/src/providers/Bee.tsx +++ b/src/providers/Bee.tsx @@ -18,7 +18,6 @@ import { Token } from '../models/Token' import type { Balance, ChequebookBalance, Settlements } from '../types' import { WalletAddress } from '../utils/wallet' import { Context as SettingsContext } from './Settings' -import { Context as TopUpContext } from './TopUp' const REFRESH_WHEN_OK = 30_000 const REFRESH_WHEN_ERROR = 5_000 @@ -192,8 +191,7 @@ function getStatus( let isRefreshing = false export function Provider({ children }: Props): ReactElement { - const { beeApi, beeDebugApi } = useContext(SettingsContext) - const { provider } = useContext(TopUpContext) + const { beeApi, beeDebugApi, provider } = useContext(SettingsContext) const [apiHealth, setApiHealth] = useState(false) const [debugApiHealth, setDebugApiHealth] = useState(null) const [nodeAddresses, setNodeAddresses] = useState(null) diff --git a/src/providers/Settings.tsx b/src/providers/Settings.tsx index 3680030..46d5412 100644 --- a/src/providers/Settings.tsx +++ b/src/providers/Settings.tsx @@ -1,32 +1,50 @@ import { Bee, BeeDebug } from '@ethersphere/bee-js' +import { providers } from 'ethers' import { createContext, ReactChild, ReactElement, useEffect, useState } from 'react' -import { config } from '../config' -import { BeeConfig, useGetBeeConfig } from '../hooks/apiHooks' +import { config as appConfig } from '../config' +import { useGetBeeConfig } from '../hooks/apiHooks' +import { restartBeeNode, setJsonRpcInDesktop } from '../utils/desktop' + +const LocalStorageKeys = { + providerUrl: 'json-rpc-provider', +} + +const providerUrl = localStorage.getItem('json-rpc-provider') || appConfig.DEFAULT_RPC_URL interface ContextInterface { apiUrl: string apiDebugUrl: string beeApi: Bee | null beeDebugApi: BeeDebug | null - setApiUrl: (url: string) => void - setDebugApiUrl: (url: string) => void lockedApiSettings: boolean desktopApiKey: string - config: BeeConfig | null + providerUrl: string + provider: providers.JsonRpcProvider + cors: string | null + dataDir: string | null + ensResolver: string | null + setApiUrl: (url: string) => void + setDebugApiUrl: (url: string) => void + setAndPersistJsonRpcProvider: (url: string) => Promise isLoading: boolean error: Error | null } const initialValues: ContextInterface = { - apiUrl: config.BEE_API_HOST, - apiDebugUrl: config.BEE_DEBUG_API_HOST, + apiUrl: appConfig.BEE_API_HOST, + apiDebugUrl: appConfig.BEE_DEBUG_API_HOST, beeApi: null, beeDebugApi: null, setApiUrl: () => {}, // eslint-disable-line setDebugApiUrl: () => {}, // eslint-disable-line lockedApiSettings: false, desktopApiKey: '', - config: null, + setAndPersistJsonRpcProvider: async () => {}, // eslint-disable-line + providerUrl, + provider: new providers.JsonRpcProvider(providerUrl), + cors: null, + dataDir: null, + ensResolver: null, isLoading: true, error: null, } @@ -51,10 +69,26 @@ export function Provider({ const [apiDebugUrl, setDebugApiUrl] = useState(initialValues.apiDebugUrl) const [beeApi, setBeeApi] = useState(null) const [beeDebugApi, setBeeDebugApi] = useState(null) - const [lockedApiSettings] = useState(Boolean(extLockedApiSettings)) const [desktopApiKey, setDesktopApiKey] = useState(initialValues.desktopApiKey) + const [providerUrl, setProviderUrl] = useState(initialValues.providerUrl) + const [provider, setProvider] = useState(initialValues.provider) const { config, isLoading, error } = useGetBeeConfig() + async function setAndPersistJsonRpcProvider(providerUrl: string) { + try { + localStorage.setItem(LocalStorageKeys.providerUrl, providerUrl) + setProviderUrl(providerUrl) + setProvider(new providers.JsonRpcProvider(providerUrl)) + + if (appConfig.BEE_DESKTOP_ENABLED) { + await setJsonRpcInDesktop(providerUrl) + await restartBeeNode() + } + } catch (error) { + console.error(error) // eslint-disable-line + } + } + function makeHttpUrl(string: string): string { if (!string.startsWith('http')) { return `http://${string}` @@ -104,9 +138,14 @@ export function Provider({ beeDebugApi, setApiUrl, setDebugApiUrl, - lockedApiSettings, + lockedApiSettings: Boolean(extLockedApiSettings), desktopApiKey, - config, + provider, + providerUrl, + cors: config?.['cors-allowed-origins'] ?? null, + dataDir: config?.['data-dir'] ?? null, + ensResolver: config?.['resolver-options'] ?? null, + setAndPersistJsonRpcProvider, isLoading, error, }} diff --git a/src/providers/TopUp.tsx b/src/providers/TopUp.tsx index 7f9c40f..d25e4b3 100644 --- a/src/providers/TopUp.tsx +++ b/src/providers/TopUp.tsx @@ -1,30 +1,20 @@ -import { providers, Wallet } from 'ethers' -import { createContext, ReactElement, useEffect, useState } from 'react' -import config from '../config' -import { setJsonRpcInDesktop } from '../utils/desktop' +import { Wallet } from 'ethers' +import { createContext, ReactElement, useContext, useEffect, useState } from 'react' +import { Context as SettingsContext } from './Settings' const LocalStorageKeys = { - providerUrl: 'json-rpc-provider', depositWallet: 'deposit-wallet', giftWallets: 'gift-wallets', invitation: 'invitation', } interface ContextInterface { - providerUrl: string - provider: providers.JsonRpcProvider giftWallets: Wallet[] - setProviderUrl: (providerUrl: string) => void addGiftWallet: (wallet: Wallet) => void } -const providerUrl = localStorage.getItem('json-rpc-provider') || config.DEFAULT_RPC_URL - const initialValues: ContextInterface = { - providerUrl, - provider: new providers.JsonRpcProvider(providerUrl), giftWallets: [], - setProviderUrl: () => {}, // eslint-disable-line addGiftWallet: () => {}, // eslint-disable-line } @@ -36,9 +26,8 @@ interface Props { } export function Provider({ children }: Props): ReactElement { - const [providerUrl, setProviderUrl] = useState(initialValues.providerUrl) - const [provider, setProvider] = useState(initialValues.provider) const [giftWallets, setGiftWallets] = useState(initialValues.giftWallets) + const { provider } = useContext(SettingsContext) useEffect(() => { if (provider === null) return @@ -50,14 +39,6 @@ export function Provider({ children }: Props): ReactElement { } }, [provider]) - function setAndPersistJsonRpcProvider(providerUrl: string) { - localStorage.setItem(LocalStorageKeys.providerUrl, providerUrl) - setProviderUrl(providerUrl) - setProvider(new providers.JsonRpcProvider(providerUrl)) - // eslint-disable-next-line no-console - setJsonRpcInDesktop(providerUrl).catch(console.error) - } - function addGiftWallet(wallet: Wallet) { const newArray = [...giftWallets, wallet] localStorage.setItem(LocalStorageKeys.giftWallets, JSON.stringify(newArray.map(x => x.privateKey))) @@ -67,10 +48,7 @@ export function Provider({ children }: Props): ReactElement { return ( diff --git a/src/utils/net.ts b/src/utils/net.ts index 3e116a6..e215f6d 100644 --- a/src/utils/net.ts +++ b/src/utils/net.ts @@ -6,8 +6,8 @@ export function getJson>(url: string): Promise return sendRequest(url, 'GET') as Promise } -export function postJson(url: string, data?: Record): Promise> { - return sendRequest(url, 'POST', data) +export function postJson>(url: string, data?: T): Promise { + return sendRequest(url, 'POST', data) as Promise } export async function sendRequest(