From 1b493aa301e7bbcf16ecc56474ee55e3701c44e3 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Fri, 13 Sep 2024 11:29:26 -0400 Subject: [PATCH] refactor(renterd): settings APIs and daemon explorer exchange rates --- .changeset/fluffy-emus-tease.md | 7 + .changeset/four-plums-invent.md | 7 + .changeset/loud-rockets-act.md | 5 + .changeset/lovely-bikes-push.md | 5 + .changeset/old-jars-study.md | 5 + .changeset/silver-pears-drop.md | 5 + .changeset/six-moons-relax.md | 5 + .changeset/sour-masks-shake.md | 7 + .../src/fixtures/configResetAllSettings.ts | 6 - apps/renterd-e2e/src/specs/config.spec.ts | 7 +- .../Config/PinnedCurrencyWarning.tsx | 15 +- apps/renterd/components/Config/index.tsx | 28 +- .../checks/useDefaultContractSetNotSet.tsx | 6 +- .../Files/checks/useNotEnoughContracts.tsx | 10 +- .../components/Keys/KeyContextMenu.tsx | 27 +- .../components/Keys/KeysCreateDialog.tsx | 27 +- .../config/fieldTips/MaxDownloadPrice.tsx | 23 +- .../config/fieldTips/MaxStoragePrice.tsx | 25 +- .../config/fieldTips/MaxUploadPrice.tsx | 25 +- apps/renterd/contexts/config/fields.tsx | 121 +-- apps/renterd/contexts/config/index.tsx | 66 +- apps/renterd/contexts/config/resources.ts | 85 +- .../renterd/contexts/config/transform.spec.ts | 781 +++++++++--------- apps/renterd/contexts/config/transformDown.ts | 104 +-- apps/renterd/contexts/config/transformUp.ts | 113 +-- apps/renterd/contexts/config/types.ts | 86 +- .../config/useAllowanceDerivedPricing.tsx | 88 +- .../config/useAutopilotEvaluations.tsx | 48 +- apps/renterd/contexts/config/useAverages.tsx | 20 +- apps/renterd/contexts/config/useForm.tsx | 9 +- .../contexts/config/useFormExchangeRate.tsx | 14 + apps/renterd/contexts/config/useOnValid.tsx | 185 ++--- apps/renterd/contexts/config/useResources.tsx | 41 +- .../contexts/config/useSyncContractSet.tsx | 30 +- apps/renterd/contexts/contracts/columns.tsx | 33 +- apps/renterd/contexts/contracts/dataset.tsx | 3 +- apps/renterd/contexts/contracts/index.tsx | 6 +- apps/renterd/contexts/contracts/types.ts | 21 +- .../contexts/contracts/useFilteredStats.tsx | 19 +- .../contexts/filesManager/index.spec.tsx | 4 +- .../renterd/contexts/filesManager/uploads.tsx | 20 +- apps/renterd/contexts/keys/index.tsx | 18 +- apps/renterd/hooks/useIsApcsEqDcs.tsx | 8 +- apps/renterd/mock/mock.tsx | 19 +- apps/renterd/pages/_app.tsx | 2 + libs/clusterd/src/index.ts | 2 +- libs/react-core/src/useExchangeRate.tsx | 2 +- libs/renterd-js/package.json | 3 +- libs/renterd-js/src/bus.ts | 213 ++--- libs/renterd-react/src/bus.ts | 157 ++-- libs/renterd-types/src/autopilot.ts | 8 +- libs/renterd-types/src/bus.ts | 62 +- libs/renterd-types/src/types.ts | 47 +- 53 files changed, 1271 insertions(+), 1412 deletions(-) create mode 100644 .changeset/fluffy-emus-tease.md create mode 100644 .changeset/four-plums-invent.md create mode 100644 .changeset/loud-rockets-act.md create mode 100644 .changeset/lovely-bikes-push.md create mode 100644 .changeset/old-jars-study.md create mode 100644 .changeset/silver-pears-drop.md create mode 100644 .changeset/six-moons-relax.md create mode 100644 .changeset/sour-masks-shake.md create mode 100644 apps/renterd/contexts/config/useFormExchangeRate.tsx diff --git a/.changeset/fluffy-emus-tease.md b/.changeset/fluffy-emus-tease.md new file mode 100644 index 000000000..7875ef934 --- /dev/null +++ b/.changeset/fluffy-emus-tease.md @@ -0,0 +1,7 @@ +--- +'@siafoundation/renterd-js': minor +'@siafoundation/renterd-react': minor +'@siafoundation/renterd-types': minor +--- + +Removed deprecated setting APIs. diff --git a/.changeset/four-plums-invent.md b/.changeset/four-plums-invent.md new file mode 100644 index 000000000..64339e288 --- /dev/null +++ b/.changeset/four-plums-invent.md @@ -0,0 +1,7 @@ +--- +'@siafoundation/renterd-js': minor +'@siafoundation/renterd-react': minor +'@siafoundation/renterd-types': minor +--- + +Added new strong settings APIs. diff --git a/.changeset/loud-rockets-act.md b/.changeset/loud-rockets-act.md new file mode 100644 index 000000000..0f983e74c --- /dev/null +++ b/.changeset/loud-rockets-act.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +The configuration feature now internally uses the new strong settings APIs. diff --git a/.changeset/lovely-bikes-push.md b/.changeset/lovely-bikes-push.md new file mode 100644 index 000000000..c914b6cfb --- /dev/null +++ b/.changeset/lovely-bikes-push.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +The configuration feature no longer includes the enable pinning or forex endpoint options under price pinning. diff --git a/.changeset/old-jars-study.md b/.changeset/old-jars-study.md new file mode 100644 index 000000000..0f378613a --- /dev/null +++ b/.changeset/old-jars-study.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +Contracts can now be filtered and sorted by deletions and sector roots spending. diff --git a/.changeset/silver-pears-drop.md b/.changeset/silver-pears-drop.md new file mode 100644 index 000000000..0984fbc41 --- /dev/null +++ b/.changeset/silver-pears-drop.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +The app now uses the daemon configured explorer for exchange rates. diff --git a/.changeset/six-moons-relax.md b/.changeset/six-moons-relax.md new file mode 100644 index 000000000..e18d999d0 --- /dev/null +++ b/.changeset/six-moons-relax.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +The keys feature now internally uses the new S3 settings API format. diff --git a/.changeset/sour-masks-shake.md b/.changeset/sour-masks-shake.md new file mode 100644 index 000000000..279375aeb --- /dev/null +++ b/.changeset/sour-masks-shake.md @@ -0,0 +1,7 @@ +--- +'@siafoundation/renterd-js': minor +'@siafoundation/renterd-react': minor +'@siafoundation/renterd-types': minor +--- + +The bus state API now includes daemon configured explorer. diff --git a/apps/renterd-e2e/src/fixtures/configResetAllSettings.ts b/apps/renterd-e2e/src/fixtures/configResetAllSettings.ts index 2f210910b..b1b3a4fde 100644 --- a/apps/renterd-e2e/src/fixtures/configResetAllSettings.ts +++ b/apps/renterd-e2e/src/fixtures/configResetAllSettings.ts @@ -12,14 +12,8 @@ export async function configResetAllSettings({ page }: { page: Page }) { await setViewMode({ page, state: 'advanced' }) // pinning - await setSwitchByLabel(page, 'pinningEnabled', true) await fillSelectInputByName(page, 'pinnedCurrency', 'usd') await fillTextInputByName(page, 'pinnedThreshold', '2') - await fillTextInputByName( - page, - 'forexEndpointURL', - 'https://api.siascan.com/exchange-rate/siacoin' - ) // storage await fillTextInputByName(page, 'storageTB', '1') diff --git a/apps/renterd-e2e/src/specs/config.spec.ts b/apps/renterd-e2e/src/specs/config.spec.ts index 34bd7d57b..7ded3bbc8 100644 --- a/apps/renterd-e2e/src/specs/config.spec.ts +++ b/apps/renterd-e2e/src/specs/config.spec.ts @@ -9,6 +9,7 @@ import { import { afterTest, beforeTest } from '../fixtures/beforeTest' import { setCurrencyDisplay } from '../fixtures/preferences' import { configResetAllSettings } from '../fixtures/configResetAllSettings' +import { fillSelectInputByName } from '../fixtures/selectInput' test.beforeEach(async ({ page }) => { await beforeTest(page) @@ -147,17 +148,17 @@ test('should show warning if pinning is not fully configured', async ({ await setSwitchByLabel(page, 'shouldPinAllowance', true) await setSwitchByLabel(page, 'shouldPinMaxStoragePrice', true) - await fillTextInputByName(page, 'forexEndpointURL', '') + await fillSelectInputByName(page, 'pinnedCurrency', '') await expect( page .getByTestId('allowanceMonthGroup') - .getByText('Enter a forex endpoint URL') + .getByText('Select a pinned currency') ).toBeVisible() await expect( page .getByTestId('maxStoragePriceTBMonthGroup') - .getByText('Enter a forex endpoint URL') + .getByText('Select a pinned currency') ).toBeVisible() }) diff --git a/apps/renterd/components/Config/PinnedCurrencyWarning.tsx b/apps/renterd/components/Config/PinnedCurrencyWarning.tsx index 655beed60..13476f977 100644 --- a/apps/renterd/components/Config/PinnedCurrencyWarning.tsx +++ b/apps/renterd/components/Config/PinnedCurrencyWarning.tsx @@ -2,13 +2,11 @@ import { Link, Panel, Text } from '@siafoundation/design-system' import { routes } from '../../config/routes' export function PinnedCurrencyWarning({ - pinningEnabled, + canUseExchangeRates, pinnedCurrency, - forexEndpointURL, }: { - pinningEnabled: boolean + canUseExchangeRates: boolean pinnedCurrency: string - forexEndpointURL: string }) { return ( @@ -16,9 +14,9 @@ export function PinnedCurrencyWarning({ To pin this field:
- {!pinningEnabled && ( + {!canUseExchangeRates && !!pinnedCurrency && ( - - Enable the pinning feature + - Enable an exchange rate API )} {!pinnedCurrency && ( @@ -26,11 +24,6 @@ export function PinnedCurrencyWarning({ - Select a pinned currency )} - {!forexEndpointURL && ( - - - Enter a forex endpoint URL - - )}
) diff --git a/apps/renterd/components/Config/index.tsx b/apps/renterd/components/Config/index.tsx index 07f77fb3c..d629153b6 100644 --- a/apps/renterd/components/Config/index.tsx +++ b/apps/renterd/components/Config/index.tsx @@ -18,20 +18,22 @@ import { StateConnError } from './StateConnError' import { Recommendations } from './Recommendations' import { ShouldPinSwitch } from './ShouldPinSwitch' import { PinnedCurrencyWarning } from './PinnedCurrencyWarning' +import { useExchangeRate } from '@siafoundation/react-core' export function Config() { const { openDialog } = useDialog() const { form, fields, remoteError, configRef } = useConfig() - const pinningEnabled = form.watch('pinningEnabled') const pinnedCurrency = form.watch('pinnedCurrency') - const forexEndpointURL = form.watch('forexEndpointURL') const shouldPinAllowance = form.watch('shouldPinAllowance') const shouldPinMaxStoragePrice = form.watch('shouldPinMaxStoragePrice') const shouldPinMaxUploadPrice = form.watch('shouldPinMaxUploadPrice') const shouldPinMaxDownloadPrice = form.watch('shouldPinMaxDownloadPrice') - const canShowPinned = pinningEnabled && pinnedCurrency && forexEndpointURL + const { rate } = useExchangeRate({ + currency: pinnedCurrency || undefined, + }) + const canUseExchangeRates = !!rate return ( {shouldPinAllowance ? ( - canShowPinned ? ( + canUseExchangeRates ? ( ) : ( ) ) : ( @@ -148,7 +149,7 @@ export function Config() { fields={fields} /> {shouldPinMaxStoragePrice ? ( - canShowPinned ? ( + canUseExchangeRates ? ( ) : ( ) ) : ( @@ -184,7 +184,7 @@ export function Config() { fields={fields} /> {shouldPinMaxUploadPrice ? ( - canShowPinned ? ( + canUseExchangeRates ? ( ) : ( ) ) : ( @@ -220,7 +219,7 @@ export function Config() { fields={fields} /> {shouldPinMaxDownloadPrice ? ( - canShowPinned ? ( + canUseExchangeRates ? ( ) : ( ) ) : ( diff --git a/apps/renterd/components/Files/checks/useDefaultContractSetNotSet.tsx b/apps/renterd/components/Files/checks/useDefaultContractSetNotSet.tsx index 050ec866b..f01effb77 100644 --- a/apps/renterd/components/Files/checks/useDefaultContractSetNotSet.tsx +++ b/apps/renterd/components/Files/checks/useDefaultContractSetNotSet.tsx @@ -1,9 +1,9 @@ -import { useSettingContractSet } from '@siafoundation/renterd-react' +import { useSettingsUpload } from '@siafoundation/renterd-react' export function useDefaultContractSetNotSet() { - const css = useSettingContractSet() + const su = useSettingsUpload() return { - active: css.data && !css.data?.default, + active: su.data && !su.data?.defaultContractSet, } } diff --git a/apps/renterd/components/Files/checks/useNotEnoughContracts.tsx b/apps/renterd/components/Files/checks/useNotEnoughContracts.tsx index e0b78b8ed..7f161729f 100644 --- a/apps/renterd/components/Files/checks/useNotEnoughContracts.tsx +++ b/apps/renterd/components/Files/checks/useNotEnoughContracts.tsx @@ -1,18 +1,18 @@ -import { useSettingRedundancy } from '@siafoundation/renterd-react' +import { useSettingsUpload } from '@siafoundation/renterd-react' import { useContracts } from '../../../contexts/contracts' export function useNotEnoughContracts() { - const redundancy = useSettingRedundancy() + const settingsUpload = useSettingsUpload() const { datasetCount, isLoading: isContractsLoading } = useContracts() const active = - redundancy.data && + settingsUpload.data && !isContractsLoading && - datasetCount < redundancy.data.totalShards + datasetCount < settingsUpload.data.redundancy.totalShards return { active, count: datasetCount, - required: redundancy.data?.totalShards || 0, + required: settingsUpload.data?.redundancy.totalShards || 0, } } diff --git a/apps/renterd/components/Keys/KeyContextMenu.tsx b/apps/renterd/components/Keys/KeyContextMenu.tsx index 6664ea215..bfbf3afdd 100644 --- a/apps/renterd/components/Keys/KeyContextMenu.tsx +++ b/apps/renterd/components/Keys/KeyContextMenu.tsx @@ -12,8 +12,8 @@ import { } from '@siafoundation/design-system' import { CaretDown16, Delete16 } from '@siafoundation/react-icons' import { - useSettingS3Authentication, - useSettingUpdate, + useSettingsS3, + useSettingsS3Update, } from '@siafoundation/renterd-react' import { useCallback } from 'react' import { omit } from '@technically/lodash' @@ -27,16 +27,21 @@ type Props = { export function KeyContextMenu({ s3Key, contentProps, buttonProps }: Props) { const { openConfirmDialog } = useDialog() - const s3AuthenticationSettings = useSettingS3Authentication() - const update = useSettingUpdate() + const settingsS3 = useSettingsS3() + const settingsS3Update = useSettingsS3Update() const deleteKey = useCallback(async () => { - const newKeys = omit(s3AuthenticationSettings.data?.v4Keypairs, s3Key) - const response = await update.put({ - params: { - key: 's3authentication', - }, + if (!settingsS3.data) { + triggerErrorToast({ title: 'Error deleting key' }) + return + } + const newKeys = omit(settingsS3.data?.authentication.v4Keypairs, s3Key) + const response = await settingsS3Update.put({ payload: { - v4Keypairs: newKeys, + ...settingsS3.data, + authentication: { + ...settingsS3.data.authentication, + v4Keypairs: newKeys, + }, }, }) if (response.error) { @@ -44,7 +49,7 @@ export function KeyContextMenu({ s3Key, contentProps, buttonProps }: Props) { } else { triggerSuccessToast({ title: `Key ${s3Key} removed` }) } - }, [s3AuthenticationSettings.data, s3Key, update]) + }, [settingsS3.data, s3Key, settingsS3Update]) return ( { const v4Keypairs = { - ...s3AuthenticationSettings.data?.v4Keypairs, + ...settingsS3.data?.authentication.v4Keypairs, [values.name]: values.secret, } - const response = await update.put({ - params: { - key: 's3authentication', - }, + const response = await settingsS3Update.put({ payload: { - v4Keypairs, + ...settingsS3.data, + authentication: { + ...settingsS3.data.authentication, + v4Keypairs, + }, }, }) if (response.error) { @@ -130,7 +131,7 @@ export function KeysCreateDialog({ trigger, open, onOpenChange }: Props) { closeDialog() } }, - [form, closeDialog, update, s3AuthenticationSettings.data] + [form, closeDialog, settingsS3Update, settingsS3.data] ) const fields = useMemo( @@ -143,10 +144,10 @@ export function KeysCreateDialog({ trigger, open, onOpenChange }: Props) { form.setValue('secret', generateSecretAccessKey()) }, existingKeys: Object.keys( - s3AuthenticationSettings.data?.v4Keypairs || {} + settingsS3.data?.authentication.v4Keypairs || {} ), }), - [s3AuthenticationSettings.data, form] + [settingsS3.data, form] ) const onInvalid = useOnInvalid(fields) diff --git a/apps/renterd/contexts/config/fieldTips/MaxDownloadPrice.tsx b/apps/renterd/contexts/config/fieldTips/MaxDownloadPrice.tsx index 999bedf33..f884a35d1 100644 --- a/apps/renterd/contexts/config/fieldTips/MaxDownloadPrice.tsx +++ b/apps/renterd/contexts/config/fieldTips/MaxDownloadPrice.tsx @@ -3,14 +3,11 @@ import { TipNumber, formSetField, } from '@siafoundation/design-system' -import React from 'react' -import { Categories, RecommendationItem, SettingsData } from '../types' -import { toHastings, fiatToSiacoin } from '@siafoundation/units' +import { fiatToSiacoin, toHastings } from '@siafoundation/units' import { UseFormReturn } from 'react-hook-form' -import { - useAllowanceDerivedPricingForEnabledFields, - useForexExchangeRate, -} from '../useAllowanceDerivedPricing' +import { Categories, RecommendationItem, SettingsData } from '../types' +import { useAllowanceDerivedPricingForEnabledFields } from '../useAllowanceDerivedPricing' +import { useFormExchangeRate } from '../useFormExchangeRate' import { fitPriceToCurrentAllowanceTipContent, recommendationTipContent, @@ -84,18 +81,16 @@ export function MaxDownloadPricePinnedTips({ const derived = useAllowanceDerivedPricingForEnabledFields({ form, }) - const exchangeRate = useForexExchangeRate({ - form, - }) + const { rate } = useFormExchangeRate(form) const derivedPriceInSiacoin = - derived?.maxDownloadPriceTBPinned && exchangeRate - ? fiatToSiacoin(derived.maxDownloadPriceTBPinned, exchangeRate) + derived?.maxDownloadPriceTBPinned && rate + ? fiatToSiacoin(derived.maxDownloadPriceTBPinned, rate) : null const recommendationInFiat = recommendations?.maxDownloadPriceTBPinned?.targetValue const recommendationInSiacoin = - recommendationInFiat && exchangeRate - ? fiatToSiacoin(recommendationInFiat, exchangeRate) + recommendationInFiat && rate + ? fiatToSiacoin(recommendationInFiat, rate) : null return ( <> diff --git a/apps/renterd/contexts/config/fieldTips/MaxStoragePrice.tsx b/apps/renterd/contexts/config/fieldTips/MaxStoragePrice.tsx index 3ab9f7013..79a309639 100644 --- a/apps/renterd/contexts/config/fieldTips/MaxStoragePrice.tsx +++ b/apps/renterd/contexts/config/fieldTips/MaxStoragePrice.tsx @@ -3,14 +3,11 @@ import { TipNumber, formSetField, } from '@siafoundation/design-system' -import React from 'react' -import { Categories, RecommendationItem, SettingsData } from '../types' import { fiatToSiacoin, toHastings } from '@siafoundation/units' import { UseFormReturn } from 'react-hook-form' -import { - useAllowanceDerivedPricingForEnabledFields, - useForexExchangeRate, -} from '../useAllowanceDerivedPricing' +import { Categories, RecommendationItem, SettingsData } from '../types' +import { useAllowanceDerivedPricingForEnabledFields } from '../useAllowanceDerivedPricing' +import { useFormExchangeRate } from '../useFormExchangeRate' import { PriceWithRedundancyTip, fitPriceToCurrentAllowanceTipContent, @@ -92,25 +89,23 @@ export function MaxStoragePricePinnedTips({ const derived = useAllowanceDerivedPricingForEnabledFields({ form, }) - const exchangeRate = useForexExchangeRate({ - form, - }) + const { rate } = useFormExchangeRate(form) const maxStoragePriceTBMonthPinned = form.watch( 'maxStoragePriceTBMonthPinned' ) const currentPriceInSiacoin = - maxStoragePriceTBMonthPinned && exchangeRate - ? fiatToSiacoin(maxStoragePriceTBMonthPinned, exchangeRate) + maxStoragePriceTBMonthPinned && rate + ? fiatToSiacoin(maxStoragePriceTBMonthPinned, rate) : null const derivedPriceInSiacoin = - derived?.maxStoragePriceTBMonthPinned && exchangeRate - ? fiatToSiacoin(derived.maxStoragePriceTBMonthPinned, exchangeRate) + derived?.maxStoragePriceTBMonthPinned && rate + ? fiatToSiacoin(derived.maxStoragePriceTBMonthPinned, rate) : null const recommendationInFiat = recommendations?.maxStoragePriceTBMonthPinned?.targetValue const recommendationInSiacoin = - recommendationInFiat && exchangeRate - ? fiatToSiacoin(recommendationInFiat, exchangeRate) + recommendationInFiat && rate + ? fiatToSiacoin(recommendationInFiat, rate) : null return ( diff --git a/apps/renterd/contexts/config/fieldTips/MaxUploadPrice.tsx b/apps/renterd/contexts/config/fieldTips/MaxUploadPrice.tsx index 339f69aa4..98b25558e 100644 --- a/apps/renterd/contexts/config/fieldTips/MaxUploadPrice.tsx +++ b/apps/renterd/contexts/config/fieldTips/MaxUploadPrice.tsx @@ -3,14 +3,11 @@ import { TipNumber, formSetField, } from '@siafoundation/design-system' -import React from 'react' -import { Categories, RecommendationItem, SettingsData } from '../types' import { fiatToSiacoin, toHastings } from '@siafoundation/units' import { UseFormReturn } from 'react-hook-form' -import { - useAllowanceDerivedPricingForEnabledFields, - useForexExchangeRate, -} from '../useAllowanceDerivedPricing' +import { Categories, RecommendationItem, SettingsData } from '../types' +import { useAllowanceDerivedPricingForEnabledFields } from '../useAllowanceDerivedPricing' +import { useFormExchangeRate } from '../useFormExchangeRate' import { PriceWithRedundancyTip, fitPriceToCurrentAllowanceTipContent, @@ -91,23 +88,21 @@ export function MaxUploadPricePinnedTips({ const derived = useAllowanceDerivedPricingForEnabledFields({ form, }) - const exchangeRate = useForexExchangeRate({ - form, - }) + const { rate } = useFormExchangeRate(form) const maxUploadPriceTBPinned = form.watch('maxUploadPriceTBPinned') const currentPriceInSiacoin = - maxUploadPriceTBPinned && exchangeRate - ? fiatToSiacoin(maxUploadPriceTBPinned, exchangeRate) + maxUploadPriceTBPinned && rate + ? fiatToSiacoin(maxUploadPriceTBPinned, rate) : null const derivedPriceInSiacoin = - derived?.maxUploadPriceTBPinned && exchangeRate - ? fiatToSiacoin(derived.maxUploadPriceTBPinned, exchangeRate) + derived?.maxUploadPriceTBPinned && rate + ? fiatToSiacoin(derived.maxUploadPriceTBPinned, rate) : null const recommendationInFiat = recommendations?.maxUploadPriceTBPinned?.targetValue const recommendationInSiacoin = - recommendationInFiat && exchangeRate - ? fiatToSiacoin(recommendationInFiat, exchangeRate) + recommendationInFiat && rate + ? fiatToSiacoin(recommendationInFiat, rate) : null return ( <> diff --git a/apps/renterd/contexts/config/fields.tsx b/apps/renterd/contexts/config/fields.tsx index 451737898..064da384b 100644 --- a/apps/renterd/contexts/config/fields.tsx +++ b/apps/renterd/contexts/config/fields.tsx @@ -2,8 +2,6 @@ import { Code, ConfigFields, - Link, - Text, toFixedMaxString, } from '@siafoundation/design-system' import { hoursInDays, secondsInMinutes } from '@siafoundation/units' @@ -60,7 +58,6 @@ type GetFields = { validationContext: { isAutopilotEnabled: boolean configViewMode: ConfigViewMode - pinningEnabled: boolean } } @@ -156,22 +153,14 @@ export function getFields({ category: 'storage', validation: { validate: { - required: requiredIfPinningEnabled( - validationContext, - (value: BigNumber, values) => { - if (!values.shouldPinAllowance) { - return true - } - return !!value || 'required' - } - ), + required: requiredIfPinningEnabled('shouldPinAllowance'), currency: requiredIfPinningEnabled( - validationContext, + 'shouldPinAllowance', (_, values) => !!values.pinnedCurrency || 'must select a pinned currency' ), range: requiredIfPinningEnabled( - validationContext, + 'shouldPinAllowance', (value: BigNumber, values) => !values.shouldPinAllowance || value?.gt(0) || @@ -467,7 +456,7 @@ export function getFields({ validation: { validate: { required: requiredIfPinningEnabled( - validationContext, + 'shouldPinMaxStoragePrice', (value: BigNumber, values) => { if (!values.shouldPinMaxStoragePrice) { return true @@ -475,18 +464,13 @@ export function getFields({ return !!value || 'required' } ), - disabled: (value: BigNumber, values) => { - if (!values.pinningEnabled && values.shouldPinMaxStoragePrice) { - return 'please enable pinning and select a currency' - } - }, currency: requiredIfPinningEnabled( - validationContext, + 'shouldPinMaxStoragePrice', (_, values) => !!values.pinnedCurrency || 'must select a pinned currency' ), range: requiredIfPinningEnabled( - validationContext, + 'shouldPinMaxStoragePrice', (value: BigNumber, values) => !values.shouldPinMaxStoragePrice || value?.gt(0) || @@ -546,7 +530,7 @@ export function getFields({ validation: { validate: { required: requiredIfPinningEnabled( - validationContext, + 'shouldPinMaxUploadPrice', (value: BigNumber, values) => { if (!values.shouldPinMaxUploadPrice) { return true @@ -555,12 +539,12 @@ export function getFields({ } ), currency: requiredIfPinningEnabled( - validationContext, + 'shouldPinMaxUploadPrice', (_, values) => !!values.pinnedCurrency || 'must select a pinned currency' ), range: requiredIfPinningEnabled( - validationContext, + 'shouldPinMaxUploadPrice', (value: BigNumber, values) => !values.shouldPinMaxUploadPrice || value?.gt(0) || @@ -620,7 +604,7 @@ export function getFields({ validation: { validate: { required: requiredIfPinningEnabled( - validationContext, + 'shouldPinMaxDownloadPrice', (value: BigNumber, values) => { if (!values.shouldPinMaxDownloadPrice) { return true @@ -629,12 +613,12 @@ export function getFields({ } ), currency: requiredIfPinningEnabled( - validationContext, + 'shouldPinMaxDownloadPrice', (_, values) => !!values.pinnedCurrency || 'must select a pinned currency' ), range: requiredIfPinningEnabled( - validationContext, + 'shouldPinMaxDownloadPrice', (value: BigNumber, values) => !values.shouldPinMaxDownloadPrice || value?.gt(0) || @@ -900,14 +884,6 @@ export function getFields({ }, // pinning - pinningEnabled: { - category: 'pinning', - type: 'boolean', - title: 'Pinning', - description: - 'Pinning allows you to set a fixed fiat price for each supported field. Pinning is available for allowance and maximum price fields.', - validation: {}, - }, pinnedCurrency: { category: 'pinning', title: 'Pinned currency', @@ -924,9 +900,7 @@ export function getFields({ value: string }[], validation: { - validate: { - required: requiredIfPinningEnabled(validationContext), - }, + required: 'required', }, }, pinnedThreshold: { @@ -944,61 +918,14 @@ export function getFields({ changing prices too often. ), - // hidden: configViewMode === 'basic', - validation: { - validate: { - required: requiredIfPinningEnabled(validationContext), - max: requiredIfPinningEnabled( - validationContext, - (value) => - new BigNumber(value as BigNumber).lte(100) || - `must be at most 100%` - ), - min: requiredIfPinningEnabled( - validationContext, - (value) => - new BigNumber(value as BigNumber).gte(0) || `must be at least 0%` - ), - }, - }, - }, - forexEndpointURL: { - category: 'pinning', - type: 'text', - title: 'Forex endpoint URL', - placeholder: 'https://api.siascan.com/exchange-rate/siacoin', - suggestion: 'https://api.siascan.com/exchange-rate/siacoin', - suggestionTip: 'SiaScan provides an exchange rate endpoint.', - description: ( - - - Endpoint for fetching exchange rates. The endpoint URL should allow - appending a currency code to the URL and the endpoint response - should be a single number representing the exchange rate. For - example, the SiaScan exchange rate endpoint: - - - https://api.siascan.com/exchange-rate/siacoin - - - https://api.siascan.com/exchange-rate/siacoin/usd - - - https://api.siascan.com/exchange-rate/siacoin/jpy - - - ), validation: { + required: 'required', validate: { - required: requiredIfPinningEnabled(validationContext), + max: (value) => + new BigNumber(value as BigNumber).lte(100) || + `must be at most 100%`, + min: (value) => + new BigNumber(value as BigNumber).gte(0) || `must be at least 0%`, }, }, }, @@ -1054,13 +981,15 @@ function requiredIfAutopilotAndAdvanced( } function requiredIfPinningEnabled( - context: { - pinningEnabled: boolean - }, + field: + | 'shouldPinAllowance' + | 'shouldPinMaxStoragePrice' + | 'shouldPinMaxUploadPrice' + | 'shouldPinMaxDownloadPrice', method?: (value: unknown, values: Values) => string | boolean ) { return (value: unknown, values: Values) => { - if (context.pinningEnabled) { + if (values[field]) { if (method) { return method(value, values) } diff --git a/apps/renterd/contexts/config/index.tsx b/apps/renterd/contexts/config/index.tsx index ce59aa9d0..7c07447d9 100644 --- a/apps/renterd/contexts/config/index.tsx +++ b/apps/renterd/contexts/config/index.tsx @@ -23,11 +23,9 @@ export function useConfigMain() { const { autopilotState, autopilot, - contractSet, gouging, - redundancy, - uploadPacking, - pricePinning, + pinned, + upload, averages, shouldSyncDefaultContractSet, setShouldSyncDefaultContractSet, @@ -45,25 +43,17 @@ export function useConfigMain() { data: autopilot.data, error: autopilot.error, }, - contractSet: { - data: contractSet.data, - error: contractSet.error, - }, - uploadPacking: { - data: uploadPacking.data, - error: uploadPacking.error, - }, gouging: { data: gouging.data, error: gouging.error, }, - redundancy: { - data: redundancy.data, - error: redundancy.error, + pinned: { + data: pinned.data, + error: pinned.error, }, - pricePinning: { - data: pricePinning.data, - error: pricePinning.error, + upload: { + data: upload.data, + error: upload.error, }, averages: { data: averages.data, @@ -80,16 +70,12 @@ export function useConfigMain() { autopilotState.error, autopilot.data, autopilot.error, - contractSet.data, - contractSet.error, - uploadPacking.data, - uploadPacking.error, gouging.data, gouging.error, - redundancy.data, - redundancy.error, - pricePinning.data, - pricePinning.error, + pinned.data, + pinned.error, + upload.data, + upload.error, averages.data, averages.error, appSettings.settings.siaCentral, @@ -111,14 +97,13 @@ export function useConfigMain() { return null } return transformDown({ + autopilotID: resources.autopilotState.data?.id, hasBeenConfigured: resources.autopilotState.data?.configured, autopilot: resources.autopilot.data, - contractSet: resources.contractSet.data, - uploadPacking: resources.uploadPacking.data, gouging: resources.gouging.data, + pinned: resources.pinned.data, + upload: resources.upload.data, averages: resources.averages.data, - redundancy: resources.redundancy.data, - pricePinning: resources.pricePinning.data, }) }, [resources]) @@ -132,25 +117,22 @@ export function useConfigMain() { // these do not seem to throw on errors, just return undefined const _autopilotState = await autopilotState.mutate() const _autopilot = isAutopilotEnabled ? await autopilot.mutate() : undefined - const _contractSet = await contractSet.mutate() const _gouging = await gouging.mutate() - const _redundancy = await redundancy.mutate() - const _uploadPacking = await uploadPacking.mutate() - const _pricePinning = await pricePinning.mutate() - if (!gouging || !redundancy) { + const _pinned = await pinned.mutate() + const _upload = await upload.mutate() + if (!_autopilotState || !_gouging || !_upload || !_pinned) { triggerErrorToast({ title: 'Error fetching settings' }) return null } form.reset( transformDown({ + autopilotID: _autopilotState.id, hasBeenConfigured: _autopilotState.configured, autopilot: _autopilot, - contractSet: _contractSet, - uploadPacking: _uploadPacking, gouging: _gouging, + pinned: _pinned, + upload: _upload, averages: averages.data, - redundancy: _redundancy, - pricePinning: _pricePinning, }) ) }, [ @@ -158,11 +140,9 @@ export function useConfigMain() { autopilotState, isAutopilotEnabled, autopilot, - contractSet, gouging, - uploadPacking, - redundancy, - pricePinning, + pinned, + upload, averages.data, ]) diff --git a/apps/renterd/contexts/config/resources.ts b/apps/renterd/contexts/config/resources.ts index 74ff75eae..8e2175b84 100644 --- a/apps/renterd/contexts/config/resources.ts +++ b/apps/renterd/contexts/config/resources.ts @@ -2,17 +2,14 @@ import { SWRError } from '@siafoundation/react-core' import { AutopilotConfig, AutopilotState, - ContractSetSettings, - GougingSettings, - PricePinSettings, - RedundancySettings, - UploadPackingSettings, + SettingsGouging, + SettingsPinned, + SettingsUpload, } from '@siafoundation/renterd-types' import { SiaCentralHostsNetworkAveragesResponse } from '@siafoundation/sia-central-types' import BigNumber from 'bignumber.js' -import { TBToBytes } from '@siafoundation/units' -export type Resources = { +export type ResourcesMaybeLoaded = { autopilotState: { data?: AutopilotState error?: SWRError @@ -21,26 +18,50 @@ export type Resources = { data?: AutopilotConfig error?: SWRError } - contractSet: { - data?: ContractSetSettings + gouging: { + data?: SettingsGouging error?: SWRError } - uploadPacking: { - data?: UploadPackingSettings + pinned: { + data?: SettingsPinned error?: SWRError } - gouging: { - data?: GougingSettings + upload: { + data?: SettingsUpload error?: SWRError } - redundancy: { - data?: RedundancySettings + averages: { + data?: SiaCentralHostsNetworkAveragesResponse error?: SWRError } - pricePinning: { - data?: PricePinSettings + appSettings: { + settings: { + siaCentral: boolean + } + } +} + +export type ResourcesRequiredLoaded = { + autopilotState: { + data: AutopilotState + error?: undefined + } + autopilot: { + data?: AutopilotConfig error?: SWRError } + gouging: { + data: SettingsGouging + error?: undefined + } + pinned: { + data: SettingsPinned + error?: undefined + } + upload: { + data: SettingsUpload + error?: undefined + } averages: { data?: SiaCentralHostsNetworkAveragesResponse error?: SWRError @@ -55,26 +76,26 @@ export type Resources = { export function checkIfAllResourcesLoaded({ autopilotState, autopilot, - contractSet, - uploadPacking, gouging, - redundancy, - pricePinning, + pinned, + upload, averages, appSettings, -}: Resources) { +}: ResourcesMaybeLoaded) { return !!( // these settings have initial daemon values ( autopilotState.data && - redundancy.data && - uploadPacking.data && + !autopilotState.error && gouging.data && - pricePinning.data && + !gouging.error && + pinned.data && + !pinned.error && + upload.data && + !upload.error && // these settings are undefined and will error // until the user sets them (autopilot.data || autopilot.error) && - (contractSet.data || contractSet.error) && // other data dependencies (!appSettings.settings.siaCentral || averages.data) ) @@ -82,13 +103,13 @@ export function checkIfAllResourcesLoaded({ } export function checkIfAnyResourcesErrored({ - uploadPacking, gouging, - redundancy, -}: Resources) { + pinned, + upload, +}: ResourcesMaybeLoaded) { return !!( // these settings have initial daemon values - (redundancy.error || uploadPacking.error || gouging.error) + (gouging.error || pinned.error || upload.error) ) } @@ -97,7 +118,7 @@ export function firstTimeGougingData({ averages, hasBeenConfigured, }: { - gouging: GougingSettings + gouging: SettingsGouging averages?: { settings: { download_price: string @@ -106,7 +127,7 @@ export function firstTimeGougingData({ } } hasBeenConfigured: boolean -}): GougingSettings { +}): SettingsGouging { // already configured, the user has changed the defaults if (hasBeenConfigured) { return gouging diff --git a/apps/renterd/contexts/config/transform.spec.ts b/apps/renterd/contexts/config/transform.spec.ts index 8c150d0a6..d1bbd2121 100644 --- a/apps/renterd/contexts/config/transform.spec.ts +++ b/apps/renterd/contexts/config/transform.spec.ts @@ -3,10 +3,9 @@ import { transformDown } from './transformDown' import { transformUp, transformUpAutopilot, - transformUpContractSet, transformUpGouging, - transformUpPricePinning, - transformUpRedundancy, + transformUpPinned, + transformUpUpload, } from './transformUp' import { SettingsData } from './types' import { @@ -17,21 +16,22 @@ import { valuePerMonthToPerPeriod, } from '@siafoundation/units' import { CurrencyId } from '@siafoundation/react-core' +import { + AutopilotConfig, + SettingsGouging, + SettingsPinned, + SettingsUpload, +} from '@siafoundation/renterd-types' +import { merge } from '@technically/lodash' describe('tansforms', () => { describe('down', () => { it('default', () => { - const { - autopilot, - contractSet, - uploadPacking, - gouging, - redundancy, - pricePinning, - } = buildAllResponses() + const { autopilot, gouging, pinned, upload } = buildAllResponses() expect( transformDown({ hasBeenConfigured: true, + autopilotID: 'autopilot', autopilot: { ...autopilot, hosts: { @@ -39,11 +39,9 @@ describe('tansforms', () => { minProtocolVersion: null, }, }, - contractSet, - uploadPacking, gouging, - redundancy, - pricePinning, + pinned, + upload, }) ).toEqual({ autopilotContractSet: 'autopilot', @@ -83,23 +81,22 @@ describe('tansforms', () => { shouldPinMaxStoragePrice: false, pinnedCurrency: 'usd', pinnedThreshold: new BigNumber(10), - pinningEnabled: false, - forexEndpointURL: '', } as SettingsData) }) it('applies first time user overrides', () => { - const { gouging, redundancy, pricePinning } = buildAllResponses() + const { gouging, pinned, upload } = buildAllResponses() const values = transformDown({ hasBeenConfigured: false, autopilot: undefined, - contractSet: undefined, - uploadPacking: { - enabled: false, - }, + autopilotID: 'autopilot', + pinned, gouging, - redundancy, - pricePinning, + upload: merge(upload, { + packing: { + enabled: false, + }, + }), averages: { settings: { download_price: (4e24).toString(), @@ -116,17 +113,18 @@ describe('tansforms', () => { }) it('does not apply overrides if missing averages', () => { - const { gouging, redundancy, pricePinning } = buildAllResponses() + const { gouging, pinned, upload } = buildAllResponses() const values = transformDown({ hasBeenConfigured: false, autopilot: undefined, - contractSet: undefined, - uploadPacking: { - enabled: false, - }, + autopilotID: 'autopilot', gouging, - redundancy, - pricePinning, + pinned, + upload: merge(upload, { + packing: { + enabled: false, + }, + }), }) expect(values.maxUploadPriceTB).toEqual(new BigNumber('1000.232323')) expect(values.maxDownloadPriceTB).toEqual(new BigNumber('1004.31')) @@ -135,331 +133,339 @@ describe('tansforms', () => { }) describe('up', () => { - it('up autopilot', () => { - expect( - transformUpAutopilot( - 'mainnet', - { - autopilotContractSet: 'autopilot', - allowanceMonth: new BigNumber('6006'), - amountHosts: new BigNumber('51'), - periodWeeks: new BigNumber('6'), - renewWindowWeeks: new BigNumber('2.2301587301587302'), - downloadTBMonth: new BigNumber('0.785365448411428571428571428571'), - uploadTBMonth: new BigNumber('0.785714285714285714285714285714'), - storageTB: new BigNumber('1'), - prune: true, + describe('autopilot', () => { + it('up autopilot', () => { + expect( + transformUpAutopilot( + 'mainnet', + { + autopilotContractSet: 'autopilot', + allowanceMonth: new BigNumber('6006'), + amountHosts: new BigNumber('51'), + periodWeeks: new BigNumber('6'), + renewWindowWeeks: new BigNumber('2.2301587301587302'), + downloadTBMonth: new BigNumber( + '0.785365448411428571428571428571' + ), + uploadTBMonth: new BigNumber('0.785714285714285714285714285714'), + storageTB: new BigNumber('1'), + prune: true, + allowRedundantIPs: false, + maxDowntimeHours: new BigNumber('1440'), + maxConsecutiveScanFailures: new BigNumber('10'), + minProtocolVersion: '', + }, + undefined + ) + ).toEqual({ + hosts: { allowRedundantIPs: false, - maxDowntimeHours: new BigNumber('1440'), - maxConsecutiveScanFailures: new BigNumber('10'), - minProtocolVersion: '', + maxDowntimeHours: 1440, + maxConsecutiveScanFailures: 10, + scoreOverrides: null, + minProtocolVersion: '1.6.0', }, - undefined - ) - ).toEqual({ - hosts: { - allowRedundantIPs: false, - maxDowntimeHours: 1440, - maxConsecutiveScanFailures: 10, - scoreOverrides: null, - minProtocolVersion: '1.6.0', - }, - contracts: { - set: 'autopilot', - amount: 51, - allowance: '8408400000000000000000000000', - period: 6048, - renewWindow: 2248, - download: 1099511627776, - upload: 1100000000000, - storage: 1000000000000, - prune: true, - }, - }) - }) - it('up autopilot accepts unknown values', () => { - expect( - transformUpAutopilot( - 'mainnet', - { - autopilotContractSet: 'autopilot', - allowanceMonth: new BigNumber('6006'), - amountHosts: new BigNumber('51'), - periodWeeks: new BigNumber('6'), - renewWindowWeeks: new BigNumber('2.2301587301587302'), - downloadTBMonth: new BigNumber('0.785365448411428571428571428571'), - uploadTBMonth: new BigNumber('0.785714285714285714285714285714'), - storageTB: new BigNumber('1'), + contracts: { + set: 'autopilot', + amount: 51, + allowance: '8408400000000000000000000000', + period: 6048, + renewWindow: 2248, + download: 1099511627776, + upload: 1100000000000, + storage: 1000000000000, prune: true, + }, + }) + }) + it('up autopilot accepts unknown values', () => { + expect( + transformUpAutopilot( + 'mainnet', + { + autopilotContractSet: 'autopilot', + allowanceMonth: new BigNumber('6006'), + amountHosts: new BigNumber('51'), + periodWeeks: new BigNumber('6'), + renewWindowWeeks: new BigNumber('2.2301587301587302'), + downloadTBMonth: new BigNumber( + '0.785365448411428571428571428571' + ), + uploadTBMonth: new BigNumber('0.785714285714285714285714285714'), + storageTB: new BigNumber('1'), + prune: true, + allowRedundantIPs: false, + maxDowntimeHours: new BigNumber('1440'), + maxConsecutiveScanFailures: new BigNumber('10'), + minProtocolVersion: '1.7.0', + }, + { + foobar1: 'value', + contracts: { + foobar: 'value', + period: 7777, + }, + hosts: { + foobar: 'value', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any + ) + ).toEqual({ + foobar1: 'value', + hosts: { + foobar: 'value', allowRedundantIPs: false, - maxDowntimeHours: new BigNumber('1440'), - maxConsecutiveScanFailures: new BigNumber('10'), + maxDowntimeHours: 1440, + maxConsecutiveScanFailures: 10, + scoreOverrides: null, minProtocolVersion: '1.7.0', }, - { - foobar1: 'value', - contracts: { - foobar: 'value', - period: 7777, - }, - hosts: { - foobar: 'value', - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any - ) - ).toEqual({ - foobar1: 'value', - hosts: { - foobar: 'value', - allowRedundantIPs: false, - maxDowntimeHours: 1440, - maxConsecutiveScanFailures: 10, - scoreOverrides: null, - minProtocolVersion: '1.7.0', - }, - contracts: { - foobar: 'value', - set: 'autopilot', - amount: 51, - allowance: '8408400000000000000000000000', - period: 6048, - renewWindow: 2248, - download: 1099511627776, - upload: 1100000000000, - storage: 1000000000000, - prune: true, - }, - }) - }) - it('uses testnet defaults', () => { - expect( - transformUpAutopilot( - 'zen', - { - autopilotContractSet: 'autopilot', - allowanceMonth: new BigNumber('6006'), - amountHosts: undefined, - periodWeeks: new BigNumber('6'), - renewWindowWeeks: new BigNumber('2.2301587301587302'), - downloadTBMonth: new BigNumber('0.785365448411428571428571428571'), - uploadTBMonth: new BigNumber('0.785714285714285714285714285714'), - storageTB: new BigNumber('1'), + contracts: { + foobar: 'value', + set: 'autopilot', + amount: 51, + allowance: '8408400000000000000000000000', + period: 6048, + renewWindow: 2248, + download: 1099511627776, + upload: 1100000000000, + storage: 1000000000000, prune: true, + }, + }) + }) + it('uses testnet defaults', () => { + expect( + transformUpAutopilot( + 'zen', + { + autopilotContractSet: 'autopilot', + allowanceMonth: new BigNumber('6006'), + amountHosts: undefined, + periodWeeks: new BigNumber('6'), + renewWindowWeeks: new BigNumber('2.2301587301587302'), + downloadTBMonth: new BigNumber( + '0.785365448411428571428571428571' + ), + uploadTBMonth: new BigNumber('0.785714285714285714285714285714'), + storageTB: new BigNumber('1'), + prune: true, + allowRedundantIPs: false, + maxDowntimeHours: new BigNumber('1440'), + maxConsecutiveScanFailures: new BigNumber('10'), + minProtocolVersion: '1.7.0', + }, + { + contracts: { + period: 7777, + }, + hosts: {}, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any + ) + ).toEqual({ + hosts: { allowRedundantIPs: false, - maxDowntimeHours: new BigNumber('1440'), - maxConsecutiveScanFailures: new BigNumber('10'), + maxDowntimeHours: 1440, + maxConsecutiveScanFailures: 10, + scoreOverrides: null, minProtocolVersion: '1.7.0', }, - { - contracts: { - period: 7777, - }, - hosts: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any - ) - ).toEqual({ - hosts: { - allowRedundantIPs: false, - maxDowntimeHours: 1440, - maxConsecutiveScanFailures: 10, - scoreOverrides: null, - minProtocolVersion: '1.7.0', - }, - contracts: { - set: 'autopilot', - amount: 12, - allowance: '8408400000000000000000000000', - period: 6048, - renewWindow: 2248, - download: 1099511627776, - upload: 1100000000000, - storage: 1000000000000, - prune: true, - }, - }) - }) - - it('up contractset', () => { - expect( - transformUpContractSet( - { - defaultContractSet: 'myset', + contracts: { + set: 'autopilot', + amount: 12, + allowance: '8408400000000000000000000000', + period: 6048, + renewWindow: 2248, + download: 1099511627776, + upload: 1100000000000, + storage: 1000000000000, + prune: true, }, - { - default: '77777777777', - foobar: 'value', - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any - ) - ).toEqual({ - default: 'myset', - foobar: 'value', + }) }) }) - it('up gouging', () => { - expect( - transformUpGouging( - { - autopilotContractSet: 'autopilot', - allowanceMonth: new BigNumber('6006'), - amountHosts: new BigNumber('51'), - periodWeeks: new BigNumber('6'), - renewWindowWeeks: new BigNumber('2.2301587301587302'), - downloadTBMonth: new BigNumber('0.785365448411428571428571428571'), - uploadTBMonth: new BigNumber('0.785714285714285714285714285714'), - storageTB: new BigNumber('1'), - prune: true, - allowRedundantIPs: false, - maxDowntimeHours: new BigNumber('1440'), - maxConsecutiveScanFailures: new BigNumber('10'), - minProtocolVersion: '1.7.0', - defaultContractSet: 'myset', - uploadPackingEnabled: false, - hostBlockHeightLeeway: new BigNumber(4), - maxContractPrice: new BigNumber('20'), - maxDownloadPriceTB: new BigNumber('1004.31'), - maxRPCPriceMillion: new BigNumber('99970619'), - maxStoragePriceTBMonth: new BigNumber('909.494702'), - maxUploadPriceTB: new BigNumber('1000.232323'), - minAccountExpiryDays: new BigNumber(1), - minMaxEphemeralAccountBalance: new BigNumber('1'), - minPriceTableValidityMinutes: new BigNumber(5), - minShards: new BigNumber(10), - totalShards: new BigNumber(30), - migrationSurchargeMultiplier: new BigNumber(10), - allowanceMonthPinned: new BigNumber('0'), - maxStoragePriceTBMonthPinned: new BigNumber('0'), - maxDownloadPriceTBPinned: new BigNumber('0'), - maxUploadPriceTBPinned: new BigNumber('0'), - shouldPinAllowance: false, - shouldPinMaxDownloadPrice: false, - shouldPinMaxUploadPrice: false, - shouldPinMaxStoragePrice: false, - pinnedCurrency: 'usd', - pinnedThreshold: new BigNumber(0), - pinningEnabled: false, - forexEndpointURL: '', - }, - { - maxStoragePrice: '77777777777', - foobar: 'value', - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any - ) - ).toEqual({ - foobar: 'value', - hostBlockHeightLeeway: 4, - maxContractPrice: '20000000000000000000000000', - maxDownloadPrice: '1004310000000000', - maxRPCPrice: '99970619000000000000000000', - maxStoragePrice: '210531181019', - maxUploadPrice: '1000232323000000', - minAccountExpiry: 86400000000000, - minMaxEphemeralAccountBalance: '1000000000000000000000000', - minPriceTableValidity: 300000000000, - migrationSurchargeMultiplier: 10, + describe('up gouging', () => { + it('up gouging', () => { + expect( + transformUpGouging( + { + autopilotContractSet: 'autopilot', + allowanceMonth: new BigNumber('6006'), + amountHosts: new BigNumber('51'), + periodWeeks: new BigNumber('6'), + renewWindowWeeks: new BigNumber('2.2301587301587302'), + downloadTBMonth: new BigNumber( + '0.785365448411428571428571428571' + ), + uploadTBMonth: new BigNumber('0.785714285714285714285714285714'), + storageTB: new BigNumber('1'), + prune: true, + allowRedundantIPs: false, + maxDowntimeHours: new BigNumber('1440'), + maxConsecutiveScanFailures: new BigNumber('10'), + minProtocolVersion: '1.7.0', + defaultContractSet: 'myset', + uploadPackingEnabled: false, + hostBlockHeightLeeway: new BigNumber(4), + maxContractPrice: new BigNumber('20'), + maxDownloadPriceTB: new BigNumber('1004.31'), + maxRPCPriceMillion: new BigNumber('99970619'), + maxStoragePriceTBMonth: new BigNumber('909.494702'), + maxUploadPriceTB: new BigNumber('1000.232323'), + minAccountExpiryDays: new BigNumber(1), + minMaxEphemeralAccountBalance: new BigNumber('1'), + minPriceTableValidityMinutes: new BigNumber(5), + minShards: new BigNumber(10), + totalShards: new BigNumber(30), + migrationSurchargeMultiplier: new BigNumber(10), + allowanceMonthPinned: new BigNumber('0'), + maxStoragePriceTBMonthPinned: new BigNumber('0'), + maxDownloadPriceTBPinned: new BigNumber('0'), + maxUploadPriceTBPinned: new BigNumber('0'), + shouldPinAllowance: false, + shouldPinMaxDownloadPrice: false, + shouldPinMaxUploadPrice: false, + shouldPinMaxStoragePrice: false, + pinnedCurrency: 'usd', + pinnedThreshold: new BigNumber(0), + }, + { + maxStoragePrice: '77777777777', + foobar: 'value', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any + ) + ).toEqual({ + foobar: 'value', + hostBlockHeightLeeway: 4, + maxContractPrice: '20000000000000000000000000', + maxDownloadPrice: '1004310000000000', + maxRPCPrice: '99970619000000000000000000', + maxStoragePrice: '210531181019', + maxUploadPrice: '1000232323000000', + minAccountExpiry: 86400000000000, + minMaxEphemeralAccountBalance: '1000000000000000000000000', + minPriceTableValidity: 300000000000, + migrationSurchargeMultiplier: 10, + }) }) }) - it('up redundancy', () => { - expect( - transformUpRedundancy( - { - minShards: new BigNumber(10), - totalShards: new BigNumber(30), + describe('up pinned', () => { + it('up pinned', () => { + expect( + transformUpPinned( + { + pinnedCurrency: 'usd', + pinnedThreshold: new BigNumber(1), + shouldPinAllowance: true, + allowanceMonthPinned: new BigNumber('1000'), + shouldPinMaxStoragePrice: true, + maxStoragePriceTBMonthPinned: new BigNumber('2000'), + shouldPinMaxUploadPrice: true, + maxUploadPriceTBPinned: new BigNumber('1'), + shouldPinMaxDownloadPrice: true, + maxDownloadPriceTBPinned: new BigNumber('1.1'), + periodWeeks: new BigNumber('6'), + }, + { + otherNewValue: '77777777777', + foobar: 'value', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + 'autopilot' + ) + ).toEqual({ + currency: 'usd' as CurrencyId, + threshold: 0.01, + autopilots: { + autopilot: { + allowance: { + pinned: true, + value: 1400, + }, + }, }, - { - minShards: 77, - foobar: 'value', - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any - ) - ).toEqual({ - foobar: 'value', - minShards: 10, - totalShards: 30, + gougingSettingsPins: { + maxStorage: { + pinned: true, + value: 2000, + }, + maxDownload: { + pinned: true, + value: 1.1, + }, + maxUpload: { + pinned: true, + value: 1, + }, + }, + otherNewValue: '77777777777', + foobar: 'value', + }) }) }) + }) - it('up price pinning', () => { + describe('up upload', () => { + it('up upload', () => { + const { upload } = buildAllResponses() expect( - transformUpPricePinning( + transformUpUpload( { - pinningEnabled: true, - pinnedCurrency: 'usd', - forexEndpointURL: '', - pinnedThreshold: new BigNumber(1), - shouldPinAllowance: true, - allowanceMonthPinned: new BigNumber('1000'), - shouldPinMaxStoragePrice: true, - maxStoragePriceTBMonthPinned: new BigNumber('2000'), - shouldPinMaxUploadPrice: true, - maxUploadPriceTBPinned: new BigNumber('1'), - shouldPinMaxDownloadPrice: true, - maxDownloadPriceTBPinned: new BigNumber('1.1'), - periodWeeks: new BigNumber('6'), + defaultContractSet: 'myset', + uploadPackingEnabled: false, + minShards: new BigNumber(11), + totalShards: new BigNumber(30), }, { - otherNewValue: '77777777777', - foobar: 'value', - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any + ...upload, + defaultContractSet: '77777777777', + extraUploadValue: 'value', + packing: { + ...upload.packing, + extraPackingValue: 'value', + }, + redundancy: { + ...upload.redundancy, + extraRedundancyValue: 'value', + }, + } as SettingsUpload ) ).toEqual({ - enabled: true, - currency: 'usd' as CurrencyId, - forexEndpointURL: '', - threshold: 0.01, - autopilots: { - autopilot: { - allowance: { - pinned: true, - value: 1400, - }, - }, + defaultContractSet: 'myset', + packing: { + ...upload.packing, + enabled: false, + extraPackingValue: 'value', }, - gougingSettingsPins: { - maxStorage: { - pinned: true, - value: 2000, - }, - maxDownload: { - pinned: true, - value: 1.1, - }, - maxUpload: { - pinned: true, - value: 1, - }, + redundancy: { + ...upload.redundancy, + minShards: 11, + totalShards: 30, + extraRedundancyValue: 'value', }, - otherNewValue: '77777777777', - foobar: 'value', + extraUploadValue: 'value', }) }) }) - describe('down up down', () => { + describe('down up down - ensure converting back and forth results in the same values', () => { it('converts ap download down up down', () => { - const { - autopilot, - contractSet, - uploadPacking, - gouging, - redundancy, - pricePinning, - } = buildAllResponses() + const { autopilotState, autopilot, gouging, pinned, upload } = + buildAllResponses() const transformUpMocks = { resources: { - autopilotState: {}, + autopilotState: { + data: autopilotState, + }, autopilot: {}, - contractSet: {}, - uploadPacking: {}, - gouging: {}, - redundancy: {}, - pricePinning: {}, + gouging: { data: gouging }, + pinned: { data: pinned }, + upload: { data: upload }, averages: {}, appSettings: { settings: { @@ -470,10 +476,12 @@ describe('tansforms', () => { renterdState: { network: 'mainnet' as const, }, + autopilotID: 'autopilot', isAutopilotEnabled: true, } const down = transformDown({ hasBeenConfigured: true, + autopilotID: 'autopilot', autopilot: { ...autopilot, contracts: { @@ -483,11 +491,9 @@ describe('tansforms', () => { period: 4244, }, }, - contractSet, - uploadPacking, gouging, - redundancy, - pricePinning, + pinned, + upload, }) // Coming down, some fields are a little different due to rounding. expect(down.downloadTBMonth).toEqual(new BigNumber('92.72')) @@ -498,6 +504,7 @@ describe('tansforms', () => { }) const downUpDown = transformDown({ hasBeenConfigured: true, + autopilotID: 'autopilot', ...downUp.payloads, }) @@ -511,43 +518,68 @@ describe('tansforms', () => { }) }) - it('ap download', () => { - const valuePerPeriod = new BigNumber(91085125718831) - const periodBlocks = new BigNumber(4244) - const valuePerMonth = valuePerPeriodToPerMonth( - valuePerPeriod, - periodBlocks.toNumber() - ) - expect(valuePerMonth).toEqual( - new BigNumber('92716244841034.38265786993402450518378887952') - ) - const periodWeeks = new BigNumber(blocksToWeeks(periodBlocks.toNumber())) - expect( - valuePerMonthToPerPeriod(valuePerMonth, periodWeeks).toFixed(0) - ).toEqual(valuePerPeriod.toString()) - }) + describe('specific unit conversions work back and forth', () => { + it('period -> month -> period', () => { + const valuePerPeriod = new BigNumber(91085125718831) + const periodBlocks = new BigNumber(4244) + const valuePerMonth = valuePerPeriodToPerMonth( + valuePerPeriod, + periodBlocks.toNumber() + ) + expect(valuePerMonth).toEqual( + new BigNumber('92716244841034.38265786993402450518378887952') + ) + const periodWeeks = new BigNumber(blocksToWeeks(periodBlocks.toNumber())) + expect( + valuePerMonthToPerPeriod(valuePerMonth, periodWeeks).toFixed(0) + ).toEqual(valuePerPeriod.toString()) + }) - it('month -> period', () => { - const valuePerMonth = new BigNumber(87908469486735) - const periodWeeks = new BigNumber(30).div(7) - expect(valuePerMonthToPerPeriod(valuePerMonth, periodWeeks)).toEqual( - valuePerMonth - ) - }) - it('period <- month', () => { - const valuePerMonth = new BigNumber(30) - const periodWeeks = new BigNumber(30).div(7) - const valuePerPeriod = valuePerMonthToPerPeriod(valuePerMonth, periodWeeks) + it('month -> period', () => { + const valuePerMonth = new BigNumber(87908469486735) + const periodWeeks = new BigNumber(30).div(7) + expect(valuePerMonthToPerPeriod(valuePerMonth, periodWeeks)).toEqual( + valuePerMonth + ) + }) + it('period <- month', () => { + const valuePerMonth = new BigNumber(30) + const periodWeeks = new BigNumber(30).div(7) + const valuePerPeriod = valuePerMonthToPerPeriod( + valuePerMonth, + periodWeeks + ) - const periodBlocks = weeksToBlocks(periodWeeks.toNumber()) - expect( - valuePerPeriodToPerMonth(valuePerPeriod, periodBlocks).toFixed(0) - ).toEqual('30') + const periodBlocks = weeksToBlocks(periodWeeks.toNumber()) + expect( + valuePerPeriodToPerMonth(valuePerPeriod, periodBlocks).toFixed(0) + ).toEqual('30') + }) }) }) function buildAllResponses() { return { + autopilotState: { + id: 'autopilot', + configured: true, + migrating: true, + migratingLastStart: new Date().toISOString(), + scanning: true, + scanningLastStart: new Date().toISOString(), + synced: true, + uptimeMS: '333', + network: 'mainnet' as const, + version: '0.0.0', + commit: 'commit', + OS: 'os', + buildTime: new Date().getTime(), + explorer: { + enabled: true, + url: 'https://api.siascan.com', + }, + startTime: new Date().getTime(), + }, autopilot: { hosts: { allowRedundantIPs: false, @@ -567,9 +599,7 @@ function buildAllResponses() { storage: 1000000000000, prune: true, }, - }, - contractSet: { default: 'myset' }, - uploadPacking: { enabled: true }, + } as AutopilotConfig, gouging: { hostBlockHeightLeeway: 4, maxContractPrice: '20000000000000000000000000', @@ -581,16 +611,19 @@ function buildAllResponses() { minMaxEphemeralAccountBalance: '1000000000000000000000000', minPriceTableValidity: 300000000000, migrationSurchargeMultiplier: 10, - }, - redundancy: { - minShards: 10, - totalShards: 30, - }, - pricePinning: { - enabled: false, + } as SettingsGouging, + pinned: { currency: 'usd' as CurrencyId, - forexEndpointURL: '', threshold: 0.1, + autopilots: { + // The default autopilot named 'autopilot'. + autopilot: { + allowance: { + pinned: false, + value: 100, + }, + }, + }, gougingSettingsPins: { maxStorage: { pinned: false, @@ -605,15 +638,17 @@ function buildAllResponses() { value: 2, }, }, - autopilots: { - // The default autopilot named 'autopilot'. - autopilot: { - allowance: { - pinned: false, - value: 100, - }, - }, + } as SettingsPinned, + upload: { + defaultContractSet: 'myset', + packing: { + enabled: true, + slabBufferMaxSizeSoft: 1, }, - }, + redundancy: { + minShards: 10, + totalShards: 30, + }, + } as SettingsUpload, } } diff --git a/apps/renterd/contexts/config/transformDown.ts b/apps/renterd/contexts/config/transformDown.ts index 9c0f7b1de..049b31351 100644 --- a/apps/renterd/contexts/config/transformDown.ts +++ b/apps/renterd/contexts/config/transformDown.ts @@ -2,41 +2,36 @@ import { toFixedMaxBigNumber, toFixedMaxString, } from '@siafoundation/design-system' +import { currencyOptions } from '@siafoundation/react-core' import { AutopilotConfig, - ContractSetSettings, - GougingSettings, - PricePinSettings, - RedundancySettings, - UploadPackingSettings, + SettingsGouging, + SettingsPinned, + SettingsUpload, } from '@siafoundation/renterd-types' import { - toSiacoins, blocksToWeeks, bytesToTB, - valuePerBytePerBlockToPerTBPerMonth, - valuePerPeriodToPerMonth, - valuePerOneToPerMillion, - weeksToBlocks, nanosecondsInDays, nanosecondsInMinutes, + toSiacoins, + valuePerBytePerBlockToPerTBPerMonth, valuePerByteToPerTB, + valuePerOneToPerMillion, + valuePerPeriodToPerMonth, + weeksToBlocks, } from '@siafoundation/units' import BigNumber from 'bignumber.js' +import { firstTimeGougingData } from './resources' import { AutopilotData, - scDecimalPlaces, - SettingsData, - ContractSetData, - defaultContractSet, GougingData, - RedundancyData, - UploadPackingData, + PinningData, + SettingsData, + UploadData, defaultAutopilot, - PricePinData, + scDecimalPlaces, } from './types' -import { firstTimeGougingData } from './resources' -import { currencyOptions } from '@siafoundation/react-core' // down export function transformDownAutopilot( @@ -101,31 +96,12 @@ export function transformDownAutopilot( } } -export function transformDownContractSet( - c?: ContractSetSettings -): ContractSetData { - if (!c) { - return defaultContractSet - } - return { - defaultContractSet: c.default, - } -} - -export function transformDownUploadPacking( - u: UploadPackingSettings -): UploadPackingData { - return { - uploadPackingEnabled: u.enabled, - } -} - export function transformDownGouging({ gouging: _gouging, averages, hasBeenConfigured, }: { - gouging: GougingSettings + gouging: SettingsGouging averages?: { settings: { download_price: string @@ -178,21 +154,19 @@ export function transformDownGouging({ } } -export function transformDownPricePinning( - p: PricePinSettings, +export function transformDownPinned( + p: SettingsPinned, + autopilotID: string, periodBlocks?: number -): PricePinData { +): PinningData { const fixedFiat = currencyOptions.find((c) => c.id === p.currency)?.fixed || 6 return { - pinningEnabled: p.enabled, pinnedCurrency: p.currency, - forexEndpointURL: p.forexEndpointURL, pinnedThreshold: new BigNumber(p.threshold).times(100), - // Assume the default autopilot named 'autopilot'. - shouldPinAllowance: p.autopilots['autopilot']?.allowance.pinned || false, + shouldPinAllowance: p.autopilots?.[autopilotID]?.allowance.pinned || false, allowanceMonthPinned: toFixedMaxBigNumber( valuePerPeriodToPerMonth( - new BigNumber(p.autopilots['autopilot']?.allowance.value || 0), + new BigNumber(p.autopilots?.[autopilotID]?.allowance.value || 0), // If pinned allowance is non zero, the period value will be defined. periodBlocks || weeksToBlocks(6) ), @@ -213,21 +187,22 @@ export function transformDownPricePinning( } } -export function transformDownRedundancy(r: RedundancySettings): RedundancyData { +export function transformDownUpload(u?: SettingsUpload): UploadData { return { - minShards: new BigNumber(r.minShards), - totalShards: new BigNumber(r.totalShards), + defaultContractSet: u.defaultContractSet ? u.defaultContractSet : '', + uploadPackingEnabled: u.packing.enabled, + minShards: new BigNumber(u.redundancy.minShards), + totalShards: new BigNumber(u.redundancy.totalShards), } } export type RemoteData = { hasBeenConfigured: boolean + autopilotID: string autopilot: AutopilotConfig | undefined - contractSet: ContractSetSettings | undefined - uploadPacking: UploadPackingSettings - gouging: GougingSettings - redundancy: RedundancySettings - pricePinning: PricePinSettings + gouging: SettingsGouging + pinned: SettingsPinned + upload: SettingsUpload averages?: { settings: { download_price: string @@ -239,30 +214,25 @@ export type RemoteData = { export function transformDown({ hasBeenConfigured, + autopilotID, autopilot, - contractSet, - uploadPacking, gouging, - redundancy, - pricePinning, + pinned, + upload, averages, }: RemoteData): SettingsData { return { // autopilot ...transformDownAutopilot(autopilot), - // contractset - ...transformDownContractSet(contractSet), - // uploadpacking - ...transformDownUploadPacking(uploadPacking), // gouging ...transformDownGouging({ gouging, averages, hasBeenConfigured, }), - // price pinning - ...transformDownPricePinning(pricePinning, autopilot?.contracts.period), - // redundancy - ...transformDownRedundancy(redundancy), + // pinning + ...transformDownPinned(pinned, autopilotID, autopilot?.contracts.period), + // upload + ...transformDownUpload(upload), } } diff --git a/apps/renterd/contexts/config/transformUp.ts b/apps/renterd/contexts/config/transformUp.ts index 8cc555f8b..1dd941ad0 100644 --- a/apps/renterd/contexts/config/transformUp.ts +++ b/apps/renterd/contexts/config/transformUp.ts @@ -1,10 +1,8 @@ import { AutopilotConfig, - ContractSetSettings, - GougingSettings, - PricePinSettings, - RedundancySettings, - UploadPackingSettings, + SettingsGouging, + SettingsPinned, + SettingsUpload, } from '@siafoundation/renterd-types' import { toHastings, @@ -21,13 +19,10 @@ import { AutopilotData, SettingsData, getAdvancedDefaultAutopilot, - ContractSetData, - RedundancyData, - UploadPackingData, - advancedDefaultContractSet, - PricePinData, + PinningData, + getAdvancedDefaultUpload, } from './types' -import { Resources } from './resources' +import { ResourcesRequiredLoaded } from './resources' import BigNumber from 'bignumber.js' import { pickBy } from '@technically/lodash' import { Dictionary } from 'lodash' @@ -83,34 +78,10 @@ export function transformUpAutopilot( } } -export function transformUpContractSet( - values: ContractSetData, - existingValues: ContractSetSettings | undefined -): ContractSetSettings { - const _default = - values.defaultContractSet || - (existingValues?.default as string) || - advancedDefaultContractSet.defaultContractSet - return { - ...existingValues, - default: _default, - } -} - -export function transformUpUploadPacking( - values: UploadPackingData, - existingValues: UploadPackingSettings -): UploadPackingSettings { - return { - ...existingValues, - enabled: values.uploadPackingEnabled, - } -} - export function transformUpGouging( values: SettingsData, - existingValues: GougingSettings -): GougingSettings { + existingValues: SettingsGouging +): SettingsGouging { return { ...existingValues, maxRPCPrice: toHastings( @@ -143,30 +114,17 @@ export function transformUpGouging( } } -export function transformUpRedundancy( - values: RedundancyData, - existingValues: RedundancySettings -): RedundancySettings { - return { - ...existingValues, - minShards: values.minShards.toNumber(), - totalShards: values.totalShards.toNumber(), - } -} - -export function transformUpPricePinning( - values: PricePinData & { periodWeeks: BigNumber }, - existingValues: PricePinSettings -): PricePinSettings { +export function transformUpPinned( + values: PinningData & { periodWeeks: BigNumber }, + existingValues: SettingsPinned, + autopilotID: string +): SettingsPinned { return { ...existingValues, - enabled: values.pinningEnabled, currency: values.pinnedCurrency, - forexEndpointURL: values.forexEndpointURL, threshold: values.pinnedThreshold.div(100).toNumber(), autopilots: { - // Update the default autopilot named 'autopilot'. - autopilot: { + [autopilotID]: { allowance: { pinned: values.shouldPinAllowance, value: valuePerMonthToPerPeriod( @@ -195,13 +153,36 @@ export function transformUpPricePinning( } } +export function transformUpUpload( + values: Partial, + existingValues: SettingsUpload +): SettingsUpload { + const defaultContractSet = + values.defaultContractSet || + existingValues.defaultContractSet || + getAdvancedDefaultUpload('mainnet').defaultContractSet + return { + ...existingValues, + defaultContractSet, + packing: { + ...existingValues.packing, + enabled: values.uploadPackingEnabled, + }, + redundancy: { + ...existingValues.redundancy, + minShards: values.minShards.toNumber(), + totalShards: values.totalShards.toNumber(), + }, + } +} + export function transformUp({ resources, renterdState, isAutopilotEnabled, values, }: { - resources: Resources + resources: ResourcesRequiredLoaded renterdState: { network: 'mainnet' | 'zen' | 'anagami' } isAutopilotEnabled: boolean values: SettingsData @@ -214,26 +195,20 @@ export function transformUp({ ) : undefined - const contractSet = transformUpContractSet(values, resources.contractSet.data) - const uploadPacking = transformUpUploadPacking( - values, - resources.uploadPacking.data - ) const gouging = transformUpGouging(values, resources.gouging.data) - const redundancy = transformUpRedundancy(values, resources.redundancy.data) - const pricePinning = transformUpPricePinning( + const pinned = transformUpPinned( values, - resources.pricePinning.data + resources.pinned.data, + resources.autopilotState.data.id ) + const upload = transformUpUpload(values, resources.upload.data) return { payloads: { autopilot, - contractSet, - uploadPacking, gouging, - redundancy, - pricePinning, + pinned, + upload, }, } } diff --git a/apps/renterd/contexts/config/types.ts b/apps/renterd/contexts/config/types.ts index b2f3a1b7e..c981557d6 100644 --- a/apps/renterd/contexts/config/types.ts +++ b/apps/renterd/contexts/config/types.ts @@ -34,14 +34,6 @@ export const defaultAutopilot = { minProtocolVersion: '', } -export const defaultContractSet = { - defaultContractSet: '', -} - -export const defaultUploadPacking = { - uploadPackingEnabled: true, -} - export const defaultGouging = { maxRPCPriceMillion: undefined as BigNumber | undefined, maxStoragePriceTBMonth: undefined as BigNumber | undefined, @@ -55,10 +47,8 @@ export const defaultGouging = { migrationSurchargeMultiplier: undefined as BigNumber | undefined, } -export const defaultPricePinning = { - pinningEnabled: false, +export const defaultPinned = { pinnedCurrency: '' as CurrencyId | '', - forexEndpointURL: '', pinnedThreshold: undefined as BigNumber | undefined, shouldPinMaxStoragePrice: false, maxStoragePriceTBMonthPinned: undefined as BigNumber | undefined, @@ -70,33 +60,34 @@ export const defaultPricePinning = { allowanceMonthPinned: undefined as BigNumber | undefined, } -export const defaultRedundancy = { +export const defaultUpload = { + // default contract set + defaultContractSet: '', + // packing + uploadPackingEnabled: true, + // redundancy minShards: undefined as BigNumber | undefined, totalShards: undefined as BigNumber | undefined, } +export const defaultRedundancy = {} + export const defaultValues = { // autopilot ...defaultAutopilot, - // contract set - ...defaultContractSet, - // upload packing - ...defaultUploadPacking, // gouging ...defaultGouging, - // redundancy - ...defaultRedundancy, - // price pinning - ...defaultPricePinning, + // pinning + ...defaultPinned, + // upload + ...defaultUpload, } export type AutopilotData = typeof defaultAutopilot -export type ContractSetData = typeof defaultContractSet -export type UploadPackingData = typeof defaultUploadPacking export type GougingData = typeof defaultGouging -export type RedundancyData = typeof defaultRedundancy +export type PinningData = typeof defaultPinned +export type UploadData = typeof defaultUpload export type SettingsData = typeof defaultValues -export type PricePinData = typeof defaultPricePinning // advanced defaults export function getAdvancedDefaultAutopilot( @@ -136,35 +127,32 @@ export function getAdvancedDefaultAutopilot( } } -export const advancedDefaultContractSet: ContractSetData = { - ...defaultContractSet, - defaultContractSet: 'autopilot', -} - -export const advancedDefaultUploadPacking: UploadPackingData = { - ...defaultUploadPacking, -} - export const advancedDefaultGouging: GougingData = { ...defaultGouging, } -export const advancedDefaultPricePinning: PricePinData = { - ...defaultPricePinning, +export const advancedDefaultPinned: PinningData = { + ...defaultPinned, } -export function getAdvancedDefaultRedundancy( +export function getAdvancedDefaultUpload( network: 'mainnet' | 'zen' | 'anagami' -): RedundancyData { - return network === 'mainnet' - ? { - minShards: new BigNumber(10), - totalShards: new BigNumber(30), - } - : { - minShards: new BigNumber(2), - totalShards: new BigNumber(6), - } +): UploadData { + const advancedDefaultRedundancy = + network === 'mainnet' + ? { + minShards: new BigNumber(10), + totalShards: new BigNumber(30), + } + : { + minShards: new BigNumber(2), + totalShards: new BigNumber(6), + } + return { + ...defaultUpload, + defaultContractSet: 'autopilot', + ...advancedDefaultRedundancy, + } } export function getAdvancedDefaults( @@ -172,11 +160,9 @@ export function getAdvancedDefaults( ): SettingsData { return { ...getAdvancedDefaultAutopilot(network), - ...advancedDefaultContractSet, - ...advancedDefaultUploadPacking, ...advancedDefaultGouging, - ...getAdvancedDefaultRedundancy(network), - ...advancedDefaultPricePinning, + ...advancedDefaultPinned, + ...getAdvancedDefaultUpload(network), } } diff --git a/apps/renterd/contexts/config/useAllowanceDerivedPricing.tsx b/apps/renterd/contexts/config/useAllowanceDerivedPricing.tsx index 15cf99634..deed52d94 100644 --- a/apps/renterd/contexts/config/useAllowanceDerivedPricing.tsx +++ b/apps/renterd/contexts/config/useAllowanceDerivedPricing.tsx @@ -1,53 +1,21 @@ +import { fiatToSiacoin, siacoinToFiat } from '@siafoundation/units' +import BigNumber from 'bignumber.js' import { useMemo } from 'react' -import { SettingsData } from './types' import { UseFormReturn } from 'react-hook-form' -import BigNumber from 'bignumber.js' -import useSWR from 'swr' -import axios from 'axios' +import { useApp } from '../app' import { calculateIdealAllowance, derivePricingFromAllowance, } from './deriveAllowance' -import { useApp } from '../app' -import { useRedundancyMultiplier } from './useRedundancyMultiplier' import { allowanceFactor, downloadWeight, storageWeight, uploadWeight, } from './deriveAllowanceConfig' -import { - fiatToSiacoin, - siacoinToFiat, - minutesInMilliseconds, -} from '@siafoundation/units' - -export function useForexExchangeRate({ - form, -}: { - form: UseFormReturn -}) { - const pinnedCurrency = form.watch('pinnedCurrency') - const forexEndpointURL = form.watch('forexEndpointURL') - const forex = useSWR( - forexEndpointURL && pinnedCurrency - ? ['pricePinningExchangeRate', forexEndpointURL, pinnedCurrency] - : null, - async () => { - const response = await axios.get(`${forexEndpointURL}/${pinnedCurrency}`) - return response.data - }, - { - refreshInterval: minutesInMilliseconds(5), - dedupingInterval: 10_000, - } - ) - const rate = forex.data - return useMemo( - () => (rate && typeof rate === 'number' ? new BigNumber(rate) : undefined), - [rate] - ) -} +import { SettingsData } from './types' +import { useFormExchangeRate } from './useFormExchangeRate' +import { useRedundancyMultiplier } from './useRedundancyMultiplier' // Convert the allowance to pricing values. This method returns values for // pinned or non-pinned values depending on which is enabled on a per-field basis. @@ -74,9 +42,7 @@ export function useAllowanceDerivedPricingForEnabledFields({ minShards: form.watch('minShards'), totalShards: form.watch('totalShards'), }) - const exchangeRate = useForexExchangeRate({ - form, - }) + const { rate } = useFormExchangeRate(form) const values = useMemo(() => { if (isAutopilotEnabled) { @@ -95,14 +61,14 @@ export function useAllowanceDerivedPricingForEnabledFields({ return null } // Convert derived siacoin prices to pinned fiat prices. - const pinnedPricing = exchangeRate + const pinnedPricing = rate ? { maxStoragePriceTBMonthPinned: - derivedPricing?.maxStoragePriceTBMonth.times(exchangeRate), + derivedPricing?.maxStoragePriceTBMonth.times(rate), maxDownloadPriceTBPinned: - derivedPricing?.maxDownloadPriceTB.times(exchangeRate), + derivedPricing?.maxDownloadPriceTB.times(rate), maxUploadPriceTBPinned: - derivedPricing?.maxUploadPriceTB.times(exchangeRate), + derivedPricing?.maxUploadPriceTB.times(rate), } : {} return { @@ -118,7 +84,7 @@ export function useAllowanceDerivedPricingForEnabledFields({ downloadTBMonth, uploadTBMonth, redundancyMultiplier, - exchangeRate, + rate, ]) const shouldPinMaxStoragePrice = form.watch('shouldPinMaxStoragePrice') @@ -170,16 +136,14 @@ export function useEnabledPricingValuesInSiacoin({ const maxDownloadPriceTBPinned = form.watch('maxDownloadPriceTBPinned') const maxUploadPriceTB = form.watch('maxUploadPriceTB') const maxUploadPriceTBPinned = form.watch('maxUploadPriceTBPinned') - const exchangeRate = useForexExchangeRate({ - form, - }) + const { rate } = useFormExchangeRate(form) const needsExchangeRate = shouldPinMaxStoragePrice || shouldPinMaxDownloadPrice || shouldPinMaxUploadPrice - if (needsExchangeRate && !exchangeRate) { + if (needsExchangeRate && !rate) { return null } @@ -197,13 +161,13 @@ export function useEnabledPricingValuesInSiacoin({ return { maxStoragePriceTBMonth: shouldPinMaxStoragePrice - ? fiatToSiacoin(maxStoragePriceTBMonthPinned, exchangeRate) + ? fiatToSiacoin(maxStoragePriceTBMonthPinned, rate) : maxStoragePriceTBMonth, maxDownloadPriceTB: shouldPinMaxDownloadPrice - ? fiatToSiacoin(maxDownloadPriceTBPinned, exchangeRate) + ? fiatToSiacoin(maxDownloadPriceTBPinned, rate) : maxDownloadPriceTB, maxUploadPriceTB: shouldPinMaxUploadPrice - ? fiatToSiacoin(maxUploadPriceTBPinned, exchangeRate) + ? fiatToSiacoin(maxUploadPriceTBPinned, rate) : maxUploadPriceTB, } } @@ -231,9 +195,7 @@ export function useEnabledAllowanceFromEnabledPricingValues({ const prices = useEnabledPricingValuesInSiacoin({ form, }) - const exchangeRate = useForexExchangeRate({ - form, - }) + const { rate } = useFormExchangeRate(form) if (!prices) { return null @@ -258,10 +220,10 @@ export function useEnabledAllowanceFromEnabledPricingValues({ } if (shouldPinAllowance) { - if (!exchangeRate) { + if (!rate) { return null } - results.allowanceMonthPinned = allowance.times(exchangeRate) + results.allowanceMonthPinned = allowance.times(rate) } else { results.allowanceMonth = allowance } @@ -278,17 +240,13 @@ export function useEnabledAllowanceInSiacoin({ const shouldPinAllowance = form.watch('shouldPinAllowance') const allowanceMonth = form.watch('allowanceMonth') const allowanceMonthPinned = form.watch('allowanceMonthPinned') - const exchangeRate = useForexExchangeRate({ - form, - }) + const { rate } = useFormExchangeRate(form) const needsExchangeRate = shouldPinAllowance - if (needsExchangeRate && !exchangeRate) { + if (needsExchangeRate && !rate) { return null } - return shouldPinAllowance - ? allowanceMonthPinned?.div(exchangeRate) - : allowanceMonth + return shouldPinAllowance ? allowanceMonthPinned?.div(rate) : allowanceMonth } export function pricesToPinnedPrices({ diff --git a/apps/renterd/contexts/config/useAutopilotEvaluations.tsx b/apps/renterd/contexts/config/useAutopilotEvaluations.tsx index b90db98c6..71fea6ebe 100644 --- a/apps/renterd/contexts/config/useAutopilotEvaluations.tsx +++ b/apps/renterd/contexts/config/useAutopilotEvaluations.tsx @@ -1,29 +1,33 @@ +import { objectEntries } from '@siafoundation/design-system' +import { currencyOptions } from '@siafoundation/react-core' import { useAutopilotConfigEvaluate, useBusState, } from '@siafoundation/renterd-react' -import { transformUp } from './transformUp' -import { Resources, checkIfAllResourcesLoaded } from './resources' +import { humanNumber, humanSiacoin, toHastings } from '@siafoundation/units' import BigNumber from 'bignumber.js' +import { useCallback, useMemo } from 'react' +import { UseFormReturn } from 'react-hook-form' +import { Fields, getFields } from './fields' +import { + ResourcesMaybeLoaded, + ResourcesRequiredLoaded, + checkIfAllResourcesLoaded, +} from './resources' +import { transformDownGouging } from './transformDown' +import { transformUp } from './transformUp' import { GougingData, RecommendationItem, SettingsData, getAdvancedDefaults, } from './types' -import { UseFormReturn } from 'react-hook-form' -import { useCallback, useMemo } from 'react' -import { transformDownGouging } from './transformDown' -import { Fields, getFields } from './fields' -import { humanNumber, humanSiacoin, toHastings } from '@siafoundation/units' import { pricesToPinnedPrices, useEnabledAllowanceInSiacoin, useEnabledPricingValuesInSiacoin, - useForexExchangeRate, } from './useAllowanceDerivedPricing' -import { objectEntries } from '@siafoundation/design-system' -import { currencyOptions } from '@siafoundation/react-core' +import { useFormExchangeRate } from './useFormExchangeRate' export function useAutopilotEvaluations({ form, @@ -31,7 +35,7 @@ export function useAutopilotEvaluations({ isAutopilotEnabled, }: { form: UseFormReturn - resources: Resources + resources: ResourcesMaybeLoaded isAutopilotEnabled: boolean }) { const values = form.watch() @@ -93,7 +97,7 @@ export function useAutopilotEvaluations({ } const { payloads } = transformUp({ - resources, + resources: resources as ResourcesRequiredLoaded, renterdState: renterdState.data, isAutopilotEnabled, values: currentValuesWithPinnedOverridesAndDefaults, @@ -122,7 +126,7 @@ export function useAutopilotEvaluations({ disabled: !eval0Enabled, payload: { gougingSettings: payloads?.gouging, - redundancySettings: payloads?.redundancy, + redundancySettings: payloads?.upload.redundancy, autopilotConfig: payloads?.autopilot, }, config: { @@ -143,7 +147,7 @@ export function useAutopilotEvaluations({ disabled: !eval50Enabled, payload: { gougingSettings: payloads?.gouging, - redundancySettings: payloads?.redundancy, + redundancySettings: payloads?.upload.redundancy, autopilotConfig: { ...payloads?.autopilot, contracts: { @@ -168,7 +172,7 @@ export function useAutopilotEvaluations({ disabled: !eval10Enabled, payload: { gougingSettings: payloads?.gouging, - redundancySettings: payloads?.redundancy, + redundancySettings: payloads?.upload.redundancy, autopilotConfig: { ...payloads?.autopilot, contracts: { @@ -205,7 +209,7 @@ export function useAutopilotEvaluations({ disabled: !usableAfterRecsEvalEnabled, payload: { gougingSettings: recommendedGougingSettings, - redundancySettings: payloads?.redundancy, + redundancySettings: payloads?.upload.redundancy, autopilotConfig: payloads?.autopilot, }, config: { @@ -242,10 +246,7 @@ export function useAutopilotEvaluations({ ] ) - const pinnedCurrency = form.watch('pinnedCurrency') - const exchangeRate = useForexExchangeRate({ - form, - }) + const { pinnedCurrency, rate } = useFormExchangeRate(form) const recommendations = useMemo(() => { if (!payloads || !recommendedGougingSettings) { @@ -265,7 +266,7 @@ export function useAutopilotEvaluations({ hasBeenConfigured: true, }) const recommendedPinned = pricesToPinnedPrices({ - exchangeRate, + exchangeRate: rate, maxStoragePriceTBMonth: recommended.maxStoragePriceTBMonth, maxDownloadPriceTB: recommended.maxDownloadPriceTB, maxUploadPriceTB: recommended.maxUploadPriceTB, @@ -313,7 +314,7 @@ export function useAutopilotEvaluations({ pinnedCurrency, usableHostsCurrent, usableHostsAfterRecommendation, - exchangeRate, + rate, ]) const foundRecommendation = !!recommendations.length @@ -387,7 +388,6 @@ const fields = getFields({ validationContext: { isAutopilotEnabled: true, configViewMode: 'basic', - pinningEnabled: false, }, isAutopilotEnabled: true, configViewMode: 'basic', @@ -475,8 +475,6 @@ export const valuesZeroDefaults: SettingsData = { migrationSurchargeMultiplier: new BigNumber(0), minShards: new BigNumber(0), totalShards: new BigNumber(0), - pinningEnabled: false, - forexEndpointURL: '', pinnedCurrency: 'usd', pinnedThreshold: new BigNumber(0), } diff --git a/apps/renterd/contexts/config/useAverages.tsx b/apps/renterd/contexts/config/useAverages.tsx index a9704579e..963ecea36 100644 --- a/apps/renterd/contexts/config/useAverages.tsx +++ b/apps/renterd/contexts/config/useAverages.tsx @@ -7,9 +7,9 @@ import { valuePerOneToPerMillion, } from '@siafoundation/units' import { useSiaCentralHostsNetworkAverages } from '@siafoundation/sia-central-react' -import { useForexExchangeRate } from './useAllowanceDerivedPricing' import { UseFormReturn } from 'react-hook-form' import { SettingsData } from './types' +import { useFormExchangeRate } from './useFormExchangeRate' export function useAverages({ form }: { form: UseFormReturn }) { const averages = useSiaCentralHostsNetworkAverages({ @@ -73,9 +73,7 @@ export function useAverages({ form }: { form: UseFormReturn }) { [averages.data] ) - const exchangeRate = useForexExchangeRate({ - form, - }) + const { rate } = useFormExchangeRate(form) const averagesSc = useMemo(() => { if (!averages.data) { return null @@ -97,19 +95,19 @@ export function useAverages({ form }: { form: UseFormReturn }) { ]) const averagesFiat = useMemo(() => { - if (!averages.data || !exchangeRate) { + if (!averages.data || !rate) { return null } return { - storageAverage: storageAverage.times(exchangeRate), - uploadAverage: uploadAverage.times(exchangeRate), - downloadAverage: downloadAverage.times(exchangeRate), - contractAverage: contractAverage.times(exchangeRate), - rpcAverage: rpcAverage.times(exchangeRate), + storageAverage: storageAverage.times(rate), + uploadAverage: uploadAverage.times(rate), + downloadAverage: downloadAverage.times(rate), + contractAverage: contractAverage.times(rate), + rpcAverage: rpcAverage.times(rate), } }, [ averages.data, - exchangeRate, + rate, storageAverage, uploadAverage, downloadAverage, diff --git a/apps/renterd/contexts/config/useForm.tsx b/apps/renterd/contexts/config/useForm.tsx index 0410ab424..01aca0a73 100644 --- a/apps/renterd/contexts/config/useForm.tsx +++ b/apps/renterd/contexts/config/useForm.tsx @@ -7,10 +7,10 @@ import { getFields } from './fields' import { useApp } from '../app' import useLocalStorageState from 'use-local-storage-state' import { useAutopilotEvaluations } from './useAutopilotEvaluations' -import { Resources } from './resources' +import { ResourcesMaybeLoaded } from './resources' import { getRedundancyMultiplier } from '@siafoundation/units' -export function useForm({ resources }: { resources: Resources }) { +export function useForm({ resources }: { resources: ResourcesMaybeLoaded }) { const form = useHookForm({ mode: 'all', defaultValues, @@ -23,7 +23,6 @@ export function useForm({ resources }: { resources: Resources }) { const uploadTBMonth = form.watch('uploadTBMonth') const minShards = form.watch('minShards') const totalShards = form.watch('totalShards') - const pinningEnabled = form.watch('pinningEnabled') const redundancyMultiplier = useMemo( () => getRedundancyMultiplier(minShards, totalShards), [minShards, totalShards] @@ -57,13 +56,11 @@ export function useForm({ resources }: { resources: Resources }) { const validationContext = useRef({ isAutopilotEnabled, configViewMode, - pinningEnabled, }) useEffect(() => { validationContext.current.isAutopilotEnabled = isAutopilotEnabled validationContext.current.configViewMode = configViewMode - validationContext.current.pinningEnabled = pinningEnabled - }, [isAutopilotEnabled, configViewMode, pinningEnabled]) + }, [isAutopilotEnabled, configViewMode]) const fields = useMemo(() => { const advancedDefaults = renterdState.data diff --git a/apps/renterd/contexts/config/useFormExchangeRate.tsx b/apps/renterd/contexts/config/useFormExchangeRate.tsx new file mode 100644 index 000000000..a331ec83a --- /dev/null +++ b/apps/renterd/contexts/config/useFormExchangeRate.tsx @@ -0,0 +1,14 @@ +import { UseFormReturn } from 'react-hook-form' +import { SettingsData } from './types' +import { useExchangeRate } from '@siafoundation/react-core' + +export function useFormExchangeRate(form: UseFormReturn) { + const pinnedCurrency = form.watch('pinnedCurrency') + const { rate } = useExchangeRate({ + currency: pinnedCurrency || undefined, + }) + return { + rate, + pinnedCurrency, + } +} diff --git a/apps/renterd/contexts/config/useOnValid.tsx b/apps/renterd/contexts/config/useOnValid.tsx index 1dcaabc01..411be0626 100644 --- a/apps/renterd/contexts/config/useOnValid.tsx +++ b/apps/renterd/contexts/config/useOnValid.tsx @@ -1,164 +1,131 @@ import { - triggerSuccessToast, triggerErrorToast, + triggerSuccessToast, } from '@siafoundation/design-system' -import { useCallback } from 'react' +import { delay, useMutate } from '@siafoundation/react-core' import { useAutopilotConfigUpdate, useAutopilotTrigger, useBusState, - useSettingUpdate, + useSettingsGougingUpdate, + useSettingsPinnedUpdate, + useSettingsUploadUpdate, } from '@siafoundation/renterd-react' -import { defaultValues } from './types' +import { busHostsRoute } from '@siafoundation/renterd-types' +import { useCallback } from 'react' +import { + ResourcesMaybeLoaded, + ResourcesRequiredLoaded, + checkIfAllResourcesLoaded, +} from './resources' import { transformUp } from './transformUp' -import { delay, useMutate } from '@siafoundation/react-core' -import { Resources } from './resources' +import { defaultValues } from './types' import { useSyncContractSet } from './useSyncContractSet' -import { busHostsRoute } from '@siafoundation/renterd-types' export function useOnValid({ resources, isAutopilotEnabled, revalidateAndResetForm, }: { - resources: Resources + resources: ResourcesMaybeLoaded isAutopilotEnabled: boolean revalidateAndResetForm: () => Promise }) { const autopilotTrigger = useAutopilotTrigger() const autopilotUpdate = useAutopilotConfigUpdate() - const settingUpdate = useSettingUpdate() + const settingsGougingUpdate = useSettingsGougingUpdate() + const settingsPinnedUpdate = useSettingsPinnedUpdate() + const settingsUploadUpdate = useSettingsUploadUpdate() const renterdState = useBusState() const { maybeSyncDefaultContractSet } = useSyncContractSet() const mutate = useMutate() const onValid = useCallback( async (values: typeof defaultValues) => { - if ( - !resources.gouging.data || - !resources.redundancy.data || - !renterdState.data - ) { + if (!checkIfAllResourcesLoaded(resources)) { return } const firstTimeSettingConfig = isAutopilotEnabled && !resources.autopilot.data - try { - const { payloads } = transformUp({ - resources, - renterdState: renterdState.data, - isAutopilotEnabled, - values, - }) - const autopilotResponse = payloads.autopilot - ? await autopilotUpdate.put({ - payload: payloads.autopilot, - }) - : undefined + const { payloads } = transformUp({ + resources: resources as ResourcesRequiredLoaded, + renterdState: renterdState.data, + isAutopilotEnabled, + values, + }) - const [ - contractSetResponse, - uploadPackingResponse, - gougingResponse, - redundancyResponse, - pricePinningResponse, - ] = await Promise.all([ - settingUpdate.put({ - params: { - key: 'contractset', - }, - payload: payloads.contractSet, - }), - settingUpdate.put({ - params: { - key: 'uploadpacking', - }, - payload: payloads.uploadPacking, - }), - settingUpdate.put({ - params: { - key: 'gouging', - }, + const autopilotResponse = payloads.autopilot + ? await autopilotUpdate.put({ + payload: payloads.autopilot, + }) + : undefined + + const [gougingResponse, pinnedResponse, uploadResponse] = + await Promise.all([ + settingsGougingUpdate.put({ payload: payloads.gouging, }), - settingUpdate.put({ - params: { - key: 'redundancy', - }, - payload: payloads.redundancy, + settingsPinnedUpdate.put({ + payload: payloads.pinned, }), - settingUpdate.put({ - params: { - key: 'pricepinning', - }, - payload: payloads.pricePinning, + settingsUploadUpdate.put({ + payload: payloads.upload, }), ]) - if (autopilotResponse?.error) { - throw Error(autopilotResponse.error) - } - if (contractSetResponse.error) { - throw Error(contractSetResponse.error) - } - if (uploadPackingResponse.error) { - throw Error(uploadPackingResponse.error) - } - if (gougingResponse.error) { - throw Error(gougingResponse.error) - } - if (redundancyResponse.error) { - throw Error(redundancyResponse.error) - } - if (pricePinningResponse.error) { - throw Error(pricePinningResponse.error) - } + const error = + autopilotResponse?.error || + gougingResponse.error || + pinnedResponse.error || + uploadResponse.error + if (error) { + triggerErrorToast({ + title: 'Error updating configuration', + body: error, + }) + return + } - if (isAutopilotEnabled) { - // Sync default contract set if the setting is enabled. - maybeSyncDefaultContractSet(values.autopilotContractSet) + if (isAutopilotEnabled) { + // Sync default contract set if the setting is enabled. + maybeSyncDefaultContractSet(values.autopilotContractSet) - // Trigger the autopilot loop with new settings applied. - autopilotTrigger.post({ - payload: { - forceScan: true, - }, - }) - } + // Trigger the autopilot loop with new settings applied. + autopilotTrigger.post({ + payload: { + forceScan: true, + }, + }) + } - triggerSuccessToast({ title: 'Configuration has been saved' }) + triggerSuccessToast({ title: 'Configuration has been saved' }) - // If autopilot is being configured for the first time, - // revalidate the empty hosts list. - if (firstTimeSettingConfig) { - const refreshHostsAfterDelay = async () => { - await delay(5_000) - mutate((key) => key.startsWith(busHostsRoute)) - await delay(5_000) - mutate((key) => key.startsWith(busHostsRoute)) - } - refreshHostsAfterDelay() + // If autopilot is being configured for the first time, + // revalidate the empty hosts list. + if (firstTimeSettingConfig) { + const refreshHostsAfterDelay = async () => { + await delay(5_000) + mutate((key) => key.startsWith(busHostsRoute)) + await delay(5_000) + mutate((key) => key.startsWith(busHostsRoute)) } - - await revalidateAndResetForm() - } catch (e) { - triggerErrorToast({ - title: 'Error updating configuration', - body: (e as Error).message, - }) - console.log(e) + refreshHostsAfterDelay() } + + await revalidateAndResetForm() }, [ + resources, renterdState.data, isAutopilotEnabled, autopilotUpdate, + settingsGougingUpdate, + settingsPinnedUpdate, + settingsUploadUpdate, revalidateAndResetForm, maybeSyncDefaultContractSet, - mutate, - settingUpdate, - resources, autopilotTrigger, + mutate, ] ) diff --git a/apps/renterd/contexts/config/useResources.tsx b/apps/renterd/contexts/config/useResources.tsx index f36240dd5..07ddd775a 100644 --- a/apps/renterd/contexts/config/useResources.tsx +++ b/apps/renterd/contexts/config/useResources.tsx @@ -1,16 +1,14 @@ +import { useAppSettings } from '@siafoundation/react-core' import { useAutopilotConfig, - useSettingContractSet, - useSettingGouging, - useSettingPricePinning, - useSettingRedundancy, - useSettingUploadPacking, + useSettingsGouging, + useSettingsPinned, + useSettingsUpload, } from '@siafoundation/renterd-react' -import { useSyncContractSet } from './useSyncContractSet' -import { useAppSettings } from '@siafoundation/react-core' import { useSiaCentralHostsNetworkAverages } from '@siafoundation/sia-central-react' -import { useApp } from '../app' import { minutesInMilliseconds } from '@siafoundation/units' +import { useApp } from '../app' +import { useSyncContractSet } from './useSyncContractSet' export function useResources() { const app = useApp() @@ -23,37 +21,22 @@ export function useResources() { }, }, }) - const contractSet = useSettingContractSet({ - config: { - swr: { - errorRetryCount: 0, - refreshInterval: minutesInMilliseconds(1), - }, - }, - }) // settings with initial defaults - const gouging = useSettingGouging({ - config: { - swr: { - refreshInterval: minutesInMilliseconds(1), - }, - }, - }) - const redundancy = useSettingRedundancy({ + const gouging = useSettingsGouging({ config: { swr: { refreshInterval: minutesInMilliseconds(1), }, }, }) - const uploadPacking = useSettingUploadPacking({ + const pinned = useSettingsPinned({ config: { swr: { refreshInterval: minutesInMilliseconds(1), }, }, }) - const pricePinning = useSettingPricePinning({ + const upload = useSettingsUpload({ config: { swr: { refreshInterval: minutesInMilliseconds(1), @@ -78,11 +61,9 @@ export function useResources() { return { autopilotState: app.autopilot.state, autopilot, - contractSet, gouging, - redundancy, - uploadPacking, - pricePinning, + pinned, + upload, averages, shouldSyncDefaultContractSet, setShouldSyncDefaultContractSet, diff --git a/apps/renterd/contexts/config/useSyncContractSet.tsx b/apps/renterd/contexts/config/useSyncContractSet.tsx index f7e978cc4..912464e1c 100644 --- a/apps/renterd/contexts/config/useSyncContractSet.tsx +++ b/apps/renterd/contexts/config/useSyncContractSet.tsx @@ -5,46 +5,46 @@ import { } from '@siafoundation/design-system' import { useCallback } from 'react' import { - useSettingContractSet, - useSettingUpdate, + useSettingsUpload, + useSettingsUploadUpdate, } from '@siafoundation/renterd-react' import useLocalStorageState from 'use-local-storage-state' -import { transformUpContractSet } from '../../contexts/config/transformUp' +import { transformUpUpload } from './transformUp' export function useSyncContractSet() { const [shouldSyncDefaultContractSet, setShouldSyncDefaultContractSet] = useLocalStorageState('v0/autopilot/syncDefaultContractSet', { defaultValue: true, }) - const contractSet = useSettingContractSet({ + const uploadSettings = useSettingsUpload({ config: { swr: { errorRetryCount: 0, }, }, }) - const settingUpdate = useSettingUpdate() + const settingsUploadUpdate = useSettingsUploadUpdate() const maybeSyncDefaultContractSet = useCallback( async (autopilotContractSet: string) => { - const csd = contractSet.data || { default: '' } + if (!uploadSettings.data) { + return + } + const csd = uploadSettings.data || { defaultContractSet: '' } try { if ( shouldSyncDefaultContractSet && - autopilotContractSet !== csd.default + autopilotContractSet !== csd.defaultContractSet ) { - await settingUpdate.put({ - params: { - key: 'contractset', - }, - payload: transformUpContractSet( + await settingsUploadUpdate.put({ + payload: transformUpUpload( { defaultContractSet: autopilotContractSet, }, - contractSet.data + uploadSettings.data ), }) - contractSet.mutate() + uploadSettings.mutate() triggerSuccessToast({ title: 'Default contract set updated', body: ( @@ -64,7 +64,7 @@ export function useSyncContractSet() { } }, // eslint-disable-next-line react-hooks/exhaustive-deps - [contractSet.data, settingUpdate, shouldSyncDefaultContractSet] + [uploadSettings.data, settingsUploadUpdate, shouldSyncDefaultContractSet] ) return { diff --git a/apps/renterd/contexts/contracts/columns.tsx b/apps/renterd/contexts/contracts/columns.tsx index 9920d6819..f9e521999 100644 --- a/apps/renterd/contexts/contracts/columns.tsx +++ b/apps/renterd/contexts/contracts/columns.tsx @@ -453,19 +453,19 @@ export const columns: ContractsTableColumn[] = [ ), }, { - id: 'spendingDownloads', - label: 'downloads spending', + id: 'spendingDeletions', + label: 'deletions spending', category: 'financial', contentClassName: 'w-[120px] justify-end', - render: ({ data: { spendingDownloads } }) => ( - + render: ({ data: { spendingDeletions } }) => ( + ), summary: ({ context: { filteredStats } }) => ( ), }, @@ -490,6 +490,27 @@ export const columns: ContractsTableColumn[] = [ /> ), }, + { + id: 'spendingSectorRoots', + label: 'sector roots spending', + category: 'financial', + contentClassName: 'w-[120px] justify-end', + render: ({ data: { spendingSectorRoots } }) => ( + + ), + summary: ({ context: { filteredStats } }) => ( + + ), + }, ] function getContractStateColor(state: ContractState) { diff --git a/apps/renterd/contexts/contracts/dataset.tsx b/apps/renterd/contexts/contracts/dataset.tsx index aa2788f7b..03cac83fb 100644 --- a/apps/renterd/contexts/contracts/dataset.tsx +++ b/apps/renterd/contexts/contracts/dataset.tsx @@ -69,7 +69,8 @@ export function useDataset({ renewedFrom: c.renewedFrom, totalCost: new BigNumber(c.totalCost), spendingUploads: new BigNumber(c.spending.uploads), - spendingDownloads: new BigNumber(c.spending.downloads), + spendingDeletions: new BigNumber(c.spending.deletions), + spendingSectorRoots: new BigNumber(c.spending.sectorRoots), spendingFundAccount: new BigNumber(c.spending.fundAccount), size: new BigNumber(c.size), } diff --git a/apps/renterd/contexts/contracts/index.tsx b/apps/renterd/contexts/contracts/index.tsx index b72277b50..0abb095bf 100644 --- a/apps/renterd/contexts/contracts/index.tsx +++ b/apps/renterd/contexts/contracts/index.tsx @@ -10,7 +10,7 @@ import { useAutopilotConfig, useContracts as useContractsData, useContractSets, - useSettingContractSet, + useSettingsUpload, } from '@siafoundation/renterd-react' import { createContext, @@ -80,8 +80,8 @@ function useContractsMain() { disabled: !isAutopilotEnabled, }) const autopilotContractSet = apConfig.data?.contracts.set - const contractSetSettings = useSettingContractSet() - const defaultContractSet = contractSetSettings.data?.default + const settingsUpload = useSettingsUpload() + const defaultContractSet = settingsUpload.data?.defaultContractSet const { dataset, diff --git a/apps/renterd/contexts/contracts/types.ts b/apps/renterd/contexts/contracts/types.ts index 889d9dedd..fc1c4e3ec 100644 --- a/apps/renterd/contexts/contracts/types.ts +++ b/apps/renterd/contexts/contracts/types.ts @@ -42,7 +42,8 @@ export type ContractDataWithoutPrunable = { revisionHeight: number totalCost: BigNumber spendingUploads: BigNumber - spendingDownloads: BigNumber + spendingDeletions: BigNumber + spendingSectorRoots: BigNumber spendingFundAccount: BigNumber size: BigNumber } @@ -68,8 +69,9 @@ export type TableColumnId = | 'prunableSize' | 'totalCost' | 'spendingUploads' - | 'spendingDownloads' + | 'spendingDeletions' | 'spendingFundAccount' + | 'spendingSectorRoots' export const columnsDefaultVisible: TableColumnId[] = [ 'contractId', @@ -82,8 +84,9 @@ export const columnsDefaultVisible: TableColumnId[] = [ 'prunableSize', 'totalCost', 'spendingUploads', - 'spendingDownloads', + 'spendingDeletions', 'spendingFundAccount', + 'spendingSectorRoots', ] export type SortField = @@ -98,8 +101,9 @@ export type SortField = | 'prunableSize' | 'totalCost' | 'spendingUploads' - | 'spendingDownloads' + | 'spendingDeletions' | 'spendingFundAccount' + | 'spendingSectorRoots' export const defaultSortField: SortField = 'startTime' @@ -164,8 +168,8 @@ export const sortOptions: { category: 'financial', }, { - id: 'spendingDownloads', - label: 'downloads spending', + id: 'spendingDeletions', + label: 'deletions spending', category: 'financial', }, { @@ -173,6 +177,11 @@ export const sortOptions: { label: 'fund account spending', category: 'financial', }, + { + id: 'spendingSectorRoots', + label: 'sector roots spending', + category: 'financial', + }, ] export type ViewMode = 'list' | 'detail' diff --git a/apps/renterd/contexts/contracts/useFilteredStats.tsx b/apps/renterd/contexts/contracts/useFilteredStats.tsx index ddee9498b..703339cdd 100644 --- a/apps/renterd/contexts/contracts/useFilteredStats.tsx +++ b/apps/renterd/contexts/contracts/useFilteredStats.tsx @@ -58,12 +58,12 @@ export function useFilteredStats({ }, new BigNumber(0)) }, [datasetFiltered]) - const spendingDownloadsTotal = useMemo(() => { + const spendingDeletionsTotal = useMemo(() => { if (!datasetFiltered) { return undefined } return datasetFiltered.reduce((acc, datum) => { - return acc.plus(datum.spendingDownloads) + return acc.plus(datum.spendingDeletions) }, new BigNumber(0)) }, [datasetFiltered]) @@ -76,6 +76,15 @@ export function useFilteredStats({ }, new BigNumber(0)) }, [datasetFiltered]) + const spendingSectorRootsTotal = useMemo(() => { + if (!datasetFiltered) { + return undefined + } + return datasetFiltered.reduce((acc, datum) => { + return acc.plus(datum.spendingSectorRoots) + }, new BigNumber(0)) + }, [datasetFiltered]) + return useMemo(() => { return { sizeTotal, @@ -83,8 +92,9 @@ export function useFilteredStats({ expiringSizeTotal, totalCostTotal, spendingUploadsTotal, - spendingDownloadsTotal, + spendingDeletionsTotal, spendingFundAccountTotal, + spendingSectorRootsTotal, } }, [ sizeTotal, @@ -92,7 +102,8 @@ export function useFilteredStats({ expiringSizeTotal, totalCostTotal, spendingUploadsTotal, - spendingDownloadsTotal, + spendingDeletionsTotal, spendingFundAccountTotal, + spendingSectorRootsTotal, ]) } diff --git a/apps/renterd/contexts/filesManager/index.spec.tsx b/apps/renterd/contexts/filesManager/index.spec.tsx index ee4e7e9c8..5ca67b95e 100644 --- a/apps/renterd/contexts/filesManager/index.spec.tsx +++ b/apps/renterd/contexts/filesManager/index.spec.tsx @@ -5,7 +5,7 @@ import { FilesManagerProvider, useFilesManager, FilesManagerState } from '.' import { useEffect } from 'react' import { mockApiBusBuckets, - mockApiBusSettingRedundancy, + mockApiBusSettingsUpload, mockMatchMedia, } from '../../mock/mock' // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -28,7 +28,7 @@ const server = setupServer() beforeAll(() => { mockMatchMedia() mockApiBusBuckets(server) - mockApiBusSettingRedundancy(server) + mockApiBusSettingsUpload(server) server.listen() }) beforeEach(() => { diff --git a/apps/renterd/contexts/filesManager/uploads.tsx b/apps/renterd/contexts/filesManager/uploads.tsx index 7b2ad5256..4fbc8643c 100644 --- a/apps/renterd/contexts/filesManager/uploads.tsx +++ b/apps/renterd/contexts/filesManager/uploads.tsx @@ -1,25 +1,25 @@ import { triggerErrorToast } from '@siafoundation/design-system' -import { Bucket, busListObjectsRoute } from '@siafoundation/renterd-types' +import { useMutate } from '@siafoundation/react-core' import { useBuckets, useMultipartUploadAbort, - useMultipartUploadPart, useMultipartUploadComplete, useMultipartUploadCreate, - useSettingRedundancy, + useMultipartUploadPart, + useSettingsUpload, } from '@siafoundation/renterd-react' +import { Bucket, busListObjectsRoute } from '@siafoundation/renterd-types' +import { MiBToBytes, minutesInMilliseconds } from '@siafoundation/units' import { throttle } from '@technically/lodash' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { ObjectUploadData, UploadsMap } from './types' +import { MultipartUpload } from '../../lib/multipartUpload' import { FullPath, getBucketFromPath, getKeyFromPath, join, } from '../../lib/paths' -import { MultipartUpload } from '../../lib/multipartUpload' -import { MiBToBytes, minutesInMilliseconds } from '@siafoundation/units' -import { useMutate } from '@siafoundation/react-core' +import { ObjectUploadData, UploadsMap } from './types' import { useWarnActiveUploadsOnClose } from './useWarnActiveUploadsOnClose' const maxConcurrentUploads = 5 @@ -39,7 +39,7 @@ export function useUploads({ activeDirectoryPath }: Props) { const busUploadCreate = useMultipartUploadCreate() const busUploadAbort = useMultipartUploadAbort() const [uploadsMap, setUploadsMap] = useState({}) - const redundancy = useSettingRedundancy({ + const uploadSettings = useSettingsUpload({ config: { swr: { refreshInterval: minutesInMilliseconds(1), @@ -110,7 +110,7 @@ export function useUploads({ activeDirectoryPath }: Props) { bucket: bucket.name, api: ref.current, partSize: getMultipartUploadPartSize( - redundancy.data?.minShards || 1 + uploadSettings.data?.redundancy.minShards || 1 ).toNumber(), maxConcurrentParts: maxConcurrentPartsPerUpload, }) @@ -144,7 +144,7 @@ export function useUploads({ activeDirectoryPath }: Props) { multipartUpload, } }, - [redundancy.data] + [uploadSettings.data] ) const addUploadToQueue = useCallback( diff --git a/apps/renterd/contexts/keys/index.tsx b/apps/renterd/contexts/keys/index.tsx index 157e7f94a..4d369e6f2 100644 --- a/apps/renterd/contexts/keys/index.tsx +++ b/apps/renterd/contexts/keys/index.tsx @@ -14,7 +14,7 @@ import { } from './types' import { columns } from './columns' import { defaultDatasetRefreshInterval } from '../../config/swr' -import { useSettingS3Authentication } from '@siafoundation/renterd-react' +import { useSettingsS3 } from '@siafoundation/renterd-react' const defaultLimit = 50 @@ -22,7 +22,7 @@ function useKeysMain() { const router = useRouter() const limit = Number(router.query.limit || defaultLimit) const offset = Number(router.query.offset || 0) - const response = useSettingS3Authentication({ + const response = useSettingsS3({ config: { swr: { refreshInterval: defaultDatasetRefreshInterval, @@ -35,13 +35,15 @@ function useKeysMain() { return null } const data: KeyData[] = - Object.entries(response.data?.v4Keypairs || {}).map(([key, secret]) => { - return { - id: key, - key, - secret, + Object.entries(response.data?.authentication.v4Keypairs || {}).map( + ([key, secret]) => { + return { + id: key, + key, + secret, + } } - }) || [] + ) || [] return data }, [response.data]) diff --git a/apps/renterd/hooks/useIsApcsEqDcs.tsx b/apps/renterd/hooks/useIsApcsEqDcs.tsx index 6b0a06ca6..01fb6f633 100644 --- a/apps/renterd/hooks/useIsApcsEqDcs.tsx +++ b/apps/renterd/hooks/useIsApcsEqDcs.tsx @@ -1,6 +1,6 @@ import { useAutopilotConfig, - useSettingContractSet, + useSettingsUpload, } from '@siafoundation/renterd-react' import { useApp } from '../contexts/app' @@ -10,10 +10,10 @@ export function useIsApcsEqDcs() { const apc = useAutopilotConfig({ disabled: autopilot.status !== 'on', }) - const css = useSettingContractSet() + const su = useSettingsUpload() return { - isValidating: apc.isValidating || css.isValidating, - data: apc.data?.contracts.set === css.data?.default, + isValidating: apc.isValidating || su.isValidating, + data: apc.data?.contracts.set === su.data?.defaultContractSet, } } diff --git a/apps/renterd/mock/mock.tsx b/apps/renterd/mock/mock.tsx index 361ccab33..069834057 100644 --- a/apps/renterd/mock/mock.tsx +++ b/apps/renterd/mock/mock.tsx @@ -1,6 +1,6 @@ import { SetupServer } from 'msw/node' import { HttpResponse, http } from 'msw' -import { Bucket, RedundancySettings } from '@siafoundation/renterd-types' +import { Bucket, SettingsUpload } from '@siafoundation/renterd-types' export function mockApiBusBuckets(server: SetupServer) { server.use( @@ -17,13 +17,20 @@ export function mockApiBusBuckets(server: SetupServer) { ) } -export function mockApiBusSettingRedundancy(server: SetupServer) { +export function mockApiBusSettingsUpload(server: SetupServer) { server.use( - http.get('/api/bus/setting/redundancy', () => { + http.get('/api/bus/settings/upload', () => { return HttpResponse.json({ - minShards: 10, - totalShards: 30, - } as RedundancySettings) + defaultContractSet: 'myset', + packing: { + enabled: true, + slabBufferMaxSizeSoft: 1, + }, + redundancy: { + minShards: 10, + totalShards: 30, + }, + } as SettingsUpload) }) ) } diff --git a/apps/renterd/pages/_app.tsx b/apps/renterd/pages/_app.tsx index 654a84a22..8db223837 100644 --- a/apps/renterd/pages/_app.tsx +++ b/apps/renterd/pages/_app.tsx @@ -4,6 +4,7 @@ import { AppProps } from 'next/app' import { Providers } from '../config/providers' import { routes } from '../config/routes' import { rootFontClasses } from '@siafoundation/fonts' +import { busStateRoute } from '@siafoundation/renterd-types' export default function App({ Component, @@ -15,6 +16,7 @@ export default function App({ diff --git a/libs/clusterd/src/index.ts b/libs/clusterd/src/index.ts index f3d34fb86..a80ec7225 100644 --- a/libs/clusterd/src/index.ts +++ b/libs/clusterd/src/index.ts @@ -16,7 +16,7 @@ export const clusterd = { nodes: [] as Node[], } -const maxTimeWaitingForAllNodesToStartup = 30_000 +const maxTimeWaitingForAllNodesToStartup = 60_000 const maxTimeWaitingForContractsToForm = 60_000 export async function setupCluster({ diff --git a/libs/react-core/src/useExchangeRate.tsx b/libs/react-core/src/useExchangeRate.tsx index 3abc36a0a..1047bf0df 100644 --- a/libs/react-core/src/useExchangeRate.tsx +++ b/libs/react-core/src/useExchangeRate.tsx @@ -87,7 +87,7 @@ export function useDaemonExplorerExchangeRate({ params: { currency, }, - disabled: !enabled || disabled, + disabled: !enabled || disabled || !currency, api, route: '/exchange-rate/siacoin/:currency', config: { diff --git a/libs/renterd-js/package.json b/libs/renterd-js/package.json index 760c0c409..9baf187bb 100644 --- a/libs/renterd-js/package.json +++ b/libs/renterd-js/package.json @@ -5,8 +5,7 @@ "license": "MIT", "dependencies": { "@siafoundation/renterd-types": "0.7.0", - "@siafoundation/request": "0.2.0", - "axios": "^0.27.2" + "@siafoundation/request": "0.2.0" }, "types": "./src/index.d.ts" } diff --git a/libs/renterd-js/src/bus.ts b/libs/renterd-js/src/bus.ts index 503d7dbea..e7922cfc8 100644 --- a/libs/renterd-js/src/bus.ts +++ b/libs/renterd-js/src/bus.ts @@ -50,7 +50,6 @@ import { ContractSetMetricsParams, ContractSetMetricsPayload, ContractSetMetricsResponse, - ContractSetSettings, ContractSetUpdateParams, ContractSetUpdatePayload, ContractSetUpdateResponse, @@ -72,7 +71,6 @@ import { ContractsReleasePayload, ContractsReleaseResponse, ContractsResponse, - GougingSettings, HostInteractionParams, HostInteractionPayload, HostInteractionResponse, @@ -133,18 +131,6 @@ import { ObjectsStatsParams, ObjectsStatsPayload, ObjectsStatsResponse, - PricePinSettings, - RedundancySettings, - S3AuthenticationSettings, - SettingParams, - SettingPayload, - SettingResponse, - SettingUpdateParams, - SettingUpdatePayload, - SettingUpdateResponse, - SettingsParams, - SettingsPayload, - SettingsResponse, SlabObjectsParams, SlabObjectsPayload, SlabObjectsResponse, @@ -166,7 +152,6 @@ import { TxPoolTransactionsParams, TxPoolTransactionsPayload, TxPoolTransactionsResponse, - UploadPackingSettings, WalletMetricsParams, WalletMetricsPayload, WalletMetricsResponse, @@ -226,8 +211,6 @@ import { busObjectsKeyRoute, busObjectsRenameRoute, busHostsRoute, - busSettingKeyRoute, - busSettingsRoute, busSlabKeyObjectsRoute, busStateRoute, busStatsObjectsRoute, @@ -248,9 +231,36 @@ import { AutopilotsResponse, busWalletEventsRoute, busListObjectsPrefixRoute, + SettingsGougingParams, + SettingsGougingPayload, + SettingsGougingResponse, + SettingsGougingUpdateParams, + SettingsGougingUpdatePayload, + SettingsGougingUpdateResponse, + SettingsPinnedParams, + SettingsPinnedPayload, + SettingsPinnedResponse, + SettingsPinnedUpdateParams, + SettingsPinnedUpdatePayload, + SettingsPinnedUpdateResponse, + SettingsS3Params, + SettingsS3Payload, + SettingsS3Response, + SettingsS3UpdateParams, + SettingsS3UpdatePayload, + SettingsS3UpdateResponse, + SettingsUploadParams, + SettingsUploadPayload, + SettingsUploadResponse, + SettingsUploadUpdateParams, + SettingsUploadUpdatePayload, + SettingsUploadUpdateResponse, + busSettingsGougingRoute, + busSettingsPinnedRoute, + busSettingsS3Route, + busSettingsUploadRoute, } from '@siafoundation/renterd-types' import { buildRequestHandler, initAxios } from '@siafoundation/request' -import { AxiosRequestConfig } from 'axios' export function Bus({ api, password }: { api: string; password?: string }) { const axios = initAxios(api, password) @@ -491,133 +501,46 @@ export function Bus({ api, password }: { api: string; password?: string }) { ObjectsStatsPayload, ObjectsStatsResponse >(axios, 'get', busStatsObjectsRoute), - settings: buildRequestHandler< - SettingsParams, - SettingsPayload, - SettingsResponse - >(axios, 'get', busSettingsRoute), - setting: function setting>({ - params, - config, - }: { - params: SettingParams - config?: AxiosRequestConfig - }) { - return buildRequestHandler< - SettingParams, - SettingPayload, - SettingResponse - >( - axios, - 'get', - busSettingKeyRoute - )({ params, config }) - }, - settingGouging: ({ config }: { config?: AxiosRequestConfig } = {}) => { - return buildRequestHandler< - SettingParams, - SettingPayload, - SettingResponse - >( - axios, - 'get', - busSettingKeyRoute - )({ - params: { - key: 'gouging', - }, - config, - }) - }, - settingRedundancy: ({ config }: { config?: AxiosRequestConfig } = {}) => { - return buildRequestHandler< - SettingParams, - SettingPayload, - SettingResponse - >( - axios, - 'get', - busSettingKeyRoute - )({ - params: { - key: 'redundancy', - }, - config, - }) - }, - settingContractSet: ({ config }: { config?: AxiosRequestConfig } = {}) => { - return buildRequestHandler< - SettingParams, - SettingPayload, - SettingResponse - >( - axios, - 'get', - busSettingKeyRoute - )({ - params: { - key: 'contractset', - }, - config, - }) - }, - settingUploadPacking: ({ - config, - }: { config?: AxiosRequestConfig } = {}) => { - return buildRequestHandler< - SettingParams, - SettingPayload, - SettingResponse - >( - axios, - 'get', - busSettingKeyRoute - )({ - params: { - key: 'uploadpacking', - }, - config, - }) - }, - settingS3Authentication: ({ - config, - }: { config?: AxiosRequestConfig } = {}) => { - return buildRequestHandler< - SettingParams, - SettingPayload, - SettingResponse - >( - axios, - 'get', - busSettingKeyRoute - )({ - params: { - key: 's3authentication', - }, - config, - }) - }, - settingPricePinning: ({ config }: { config?: AxiosRequestConfig } = {}) => { - return buildRequestHandler< - SettingParams, - SettingPayload, - SettingResponse - >( - axios, - 'get', - busSettingKeyRoute - )({ - params: { - key: 'pricepinning', - }, - config, - }) - }, - settingUpdate: buildRequestHandler< - SettingUpdateParams, - SettingUpdatePayload, - SettingUpdateResponse - >(axios, 'put', busSettingKeyRoute), + settingsGouging: buildRequestHandler< + SettingsGougingParams, + SettingsGougingPayload, + SettingsGougingResponse + >(axios, 'get', busSettingsGougingRoute), + settingsPinned: buildRequestHandler< + SettingsPinnedParams, + SettingsPinnedPayload, + SettingsPinnedResponse + >(axios, 'get', busSettingsPinnedRoute), + settingsS3: buildRequestHandler< + SettingsS3Params, + SettingsS3Payload, + SettingsS3Response + >(axios, 'get', busSettingsS3Route), + settingsUpload: buildRequestHandler< + SettingsUploadParams, + SettingsUploadPayload, + SettingsUploadResponse + >(axios, 'get', busSettingsUploadRoute), + settingsGougingUpdate: buildRequestHandler< + SettingsGougingUpdateParams, + SettingsGougingUpdatePayload, + SettingsGougingUpdateResponse + >(axios, 'put', busSettingsGougingRoute), + settingsPinnedUpdate: buildRequestHandler< + SettingsPinnedUpdateParams, + SettingsPinnedUpdatePayload, + SettingsPinnedUpdateResponse + >(axios, 'put', busSettingsPinnedRoute), + settingsS3Update: buildRequestHandler< + SettingsS3UpdateParams, + SettingsS3UpdatePayload, + SettingsS3UpdateResponse + >(axios, 'put', busSettingsS3Route), + settingsUploadUpdate: buildRequestHandler< + SettingsUploadUpdateParams, + SettingsUploadUpdatePayload, + SettingsUploadUpdateResponse + >(axios, 'put', busSettingsUploadRoute), alerts: buildRequestHandler( axios, 'get', diff --git a/libs/renterd-react/src/bus.ts b/libs/renterd-react/src/bus.ts index 4b9563eda..2906ddfb2 100644 --- a/libs/renterd-react/src/bus.ts +++ b/libs/renterd-react/src/bus.ts @@ -124,13 +124,6 @@ import { ObjectResponse, ObjectsStatsParams, ObjectsStatsResponse, - SettingParams, - SettingResponse, - SettingUpdateParams, - SettingUpdatePayload, - SettingUpdateResponse, - SettingsParams, - SettingsResponse, SlabObjectsParams, SlabObjectsResponse, SyncerAddressParams, @@ -186,8 +179,6 @@ import { busObjectsKeyRoute, busObjectsRenameRoute, busHostsRoute, - busSettingKeyRoute, - busSettingsRoute, busStateRoute, busStatsObjectsRoute, busSyncerAddrRoute, @@ -215,18 +206,12 @@ import { busMultipartListpartsRoute, busMultipartListuploadsRoute, busMultipartPartRoute, - GougingSettings, - ContractSetSettings, - RedundancySettings, - S3AuthenticationSettings, - UploadPackingSettings, ContractsPrunableParams, ContractsPrunableResponse, busContractsPrunableRoute, ContractSizeParams, ContractSizeResponse, busContractIdSize, - PricePinSettings, WalletSendParams, WalletSendPayload, WalletSendResponse, @@ -239,6 +224,30 @@ import { busWalletEventsRoute, busListObjectsPrefixRoute, busListObjectsRoute, + busSettingsGougingRoute, + busSettingsPinnedRoute, + busSettingsS3Route, + busSettingsUploadRoute, + SettingsGougingParams, + SettingsGougingResponse, + SettingsPinnedParams, + SettingsS3Response, + SettingsS3Params, + SettingsUploadResponse, + SettingsUploadParams, + SettingsGougingUpdateResponse, + SettingsGougingUpdatePayload, + SettingsGougingUpdateParams, + SettingsPinnedUpdateParams, + SettingsPinnedUpdatePayload, + SettingsPinnedUpdateResponse, + SettingsS3UpdateParams, + SettingsS3UpdatePayload, + SettingsS3UpdateResponse, + SettingsUploadUpdateParams, + SettingsUploadUpdatePayload, + SettingsUploadUpdateResponse, + SettingsPinnedResponse, } from '@siafoundation/renterd-types' // state @@ -744,88 +753,98 @@ export function useObjectStats( return useGetSwr({ ...args, route: busStatsObjectsRoute }) } -type Setting = Record | string - -export function useSettings( - args?: HookArgsSwr +export function useSettingsGouging( + args?: HookArgsSwr ) { - return useGetSwr({ ...args, route: busSettingsRoute }) + return useGetSwr({ ...args, route: busSettingsGougingRoute }) } -export function useSetting( - args: HookArgsSwr> +export function useSettingsPinned( + args?: HookArgsSwr ) { - return useGetSwr({ ...args, route: busSettingKeyRoute }) -} - -export function useSettingGouging(args?: HookArgsSwr) { - return useSetting({ - ...args, - params: { key: 'gouging' }, - }) + return useGetSwr({ ...args, route: busSettingsPinnedRoute }) } -export function useSettingContractSet( - args?: HookArgsSwr +export function useSettingsS3( + args?: HookArgsSwr ) { - return useSetting({ - ...args, - params: { key: 'contractset' }, - }) + return useGetSwr({ ...args, route: busSettingsS3Route }) } -export function useSettingRedundancy( - args?: HookArgsSwr +export function useSettingsUpload( + args?: HookArgsSwr ) { - return useSetting({ - ...args, - params: { key: 'redundancy' }, - }) + return useGetSwr({ ...args, route: busSettingsUploadRoute }) } -export function useSettingS3Authentication( - args?: HookArgsSwr +export function useSettingsGougingUpdate( + args?: HookArgsCallback< + SettingsGougingUpdateParams, + SettingsGougingUpdatePayload, + SettingsGougingUpdateResponse + > ) { - return useSetting({ - ...args, - params: { key: 's3authentication' }, - }) + return usePutFunc( + { + ...args, + route: busSettingsGougingRoute, + }, + async (mutate) => { + mutate((key) => key.startsWith(busSettingsGougingRoute)) + } + ) } -export function useSettingUploadPacking( - args?: HookArgsSwr +export function useSettingsPinnedUpdate( + args?: HookArgsCallback< + SettingsPinnedUpdateParams, + SettingsPinnedUpdatePayload, + SettingsPinnedUpdateResponse + > ) { - return useSetting({ - ...args, - params: { key: 'uploadpacking' }, - }) + return usePutFunc( + { + ...args, + route: busSettingsPinnedRoute, + }, + async (mutate) => { + mutate((key) => key.startsWith(busSettingsPinnedRoute)) + } + ) } -export function useSettingPricePinning( - args?: HookArgsSwr +export function useSettingsS3Update( + args?: HookArgsCallback< + SettingsS3UpdateParams, + SettingsS3UpdatePayload, + SettingsS3UpdateResponse + > ) { - return useSetting({ - ...args, - params: { key: 'pricepinning' }, - }) + return usePutFunc( + { + ...args, + route: busSettingsS3Route, + }, + async (mutate) => { + mutate((key) => key.startsWith(busSettingsS3Route)) + } + ) } -export function useSettingUpdate( +export function useSettingsUploadUpdate( args?: HookArgsCallback< - SettingUpdateParams, - SettingUpdatePayload, - SettingUpdateResponse + SettingsUploadUpdateParams, + SettingsUploadUpdatePayload, + SettingsUploadUpdateResponse > ) { return usePutFunc( { ...args, - route: busSettingKeyRoute, + route: busSettingsUploadRoute, }, - async (mutate, args) => { - mutate((key) => - key.startsWith(busSettingKeyRoute.replace(':key', args.params.key)) - ) + async (mutate) => { + mutate((key) => key.startsWith(busSettingsUploadRoute)) } ) } diff --git a/libs/renterd-types/src/autopilot.ts b/libs/renterd-types/src/autopilot.ts index bbb1d9d22..127742715 100644 --- a/libs/renterd-types/src/autopilot.ts +++ b/libs/renterd-types/src/autopilot.ts @@ -1,4 +1,4 @@ -import { AutopilotConfig, GougingSettings, RedundancySettings } from './types' +import { AutopilotConfig, SettingsGouging, SettingsRedundancy } from './types' import { BusStateResponse } from './bus' export const autopilotStateRoute = '/autopilot/state' @@ -31,14 +31,14 @@ export type AutopilotConfigUpdatePayload = AutopilotConfig export type AutopilotConfigUpdateResponse = void export type ConfigRecommendation = { - gougingSettings: GougingSettings + gougingSettings: SettingsGouging } export type AutopilotConfigEvaluateParams = void export type AutopilotConfigEvaluatePayload = { autopilotConfig: AutopilotConfig - gougingSettings: GougingSettings - redundancySettings: RedundancySettings + gougingSettings: SettingsGouging + redundancySettings: SettingsRedundancy } export type AutopilotConfigEvaluateResponse = { hosts: number diff --git a/libs/renterd-types/src/bus.ts b/libs/renterd-types/src/bus.ts index 202bb9932..e652a16c9 100644 --- a/libs/renterd-types/src/bus.ts +++ b/libs/renterd-types/src/bus.ts @@ -17,6 +17,10 @@ import { HostSettings, Obj, ObjectMetadata, + SettingsGouging, + SettingsPinned, + SettingsS3, + SettingsUpload, SlabSlice, } from './types' @@ -66,8 +70,10 @@ export const busObjectsKeyRoute = '/bus/objects/:key' export const busObjectsRenameRoute = '/bus/objects/rename' export const busStatsObjectsRoute = '/bus/stats/objects' export const busSettingRoute = '/bus/setting' -export const busSettingsRoute = '/bus/settings' -export const busSettingKeyRoute = '/bus/setting/:key' +export const busSettingsGougingRoute = '/bus/settings/gouging' +export const busSettingsPinnedRoute = '/bus/settings/pinned' +export const busSettingsS3Route = '/bus/settings/s3' +export const busSettingsUploadRoute = '/bus/settings/upload' export const busAlertsRoute = '/bus/alerts' export const busAlertsDismissRoute = '/bus/alerts/dismiss' export const busSlabKeyObjectsRoute = '/bus/slab/:key/objects' @@ -91,6 +97,10 @@ type BuildState = { commit: string OS: string buildTime: number + explorer: { + enabled: boolean + url: string + } } export type BusStateParams = void @@ -456,20 +466,6 @@ export type ObjectsStatsResponse = { totalUploadedSize: number // uploaded size of all objects including redundant sectors } -export type Setting = Record | string - -export type SettingsParams = void -export type SettingsPayload = void -export type SettingsResponse = string[] - -export type SettingParams = { key: string } -export type SettingPayload = void -export type SettingResponse = T - -export type SettingUpdateParams = { key: string } -export type SettingUpdatePayload = Setting -export type SettingUpdateResponse = void - // alerts export type AlertSeverity = 'info' | 'warning' | 'error' | 'critical' @@ -680,3 +676,37 @@ export type MultipartUploadAddPartPayload = { usedContracts?: Contract[] } export type MultipartUploadAddPartResponse = void + +// Settings + +export type SettingsGougingParams = void +export type SettingsGougingPayload = void +export type SettingsGougingResponse = SettingsGouging + +export type SettingsPinnedParams = void +export type SettingsPinnedPayload = void +export type SettingsPinnedResponse = SettingsPinned + +export type SettingsS3Params = void +export type SettingsS3Payload = void +export type SettingsS3Response = SettingsS3 + +export type SettingsUploadParams = void +export type SettingsUploadPayload = void +export type SettingsUploadResponse = SettingsUpload + +export type SettingsGougingUpdateParams = void +export type SettingsGougingUpdatePayload = SettingsGouging +export type SettingsGougingUpdateResponse = void + +export type SettingsPinnedUpdateParams = void +export type SettingsPinnedUpdatePayload = SettingsPinned +export type SettingsPinnedUpdateResponse = void + +export type SettingsS3UpdateParams = void +export type SettingsS3UpdatePayload = SettingsS3 +export type SettingsS3UpdateResponse = void + +export type SettingsUploadUpdateParams = void +export type SettingsUploadUpdatePayload = SettingsUpload +export type SettingsUploadUpdateResponse = void diff --git a/libs/renterd-types/src/types.ts b/libs/renterd-types/src/types.ts index 0db81005b..b9399f3c1 100644 --- a/libs/renterd-types/src/types.ts +++ b/libs/renterd-types/src/types.ts @@ -123,11 +123,9 @@ export type Obj = ObjectMetadata & metadata: ObjectUserMetadata } -export type ContractSetSettings = { - default: string -} +// Settings -export type GougingSettings = { +export type SettingsGouging = { maxStoragePrice: string maxDownloadPrice: string maxUploadPrice: string @@ -140,27 +138,32 @@ export type GougingSettings = { migrationSurchargeMultiplier: number } -export type UploadPackingSettings = { +export type SettingsUploadPacking = { enabled: boolean + slabBufferMaxSizeSoft: number } -export type RedundancySettings = { +export type SettingsRedundancy = { minShards: number totalShards: number } -export type ContractSpending = { - uploads: Currency - downloads: Currency - fundAccount: Currency +export type SettingsUpload = { + defaultContractSet: string + packing: SettingsUploadPacking + redundancy: SettingsRedundancy } -export type S3AuthenticationSettings = { +export type SettingsS3Authentication = { v4Keypairs: { [key: string]: string } } +export type SettingsS3 = { + authentication: SettingsS3Authentication +} + export type Pin = { pinned: boolean value: number @@ -176,22 +179,13 @@ export type AutopilotPins = { allowance: Pin } -// PricePinSettings holds the configuration for pinning certain settings to +// SettingsPinned holds the configuration for pinning certain settings to // a specific currency (e.g., USD). It uses a Forex API to fetch the current // exchange rate, allowing users to set prices in USD instead of SC. -export type PricePinSettings = { - // Enabled can be used to either enable or temporarily disable price - // pinning. If enabled, both the currency and the Forex endpoint URL - // must be valid. - enabled: boolean - +export type SettingsPinned = { // Currency is the external three-letter currency code. currency: CurrencyId | '' - // ForexEndpointURL is the endpoint that returns the exchange rate for - // Siacoin against the underlying currency. - forexEndpointURL: string - // Threshold is a percentage between 0 and 1 that determines when the // pinned settings are updated based on the exchange rate at the time. threshold: number @@ -204,6 +198,15 @@ export type PricePinSettings = { gougingSettingsPins: GougingSettingsPins } +// Contracts + +export type ContractSpending = { + deletions: Currency + fundAccount: Currency + sectorRoots: Currency + uploads: Currency +} + export type ContractState = 'pending' | 'active' | 'complete' | 'failed' export type Contract = {