Compare commits

...

35 Commits

Author SHA1 Message Date
Gala efdb8adab8 improve typing 2022-08-09 15:05:22 +02:00
Gala e8632c3762 Merge branch 'develop' into 306-wallet-balance-ui 2022-08-09 14:59:51 +02:00
Jędrzej Stuczyński fa354016e0 Removed useless into() conversion 2022-08-08 09:49:52 +01:00
Tommy Verrall 935ee765e9 Merge pull request #1498 from nymtech/feature/fix-envs
fix .env files for explorer.
2022-08-05 16:36:08 +01:00
tommy 4c8e59e6fc fix .env files for explorer. 2022-08-05 17:11:35 +02:00
Bogdan-Ștefan Neacşu 067f3e6f1a validator-api: handle SIGTERM (#1496)
* validator-api: handle SIGTERM

* Update CHANGELOG
2022-08-05 15:59:34 +03:00
Gala fb0b928598 Merge branch '306-wallet-balance-ui' of github.com:nymtech/nym into 306-wallet-balance-ui 2022-08-05 13:30:44 +02:00
Gala e0e9d03b7f Merge branch 'develop' into 306-wallet-balance-ui 2022-08-05 13:29:32 +02:00
Gala 6f09d46dce Merge pull request #1492 from nymtech/feature/delegating_ui_update
feat(wallet-delegation): new confirmation modal ui
2022-08-05 12:08:51 +02:00
Gala bdef48331b Merge pull request #1489 from nymtech/332-fix-vesting-ui
Wallet: Fixing dark mode colours in vesting shedule
2022-08-05 12:05:19 +02:00
Gala 51a6936e51 Merge pull request #1491 from nymtech/330-mix-wallet-ui
Wallet: Fix light mode bg and nav bar UI
2022-08-05 12:02:50 +02:00
Gala fd456d2952 Merge pull request #1490 from nymtech/334-ne-changes
NE: Filter by use routing score instead of stake parameter and adding filters info
2022-08-04 18:24:04 +02:00
Gala eee1abe593 removing extra styles 2022-08-04 18:15:12 +02:00
Gala fffad43937 Avoiding CAPS in button 2022-08-04 17:51:24 +02:00
Gala 3a79f43a8d styling 2022-08-04 17:23:32 +02:00
pierre 2e495f87ab refactor(wallet-delegation): ModalListItem component, pass colon in label value 2022-08-04 16:35:14 +02:00
Gala 57a9f18f5a fixing padding marging and wording 2022-08-04 16:30:33 +02:00
Gala 0c6a0a9cae Merge branch 'develop' into 334-ne-changes 2022-08-04 15:18:54 +02:00
Gala c80d8d354a adding nodes number info 2022-08-04 15:17:44 +02:00
Gala 3f544dbc69 adding Advanced filtering text and hover fix 2022-08-04 14:59:17 +02:00
pierre d1e1f15db0 Merge branch 'develop' into feature/delegating_ui_update 2022-08-04 13:26:41 +02:00
pierre 651c314182 feat(wallet-delegation): new confirmation modal ui 2022-08-04 13:20:00 +02:00
Gala a57545521d some refactor 2022-08-04 11:28:04 +02:00
Gala da60606921 hover bg color in dark and light mode 2022-08-03 17:20:46 +02:00
Gala 14f9bf7234 left nav spacing 2022-08-03 16:50:02 +02:00
Gala c1fa92869a fix light bg color 2022-08-03 16:49:47 +02:00
Gala c8533e3ec8 cleaning 2022-08-03 14:15:03 +02:00
Gala 06c4dd601d filter by use routing score instead of stake parameter 2022-08-03 14:06:33 +02:00
Gala 4ff80bbab2 fixing dark mode progress bar 2022-08-03 14:02:50 +02:00
Gala d7220b1fec adding info text in filters and renaming 2022-08-03 13:42:48 +02:00
Gala d92df9ada3 fixing dark mode colours 2022-08-03 12:24:21 +02:00
Gala 78ace473c7 balance page ui 2022-07-19 12:12:01 +02:00
Gala e877dfe224 buton hover bg and text 2022-07-19 12:12:01 +02:00
Gala 30c488484c balance page ui 2022-07-19 11:55:35 +02:00
Gala 8cd155b2ac buton hover bg and text 2022-07-18 17:53:28 +02:00
39 changed files with 346 additions and 266 deletions
+2
View File
@@ -37,6 +37,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- validator: fixed local docker-compose setup to work on Apple M1 ([#1329])
- explorer-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service ([#1482]).
- network-requester: fix filter for suffix-only domains ([#1487])
- validator-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service ([#1496]).
### Changed
@@ -77,6 +78,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1478]: https://github.com/nymtech/nym/pull/1478
[#1482]: https://github.com/nymtech/nym/pull/1482
[#1487]: https://github.com/nymtech/nym/pull/1487
[#1496]: https://github.com/nymtech/nym/pull/1496
## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22)
Generated
+1
View File
@@ -3325,6 +3325,7 @@ dependencies = [
"serde",
"serde_json",
"sqlx",
"task",
"thiserror",
"time 0.3.9",
"tokio",
+1 -1
View File
@@ -507,7 +507,7 @@ mod test {
for (expected, raw_display) in values {
let coin = DecCoin {
denom: Network::MAINNET.mix_denom().display.into(),
denom: Network::MAINNET.mix_denom().display,
amount: raw_display.parse().unwrap(),
};
let base = reg.attempt_convert_to_base_coin(coin).unwrap();
+1
View File
@@ -1,5 +1,6 @@
EXPLORER_API_URL=https://explorer.nymtech.net/api/v1
VALIDATOR_API_URL=https://validator.nymtech.net
VALIDATOR_URL=https://rpc.nyx.nodes.guru
BIG_DIPPER_URL=https://blocks.nymtech.net
CURRENCY_DENOM=unym
CURRENCY_STAKING_DENOM=unyx
+2 -1
View File
@@ -1,5 +1,6 @@
EXPLORER_API_URL=https://qa-explorer.nymtech.net/api/v1
VALIDATOR_API_URL=https://qa-validator.nymtech.net
VALIDATOR_API_URL=https://qa-validator-api.nymtech.net
VALIDATOR_URL=https://qa-validator.nymtech.net
BIG_DIPPER_URL=https://qa-blocks.nymtech.net
CURRENCY_DENOM=unymt
CURRENCY_STAKING_DENOM=unyxt
+2 -1
View File
@@ -1,6 +1,7 @@
// master APIs
export const API_BASE_URL = process.env.EXPLORER_API_URL;
export const VALIDATOR_API_BASE_URL = process.env.VALIDATOR_API_URL;
export const VALIDATOR_URL = process.env.VALIDATOR_URL;
export const BIG_DIPPER = process.env.BIG_DIPPER_URL;
// specific API routes
@@ -9,7 +10,7 @@ export const MIXNODE_PING = `${API_BASE_URL}/ping`;
export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`;
export const MIXNODE_API = `${API_BASE_URL}/mix-node`;
export const GATEWAYS_API = `${VALIDATOR_API_BASE_URL}/api/v1/gateways`;
export const VALIDATORS_API = `${VALIDATOR_API_BASE_URL}/validators`;
export const VALIDATORS_API = `${VALIDATOR_URL}/validators`;
export const BLOCK_API = `${VALIDATOR_API_BASE_URL}/block`;
export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`;
export const UPTIME_STORY_API = `${VALIDATOR_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this.
+15 -8
View File
@@ -6,7 +6,6 @@ import {
DialogContent,
DialogActions,
DialogTitle,
IconButton,
Slider,
Typography,
Box,
@@ -25,6 +24,7 @@ import { useIsMobile } from '../../hooks/useIsMobile';
const FilterItem = ({
label,
id,
tooltipInfo,
value,
marks,
scale,
@@ -36,6 +36,7 @@ const FilterItem = ({
}) => (
<Box sx={{ p: 2 }}>
<Typography gutterBottom>{label}</Typography>
<Typography fontSize={12}>{tooltipInfo}</Typography>
<Slider
value={value}
onChange={(e: Event, newValue: number | number[]) => onChange(id, newValue as number[])}
@@ -50,7 +51,7 @@ const FilterItem = ({
);
export const Filters = () => {
const { filterMixnodes, fetchMixnodes } = useMainContext();
const { filterMixnodes, fetchMixnodes, mixnodes } = useMainContext();
const { status } = useParams<{ status: MixnodeStatusWithAll | undefined }>();
const isMobile = useIsMobile();
@@ -129,17 +130,23 @@ export const Filters = () => {
variant={isMobile ? 'standard' : 'outlined'}
action={
<Button size="small" onClick={onClearFilters}>
Clear
CLEAR FILTERS
</Button>
}
sx={{ width: 300 }}
>
Filters applied
{mixnodes?.data?.length} mixnodes matched your criteria
</Alert>
</Snackbar>
<IconButton size="large" onClick={handleToggleShowFilters}>
<Tune />
</IconButton>
<Button
size="large"
variant="text"
color="inherit"
endIcon={<Tune />}
onClick={handleToggleShowFilters}
sx={{ textTransform: 'none' }}
>
Advanced filters
</Button>
<Dialog open={showFilters} onClose={handleToggleShowFilters} maxWidth="md" fullWidth>
<DialogTitle>Mixnode filters</DialogTitle>
<DialogContent dividers>
+20 -54
View File
@@ -18,6 +18,8 @@ export const generateFilterSchema = (upperSaturationValue?: number) => ({
{ label: '90', value: 90 },
{ label: '100', value: 100 },
],
tooltipInfo:
'As a delegator you want to chose nodes with lower profit margin, meaning more payout for their delegators',
},
stakeSaturation: {
label: 'Stake saturation (%)',
@@ -43,65 +45,29 @@ export const generateFilterSchema = (upperSaturationValue?: number) => ({
},
],
max: upperSaturationValue,
tooltipInfo: "Select nodes with <100% saturation. Any additional stake above 100% saturation won't get rewards",
},
stake: {
label: 'Stake (NYM)',
id: EnumFilterKey.stake,
value: [20, 90],
min: 20,
max: 90,
routingScore: {
label: 'Routing score (%)',
id: EnumFilterKey.routingScore,
value: [0, 100],
marks: [
{
value: 0,
label: '1',
},
{
value: 10,
label: '10',
},
{
value: 20,
label: '100',
},
{
value: 30,
label: '1k',
},
{
value: 40,
label: '10k',
},
{
value: 50,
label: '100k',
},
{
value: 60,
label: '1M',
},
{
value: 70,
label: '10M',
},
{
value: 80,
label: '100M',
},
{
value: 90,
label: '1B',
},
{ label: '0', value: 0 },
{ label: '10', value: 10 },
{ label: '20', value: 20 },
{ label: '30', value: 30 },
{ label: '40', value: 40 },
{ label: '50', value: 50 },
{ label: '60', value: 60 },
{ label: '70', value: 70 },
{ label: '80', value: 80 },
{ label: '90', value: 90 },
{ label: '100', value: 100 },
],
tooltipInfo: 'The higher the routing score the better the performance of the node and so its rewards',
},
});
const formatStakeValuesToMinorDenom = ([value_1, value_2]: number[]) => {
const lowerValue = 10 ** (value_1 / 10) * 1_000_000;
const upperValue = 10 ** (value_2 / 10) * 1_000_000;
return [lowerValue, upperValue];
};
const formatStakeSaturationValues = ([value_1, value_2]: number[]) => {
const lowerValue = value_1 / 100;
const upperValue = value_2 / 100;
@@ -110,7 +76,7 @@ const formatStakeSaturationValues = ([value_1, value_2]: number[]) => {
};
export const formatOnSave = (filters: TFilters) => ({
stake: formatStakeValuesToMinorDenom(filters.stake.value),
routingScore: filters.routingScore.value,
profitMargin: filters.profitMargin.value,
stakeSaturation: formatStakeSaturationValues(filters.stakeSaturation.value),
});
+2 -2
View File
@@ -102,8 +102,8 @@ export const MainContextProvider: React.FC = ({ children }) => {
m.mix_node.profit_margin_percent <= filters.profitMargin[1] &&
m.stake_saturation >= filters.stakeSaturation[0] &&
m.stake_saturation <= filters.stakeSaturation[1] &&
+m.pledge_amount.amount + +m.total_delegation.amount >= filters.stake[0] &&
+m.pledge_amount.amount + +m.total_delegation.amount <= filters.stake[1],
m.avg_uptime >= filters.routingScore[0] &&
m.avg_uptime <= filters.routingScore[1],
);
setMixnodes({ data: filtered, isLoading: false });
};
+2 -1
View File
@@ -4,7 +4,7 @@ import { Mark } from '@mui/base';
export enum EnumFilterKey {
profitMargin = 'profitMargin',
stakeSaturation = 'stakeSaturation',
stake = 'stake',
routingScore = 'routingScore',
}
export type TFilterItem = {
@@ -15,6 +15,7 @@ export type TFilterItem = {
min?: number;
max?: number;
scale?: (value: number) => number;
tooltipInfo?: string;
};
export type TFilters = { [key in EnumFilterKey]: TFilterItem };
@@ -6,7 +6,15 @@ import { AccountAvatar } from './AccountAvatar';
export const AccountOverview = ({ account, onClick }: { account: AccountEntry; onClick: () => void }) => (
<Button
startIcon={<AccountAvatar name={account.id} />}
sx={{ color: 'text.primary' }}
sx={{
color: 'text.primary',
'&:hover': (t) =>
t.palette.mode === 'dark'
? {
backgroundColor: 'rgba(255, 255, 255, 0.08)',
}
: {},
}}
onClick={onClick}
disableRipple
>
@@ -10,9 +10,9 @@ export default {
const Template: ComponentStory<typeof ConfirmTx> = (args) => (
<ConfirmTx {...args}>
<ModalListItem label="Transaction type" value="Bond" divider />
<ModalListItem label="Current bond" value="100 NYM" divider />
<ModalListItem label="Additional bond" value="50 NYM" divider />
<ModalListItem label="Transaction type:" value="Bond" divider />
<ModalListItem label="Current bond:" value="100 NYM" divider />
<ModalListItem label="Additional bond:" value="50 NYM" divider />
</ConfirmTx>
);
@@ -168,8 +168,8 @@ export const DelegateModal: React.FC<{
onPrev={resetFeeState}
onConfirm={handleOk}
>
<ModalListItem label="Node identity key" value={identityKey} divider />
<ModalListItem label="Amount" value={`${amount} ${denom.toUpperCase()}`} divider />
<ModalListItem label="Node identity key:" value={identityKey} divider />
<ModalListItem label="Amount:" value={`${amount} ${denom.toUpperCase()}`} divider />
</ConfirmTx>
);
}
@@ -184,23 +184,24 @@ export const DelegateModal: React.FC<{
}
}}
header={header || 'Delegate'}
subHeader="Delegate to mixnode"
okLabel={buttonText || 'Delegate stake'}
okDisabled={!isValidated}
sx={sx}
backdropProps={backdropProps}
>
<IdentityKeyFormField
required
fullWidth
placeholder="Node identity key"
onChanged={handleIdentityKeyChanged}
initialValue={identityKey}
readOnly={Boolean(initialIdentityKey)}
textFieldProps={{
autoFocus: !initialIdentityKey,
}}
/>
<Box sx={{ mt: 2 }}>
<IdentityKeyFormField
required
fullWidth
placeholder="Node identity key"
onChanged={handleIdentityKeyChanged}
initialValue={identityKey}
readOnly={Boolean(initialIdentityKey)}
textFieldProps={{
autoFocus: !initialIdentityKey,
}}
/>
</Box>
<Typography
component="div"
textAlign="left"
@@ -230,29 +231,30 @@ export const DelegateModal: React.FC<{
{errorAmount}
</Typography>
<Box sx={{ mt: 3 }}>
<ModalListItem label="Account balance" value={accountBalance} divider />
<ModalListItem label="Account balance:" value={accountBalance} divider strong />
</Box>
<ModalListItem label="Rewards payout interval" value={rewardInterval} hidden divider />
<ModalListItem label="Rewards payout interval:" value={rewardInterval} hidden divider />
<ModalListItem
label="Node profit margin"
label="Node profit margin:"
value={`${profitMarginPercentage ? `${profitMarginPercentage}%` : '-'}`}
hidden={profitMarginPercentage === undefined}
divider
/>
<ModalListItem
label="Node uptime"
label="Node avg. uptime:"
value={`${nodeUptimePercentage ? `${nodeUptimePercentage}%` : '-'}`}
hidden={nodeUptimePercentage === undefined}
divider
/>
<ModalListItem
label="Node est. reward per epoch"
label="Node est. reward per epoch:"
value={`${estimatedReward} ${denom.toUpperCase()}`}
hidden
divider
/>
<ModalListItem label="Est. fee for this transaction will be calculated in the next page" />
</SimpleModal>
);
};
@@ -29,9 +29,6 @@ const transactionForDarkTheme = {
url: 'https://sandbox-blocks.nymtech.net/transactions/11ED7B9E21534A9421834F52FED5103DC6E982949C06335F5E12EFC71DAF0CFO',
hash: '11ED7B9E21534A9421834F52FED5103DC6E982949C06335F5E12EFC71DAF0CF0',
};
const balance = '104 NYMT';
const balanceVested = '12 NYMT';
const recipient = 'nymt1923pujepxfnv8dqyxqrl078s4ysf3xn2p7z2xa';
const Content: React.FC<{ children: React.ReactElement<any, any>; handleClick: () => void }> = ({
children,
@@ -78,8 +75,6 @@ export const DelegateSuccess = () => {
status="success"
action="delegate"
message="You delegated 5 NYM"
recipient={recipient}
balance={balance}
transactions={theme.palette.mode === 'light' ? [transaction] : [transactionForDarkTheme]}
{...storybookStyles(theme)}
/>
@@ -99,8 +94,6 @@ export const UndelegateSuccess = () => {
status="success"
action="undelegate"
message="You undelegated 5 NYM"
recipient={recipient}
balance={balance}
transactions={theme.palette.mode === 'light' ? [transaction] : [transactionForDarkTheme]}
{...storybookStyles(theme)}
/>
@@ -120,8 +113,6 @@ export const RedeemSuccess = () => {
status="success"
action="redeem"
message="42 NYM"
recipient={recipient}
balance={balance}
transactions={
theme.palette.mode === 'light'
? [transaction, transaction]
@@ -145,9 +136,6 @@ export const RedeemWithVestedSuccess = () => {
status="success"
action="redeem"
message="42 NYM"
recipient={recipient}
balance={balance}
balanceVested={balanceVested}
transactions={
theme.palette.mode === 'light'
? [transaction, transaction]
@@ -171,8 +159,6 @@ export const RedeemAllSuccess = () => {
status="success"
action="redeem-all"
message="42 NYM"
recipient={recipient}
balance={balance}
transactions={
theme.palette.mode === 'light'
? [transaction, transaction]
@@ -196,8 +182,6 @@ export const Error = () => {
status="error"
action="redeem-all"
message="Minim esse veniam Lorem id velit Lorem eu eu est. Excepteur labore sunt do proident proident sint aliquip consequat Lorem sint non nulla ad excepteur."
recipient={recipient}
balance={balance}
transactions={theme.palette.mode === 'light' ? [transaction] : [transactionForDarkTheme]}
{...storybookStyles(theme)}
/>
@@ -1,9 +1,10 @@
import React from 'react';
import { Box, Button, Modal, Typography, SxProps } from '@mui/material';
import { Box, Button, Modal, Typography, SxProps, Stack } from '@mui/material';
import { Link } from '@nymproject/react/link/Link';
import { Console } from 'src/utils/console';
import { modalStyle } from '../Modals/styles';
import { LoadingModal } from '../Modals/LoadingModal';
import { ConfirmationModal } from '../Modals/ConfirmationModal';
export type ActionType = 'delegate' | 'undelegate' | 'redeem' | 'redeem-all' | 'compound';
@@ -15,9 +16,9 @@ const actionToHeader = (action: ActionType): string => {
case 'redeem-all':
return 'All rewards redeemed successfully';
case 'delegate':
return 'Delegation complete';
return 'Delegation successful';
case 'undelegate':
return 'Undelegation complete';
return 'Undelegation successful';
case 'compound':
return 'Rewards compounded successfully';
default:
@@ -29,9 +30,6 @@ export type DelegationModalProps = {
status: 'loading' | 'success' | 'error';
action: ActionType;
message?: string;
recipient?: string;
balance?: string;
balanceVested?: string;
transactions?: {
url: string;
hash: string;
@@ -45,20 +43,7 @@ export const DelegationModal: React.FC<
sx?: SxProps;
backdropProps?: object;
}
> = ({
status,
action,
message,
recipient,
balance,
balanceVested,
transactions,
open,
onClose,
children,
sx,
backdropProps,
}) => {
> = ({ status, action, message, transactions, open, onClose, children, sx, backdropProps }) => {
if (status === 'loading') return <LoadingModal sx={sx} backdropProps={backdropProps} />;
if (status === 'error') {
@@ -81,54 +66,28 @@ export const DelegationModal: React.FC<
}
transactions?.map((transaction) => Console.log('action', action, 'status', status, 'key', transaction.hash));
return (
<Modal open={open} onClose={onClose} BackdropProps={backdropProps}>
<Box sx={{ ...modalStyle, ...sx }} textAlign="center">
<Typography color={(theme) => theme.palette.success.main} mb={1}>
{actionToHeader(action)}
</Typography>
<Typography mb={3} color="text.primary">
{message}
</Typography>
{recipient && (
<Typography mb={1} fontSize="small" color={(theme) => theme.palette.text.secondary}>
Recipient: {recipient}
</Typography>
return (
<ConfirmationModal
open={open}
onConfirm={onClose || (() => {})}
title={actionToHeader(action)}
confirmButton="Done"
>
<Stack alignItems="center" spacing={2} mb={0}>
{message && <Typography>{message}</Typography>}
{transactions?.length === 1 && (
<Link href={transactions[0].url} target="_blank" sx={{ ml: 1 }} text="View on blockchain" noIcon />
)}
{balanceVested ? (
<>
<Typography mb={1} fontSize="small" color={(theme) => theme.palette.text.secondary}>
Your current balance: {balance?.toUpperCase()}
</Typography>
<Typography mb={1} fontSize="small" color={(theme) => theme.palette.text.secondary}>
({balanceVested.toUpperCase()} is unlocked in your vesting account)
</Typography>
</>
) : (
<Typography mb={1} fontSize="small" color={(theme) => theme.palette.text.secondary}>
Your current balance: {balance?.toUpperCase()}
</Typography>
)}
{transactions && (
<Typography mb={1} fontSize="small" color={(theme) => theme.palette.text.secondary}>
Check the transaction {transactions.length > 1 ? 'hashes' : 'hash'}:
{transactions.map((transaction) => (
<Link
key={transaction.hash}
href={transaction.url}
target="_blank"
sx={{ ml: 1 }}
text={transaction.hash.slice(0, 6)}
/>
{transactions && transactions.length > 1 && (
<Stack alignItems="center" spacing={1}>
<Typography>View the transactions on blockchain:</Typography>
{transactions.map(({ url, hash }) => (
<Link href={url} target="_blank" sx={{ ml: 1 }} text={hash.slice(0, 6)} key={hash} noIcon />
))}
</Typography>
</Stack>
)}
{children}
<Button variant="contained" sx={{ mt: 3 }} size="large" onClick={onClose}>
Finish
</Button>
</Box>
</Modal>
</Stack>
</ConfirmationModal>
);
};
@@ -55,7 +55,7 @@ export const UndelegateModal: React.FC<{
/>
<Box sx={{ mt: 3 }}>
<ModalListItem label="Delegation amount" value={`${amount} ${currency}`} divider />
<ModalListItem label="Delegation amount:" value={`${amount} ${currency}`} divider />
</Box>
<Typography mb={5} fontSize="smaller" sx={{ color: 'text.primary' }}>
@@ -13,5 +13,5 @@ const getValue = ({ fee, isLoading, error }: TFeeProps) => {
};
export const ModalFee = ({ fee, isLoading, error }: TFeeProps) => (
<ModalListItem label="Estimated fee for this operation" value={getValue({ fee, isLoading, error })} />
<ModalListItem label="Estimated fee for this operation:" value={getValue({ fee, isLoading, error })} />
);
@@ -7,20 +7,22 @@ export const ModalListItem: React.FC<{
divider?: boolean;
hidden?: boolean;
strong?: boolean;
value: React.ReactNode;
value?: React.ReactNode;
}> = ({ label, value, hidden, divider, strong }) => (
<Box sx={{ display: hidden ? 'none' : 'block' }}>
<Stack direction="row" justifyContent="space-between">
<Typography fontSize="smaller" fontWeight={strong ? 600 : undefined} sx={{ color: 'text.primary' }}>
{label}:
</Typography>
<Typography
fontSize="smaller"
fontWeight={strong ? 600 : undefined}
sx={{ color: 'text.primary', textTransform: 'uppercase' }}
>
{value}
{label}
</Typography>
{value && (
<Typography
fontSize="smaller"
fontWeight={strong ? 600 : undefined}
sx={{ color: 'text.primary', textTransform: 'uppercase' }}
>
{value}
</Typography>
)}
</Stack>
{divider && <ModalDivider />}
</Box>
+28 -3
View File
@@ -68,9 +68,16 @@ export const Nav = () => {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
marginLeft: 12,
marginRight: 12,
}}
>
<List disablePadding>
<List
disablePadding
sx={{
width: '100%',
}}
>
{routesSchema
.filter(({ mode }) => {
if (!mode) {
@@ -86,17 +93,35 @@ export const Nav = () => {
}
})
.map(({ Icon, onClick, label, route }) => (
<ListItem disableGutters key={label} onClick={onClick} sx={{ cursor: 'pointer' }}>
<ListItem
disableGutters
key={label}
onClick={onClick}
sx={{
cursor: 'pointer',
py: 2,
paddingLeft: 3.5,
borderRadius: 1,
'&:hover': { backgroundColor: (theme) => theme.palette.nym.nymWallet.hover.background },
}}
>
<ListItemIcon
sx={{
height: '20px',
minWidth: 30,
color: location.pathname === route ? 'primary.main' : 'text.primary',
}}
>
<Icon sx={{ fontSize: 20 }} />
<Icon
sx={{
fontSize: 20,
}}
/>
</ListItemIcon>
<ListItemText
sx={{
height: '20px',
margin: 0,
color: location.pathname === route ? 'primary.main' : 'text.primary',
'& .MuiListItemText-primary': {
fontSize: 14,
+10 -1
View File
@@ -40,7 +40,16 @@ export const NetworkSelector = () => {
<Button
variant="text"
color="primary"
sx={{ color: 'text.primary' }}
sx={{
color: 'text.primary',
fontSize: 14,
'&:hover': (t) =>
t.palette.mode === 'dark'
? {
backgroundColor: 'rgba(255, 255, 255, 0.08)',
}
: {},
}}
onClick={handleClick}
disableElevation
endIcon={<ArrowDropDown sx={{ color: (theme) => `1px solid ${theme.palette.text.primary}` }} />}
+4 -2
View File
@@ -1,4 +1,5 @@
import React from 'react';
import { SxProps } from '@mui/material';
import { Box, Card, CardContent, CardHeader } from '@mui/material';
import { styled, Theme } from '@mui/material/styles';
import { Title } from './Title';
@@ -12,16 +13,17 @@ const CardContentNoPadding = styled(CardContent)(() => ({
export const NymCard: React.FC<{
title: string | React.ReactElement;
titleSx?: SxProps;
subheader?: string;
Action?: React.ReactNode;
Icon?: React.ReactNode;
noPadding?: boolean;
borderless?: boolean;
dataTestid?: string;
}> = ({ title, subheader, Action, Icon, noPadding, borderless, children, dataTestid }) => (
}> = ({ title, titleSx, subheader, Action, Icon, noPadding, borderless, children, dataTestid }) => (
<Card variant="outlined" sx={{ overflow: 'auto', ...(borderless && { border: 'none', dropShadow: 'none' }) }}>
<CardHeader
sx={{ p: 3, color: (theme: Theme) => theme.palette.text.primary }}
sx={{ p: 3, color: (theme: Theme) => theme.palette.text.primary, ...titleSx }}
title={<Title title={title} Icon={Icon} />}
subheader={subheader}
data-testid={dataTestid || title}
@@ -39,11 +39,11 @@ export const SendDetailsModal = ({
backdropProps={backdropProps}
>
<Stack gap={0.5} sx={{ mt: 4 }}>
<ModalListItem label="From" value={fromAddress} divider />
<ModalListItem label="To" value={toAddress} divider />
<ModalListItem label="Amount" value={`${amount?.amount} ${denom.toUpperCase()}`} divider />
<ModalListItem label="From:" value={fromAddress} divider />
<ModalListItem label="To:" value={toAddress} divider />
<ModalListItem label="Amount:" value={`${amount?.amount} ${denom.toUpperCase()}`} divider />
<ModalListItem
label="Fee for this transaction"
label="Fee for this transaction:"
value={!fee ? 'n/a' : `${fee.amount?.amount} ${fee.amount?.denom}`}
divider
/>
@@ -77,8 +77,8 @@ export const SendInputModal = ({
</Typography>
</Stack>
<Stack gap={0.5} sx={{ mt: 2 }}>
<ModalListItem label="Account balance" value={balance} divider strong />
<ModalListItem label="Your address" value={fromAddress} divider />
<ModalListItem label="Account balance:" value={balance} divider strong />
<ModalListItem label="Your address:" value={fromAddress} divider />
<Typography fontSize="smaller" sx={{ color: 'text.primary' }}>
Est. fee for this transaction will be show on the next page
</Typography>
+4 -5
View File
@@ -26,26 +26,25 @@ export const ApplicationLayout: React.FC = ({ children }) => {
sx={{
background: (t) => t.palette.nym.nymWallet.nav.background,
overflow: 'auto',
py: 3,
px: 5,
py: 5,
}}
display="flex"
flexDirection="column"
justifyContent="space-between"
>
<Box>
<Box sx={{ mb: 4 }}>
<Box sx={{ ml: 5, mb: 3 }}>
<NymWordmark height={14} />
</Box>
<Nav />
</Box>
{appVersion && (
<Box color="#888" mt={8}>
<Box color="#888" ml={5} mt={8} fontSize={14}>
Version {appVersion}
</Box>
)}
</Box>
<Container maxWidth="xl">
<Container maxWidth="xl" sx={{ px: { sm: 5 } }}>
<AppBar />
<Box overflow="auto" sx={{ height: () => `calc(100% - ${theme.spacing(10)})` }}>
{children}
+1
View File
@@ -14,6 +14,7 @@ export const BalanceCard = () => {
return (
<NymCard
title="Balance"
titleSx={{ fontSize: 20 }}
data-testid="check-balance"
borderless
Action={<ClientAddress withCopy showEntireAddress />}
@@ -87,12 +87,12 @@ export const TransferModal = ({ onClose }: { onClose: () => void }) => {
) : (
<>
<ModalListItem
label="Unlocked transferrable tokens"
label="Unlocked transferrable tokens:"
value={`${userBalance.tokenAllocation?.spendable} ${clientDetails?.display_mix_denom.toUpperCase()}`}
divider
/>
<ModalListItem
label="Est. fee for this transaction"
label="Est. fee for this transaction:"
value={fee ? `${fee.amount?.amount} ${fee.amount?.denom}` : <CircularProgress size={15} />}
divider
/>
@@ -1,5 +1,6 @@
/* eslint-disable react/no-array-index-key */
import React, { useContext } from 'react';
import { useTheme } from '@mui/material/styles';
import { Box, Tooltip, Typography } from '@mui/material';
import { format } from 'date-fns';
import { AppContext } from '../../../context/main';
@@ -21,6 +22,9 @@ export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ perc
userBalance: { currentVestingPeriod, vestingAccountInfo },
} = useContext(AppContext);
const theme = useTheme();
const { mode } = theme.palette;
const nextPeriod =
typeof currentVestingPeriod === 'object' && !!vestingAccountInfo?.periods
? Number(vestingAccountInfo?.periods[currentVestingPeriod.In + 1]?.start_time)
@@ -29,19 +33,31 @@ export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ perc
return (
<Box display="flex" flexDirection="column" gap={1} position="relative" width="100%">
<svg width="100%" height="12">
<rect y="2" width="100%" height="6" rx="0" fill="#E6E6E6" />
<rect y="2" width={`${percentageComplete}%`} height="6" rx="0" fill="#121726" />
<rect y="2" width="100%" height="6" rx="0" fill="text.dark" />
<rect
y="2"
width={`${percentageComplete}%`}
height="6"
rx="0"
fill={mode === 'light' ? 'text.dark' : theme.palette.success.main}
/>
{vestingAccountInfo?.periods.map((period, i, arr) => (
<Marker
position={`${calculateMarkerPosition(arr.length, i)}%`}
color={+percentageComplete.toFixed(2) >= calculateMarkerPosition(arr.length, i) ? '#121726' : '#B9B9B9'}
color={
+percentageComplete.toFixed(2) >= calculateMarkerPosition(arr.length, i)
? mode === 'light'
? 'text.dark'
: theme.palette.success.main
: '#B9B9B9'
}
tooltipText={format(new Date(Number(period.start_time) * 1000), 'HH:mm do MMM yyyy')}
key={i}
/>
))}
<Marker
position="calc(100% - 4px)"
color={percentageComplete === 100 ? '#121726' : '#B9B9B9'}
color={percentageComplete === 100 ? (mode === 'light' ? 'text.dark' : theme.palette.success.main) : '#B9B9B9'}
tooltipText="End of vesting schedule"
/>
</svg>
+28 -4
View File
@@ -65,20 +65,44 @@ const VestingSchedule = () => {
))}
</TableRow>
<TableRow>
<TableCell sx={{ borderBottom: 'none', textTransform: 'uppercase' }}>
<TableCell
sx={{
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
borderBottom: 'none',
textTransform: 'uppercase',
}}
>
{userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '}
{clientDetails?.display_mix_denom.toUpperCase()}
</TableCell>
<TableCell align="left" sx={{ borderBottom: 'none' }}>
<TableCell
align="left"
sx={{
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
borderBottom: 'none',
}}
>
{vestingPeriod(userBalance.currentVestingPeriod, userBalance.originalVesting?.number_of_periods)}
</TableCell>
<TableCell sx={{ borderBottom: 'none' }}>
<TableCell
sx={{
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
borderBottom: 'none',
}}
>
<Box display="flex" alignItems="center" gap={1}>
<Typography variant="body2">{`${vestedPercentage}%`}</Typography>
<VestingTimeline percentageComplete={vestedPercentage} />
</Box>
</TableCell>
<TableCell sx={{ borderBottom: 'none', textTransform: 'uppercase' }} align="right">
<TableCell
sx={{
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
borderBottom: 'none',
textTransform: 'uppercase',
}}
align="right"
>
{userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '}
{clientDetails?.display_mix_denom.toUpperCase()}
</TableCell>
@@ -20,8 +20,8 @@ export const ConfirmationModal = ({
}) => (
<SimpleModal header="Bond confirmation" open onOk={onConfirm} okLabel="Confirm" hideCloseIcon onBack={onPrev}>
<Box sx={{ mt: 3 }}>
<ModalListItem label="Mixnode identity" value={identity} />
<ModalListItem label="Amount" value={`${amount.amount} ${amount.denom}`} />
<ModalListItem label="Mixnode identity:" value={identity} />
<ModalListItem label="Amount:" value={`${amount.amount} ${amount.denom}`} />
<ModalFee fee={fee} isLoading={false} />
</Box>
</SimpleModal>
+1 -5
View File
@@ -138,7 +138,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
setConfirmationModalProps({
status: 'success',
action: 'delegate',
message: 'Delegations can take up to one hour to process',
message: 'This operation can take up to one hour to process',
...balances,
transactions: [
{ url: `${urls(network).blockExplorer}/transaction/${tx.transaction_hash}`, hash: tx.transaction_hash },
@@ -240,11 +240,9 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
try {
const txs = await claimRewards(identityKey, fee);
const bal = await userBalance();
setConfirmationModalProps({
status: 'success',
action: 'redeem',
balance: bal?.printable_balance || '-',
transactions: txs.map((tx) => ({
url: `${urls(network).blockExplorer}/transaction/${tx.transaction_hash}`,
hash: tx.transaction_hash,
@@ -270,11 +268,9 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
try {
const txs = await compoundRewards(identityKey, fee);
const bal = await userBalance();
setConfirmationModalProps({
status: 'success',
action: 'compound',
balance: bal?.printable_balance || '-',
transactions: txs.map((tx) => ({
url: `${urls(network).blockExplorer}/transaction/${tx.transaction_hash}`,
hash: tx.transaction_hash,
+8 -5
View File
@@ -64,6 +64,9 @@ declare module '@mui/material/styles' {
nav: {
background: string;
};
hover: {
background: string;
};
}
/**
@@ -82,7 +85,7 @@ declare module '@mui/material/styles' {
/**
* Add anything not palette related to the theme here
*/
interface NymTheme {}
interface NymTheme { }
/**
* This augments the definitions of the MUI Theme with the Nym theme, as well as
@@ -90,8 +93,8 @@ declare module '@mui/material/styles' {
*
* IMPORTANT: only add extensions to the interfaces above, do not modify the lines below
*/
interface Theme extends NymTheme {}
interface ThemeOptions extends Partial<NymTheme> {}
interface Palette extends NymPaletteAndNymWalletPalette {}
interface PaletteOptions extends NymPaletteAndNymWalletPaletteOptions {}
interface Theme extends NymTheme { }
interface ThemeOptions extends Partial<NymTheme> { }
interface Palette extends NymPaletteAndNymWalletPalette { }
interface PaletteOptions extends NymPaletteAndNymWalletPaletteOptions { }
}
+7 -1
View File
@@ -56,12 +56,15 @@ const darkMode: NymPaletteVariant = {
nav: {
background: '#292E34',
},
hover: {
background: '#36393E',
},
};
const lightMode: NymPaletteVariant = {
mode: 'light',
background: {
main: '#E5E5E5',
main: '#F4F6F8',
paper: '#FFFFFF',
warn: '#FFE600',
grey: '#F5F5F5',
@@ -80,6 +83,9 @@ const lightMode: NymPaletteVariant = {
nav: {
background: '#FFFFFF',
},
hover: {
background: '#F9F9F9',
},
};
/**
+1
View File
@@ -58,6 +58,7 @@ gateway-client = { path="../common/client-libs/gateway-client" }
mixnet-contract-common = { path= "../common/cosmwasm-smart-contracts/mixnet-contract" }
multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" }
nymsphinx = { path="../common/nymsphinx" }
task = { path = "../common/task" }
topology = { path="../common/topology" }
validator-api-requests = { path = "validator-api-requests" }
validator-client = { path="../common/client-libs/validator-client", features = ["nymd-client"] }
+17 -10
View File
@@ -14,6 +14,7 @@ use okapi::openapi3::OpenApi;
use rocket::Route;
use rocket_okapi::openapi_get_routes_spec;
use rocket_okapi::settings::OpenApiSettings;
use task::ShutdownListener;
use rocket::fairing::AdHoc;
use serde::Serialize;
@@ -252,20 +253,26 @@ impl<C> ValidatorCacheRefresher<C> {
Ok(())
}
pub(crate) async fn run(&self)
pub(crate) async fn run(&self, mut shutdown: ShutdownListener)
where
C: CosmWasmClient + Sync,
{
let mut interval = time::interval(self.caching_interval);
loop {
interval.tick().await;
if let Err(err) = self.refresh_cache().await {
error!("Failed to refresh validator cache - {}", err);
} else {
// relaxed memory ordering is fine here. worst case scenario network monitor
// will just have to wait for an additional backoff to see the change.
// And so this will not really incur any performance penalties by setting it every loop iteration
self.cache.initialised.store(true, Ordering::Relaxed)
while !shutdown.is_shutdown() {
tokio::select! {
_ = interval.tick() => {
if let Err(err) = self.refresh_cache().await {
error!("Failed to refresh validator cache - {}", err);
} else {
// relaxed memory ordering is fine here. worst case scenario network monitor
// will just have to wait for an additional backoff to see the change.
// And so this will not really incur any performance penalties by setting it every loop iteration
self.cache.initialised.store(true, Ordering::Relaxed)
}
}
_ = shutdown.recv() => {
trace!("UpdateHandler: Received shutdown");
}
}
}
}
+50 -12
View File
@@ -31,6 +31,7 @@ use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use std::{fs, process};
use task::ShutdownNotifier;
use tokio::sync::Notify;
// use validator_client::nymd::SigningNymdClient;
// use validator_client::ValidatorClientError;
@@ -226,14 +227,44 @@ fn parse_args<'a>() -> ArgMatches<'a> {
base_app.get_matches()
}
async fn wait_for_interrupt() {
if let Err(e) = tokio::signal::ctrl_c().await {
error!(
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
e
);
async fn wait_for_interrupt(mut shutdown: ShutdownNotifier) {
wait_for_signal().await;
log::info!("Sending shutdown");
shutdown.signal_shutdown().ok();
log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
shutdown.wait_for_shutdown().await;
log::info!("Stopping nym validator API");
}
#[cfg(unix)]
async fn wait_for_signal() {
use tokio::signal::unix::{signal, SignalKind};
let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel");
let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel");
tokio::select! {
_ = tokio::signal::ctrl_c() => {
log::info!("Received SIGINT");
},
_ = sigterm.recv() => {
log::info!("Received SIGTERM");
}
_ = sigquit.recv() => {
log::info!("Received SIGQUIT");
}
}
}
#[cfg(not(unix))]
async fn wait_for_signal() {
tokio::select! {
_ = tokio::signal::ctrl_c() => {
log::info!("Received SIGINT");
},
}
println!("Received SIGINT - the network monitor will terminate now");
}
fn setup_logging() {
@@ -547,6 +578,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
let signing_nymd_client = Client::new_signing(&config);
let liftoff_notify = Arc::new(Notify::new());
let shutdown = ShutdownNotifier::default();
// let's build our rocket!
let rocket = setup_rocket(
@@ -569,7 +601,8 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
// setup our daily uptime updater. Note that if network monitor is disabled, then we have
// no data for the updates and hence we don't need to start it up
let uptime_updater = HistoricalUptimeUpdater::new(storage.clone());
tokio::spawn(async move { uptime_updater.run().await });
let shutdown_listener = shutdown.subscribe();
tokio::spawn(async move { uptime_updater.run(shutdown_listener).await });
// spawn the cache refresher
let validator_cache_refresher = ValidatorCacheRefresher::new(
@@ -578,7 +611,8 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
validator_cache.clone(),
Some(storage.clone()),
);
tokio::spawn(async move { validator_cache_refresher.run().await });
let shutdown_listener = shutdown.subscribe();
tokio::spawn(async move { validator_cache_refresher.run(shutdown_listener).await });
// spawn rewarded set updater
let mut rewarded_set_updater =
@@ -593,11 +627,15 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
None,
);
let shutdown_listener = shutdown.subscribe();
// spawn our cacher
tokio::spawn(async move { validator_cache_refresher.run().await });
tokio::spawn(async move { validator_cache_refresher.run(shutdown_listener).await });
}
// launch the rocket!
// Rocket handles shutdown on it's own, but its shutdown handling should be incorporated
// with that of the rest of the tasks.
// Currently it's runtime is forcefully terminated once the validator-api exits.
let shutdown_handle = rocket.shutdown();
tokio::spawn(rocket.launch());
@@ -610,12 +648,12 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
// we're ready to go! spawn the network monitor!
let runnables = monitor_builder.build().await;
runnables.spawn_tasks();
runnables.spawn_tasks(&shutdown);
} else {
info!("Network monitoring is disabled.");
}
wait_for_interrupt().await;
wait_for_interrupt(shutdown).await;
shutdown_handle.notify();
Ok(())
+6 -3
View File
@@ -6,6 +6,7 @@ use crypto::asymmetric::{encryption, identity};
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use std::sync::Arc;
use task::ShutdownNotifier;
use crate::config::Config;
use crate::contract_cache::ValidatorCache;
@@ -131,11 +132,13 @@ impl NetworkMonitorRunnables {
// TODO: note, that is not exactly doing what we want, because when
// `ReceivedProcessor` is constructed, it already spawns a future
// this needs to be refactored!
pub(crate) fn spawn_tasks(self) {
pub(crate) fn spawn_tasks(self, shutdown: &ShutdownNotifier) {
let mut packet_receiver = self.packet_receiver;
let mut monitor = self.monitor;
tokio::spawn(async move { packet_receiver.run().await });
tokio::spawn(async move { monitor.run().await });
let shutdown_listener = shutdown.subscribe();
tokio::spawn(async move { packet_receiver.run(shutdown_listener).await });
let shutdown_listener = shutdown.subscribe();
tokio::spawn(async move { monitor.run(shutdown_listener).await });
}
}
@@ -12,6 +12,7 @@ use crate::storage::ValidatorApiStorage;
use log::{debug, error, info};
use std::collections::{HashMap, HashSet};
use std::process;
use task::ShutdownListener;
use tokio::time::{sleep, Duration, Instant};
pub(crate) mod gateway_clients_cache;
@@ -296,7 +297,7 @@ impl Monitor {
self.test_nonce += 1;
}
pub(crate) async fn run(&mut self) {
pub(crate) async fn run(&mut self, mut shutdown: ShutdownListener) {
self.received_processor.start_receiving();
// wait for validator cache to be ready
@@ -308,9 +309,13 @@ impl Monitor {
.spawn_gateways_pinger(self.gateway_ping_interval);
let mut run_interval = tokio::time::interval(self.run_interval);
loop {
run_interval.tick().await;
self.test_run().await;
while !shutdown.is_shutdown() {
tokio::select! {
_ = run_interval.tick() => self.test_run().await,
_ = shutdown.recv() => {
trace!("UpdateHandler: Received shutdown");
}
}
}
}
}
@@ -7,6 +7,7 @@ use crypto::asymmetric::identity;
use futures::channel::mpsc;
use futures::StreamExt;
use gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver};
use task::ShutdownListener;
pub(crate) type GatewayClientUpdateSender = mpsc::UnboundedSender<GatewayClientUpdate>;
pub(crate) type GatewayClientUpdateReceiver = mpsc::UnboundedReceiver<GatewayClientUpdate>;
@@ -55,8 +56,8 @@ impl PacketReceiver {
.expect("packet processor seems to have crashed!");
}
pub(crate) async fn run(&mut self) {
loop {
pub(crate) async fn run(&mut self, mut shutdown: ShutdownListener) {
while !shutdown.is_shutdown() {
tokio::select! {
// unwrap here is fine as it can only return a `None` if the PacketSender has died
// and if that was the case, then the entire monitor is already in an undefined state
@@ -67,6 +68,9 @@ impl PacketReceiver {
Some((_gateway_id, message)) = self.gateways_reader.stream_map().next() => {
self.process_gateway_messages(message)
}
_ = shutdown.recv() => {
trace!("UpdateHandler: Received shutdown");
}
}
}
}
@@ -7,6 +7,7 @@ use crate::node_status_api::models::{
use crate::node_status_api::ONE_DAY;
use crate::storage::ValidatorApiStorage;
use log::error;
use task::ShutdownListener;
use time::OffsetDateTime;
use tokio::time::sleep;
@@ -67,18 +68,23 @@ impl HistoricalUptimeUpdater {
Ok(())
}
pub(crate) async fn run(&self) {
loop {
// start any updates a day after starting the task so that we would have complete data
sleep(ONE_DAY).await;
if let Err(err) = self.update_uptimes().await {
// normally that would have been a warning rather than an error,
// however, in this case it implies some underlying issues with our database
// that might affect the entire program
error!(
"We failed to update daily uptimes of active nodes - {}",
err
)
pub(crate) async fn run(&self, mut shutdown: ShutdownListener) {
while !shutdown.is_shutdown() {
tokio::select! {
_ = sleep(ONE_DAY) => {
if let Err(err) = self.update_uptimes().await {
// normally that would have been a warning rather than an error,
// however, in this case it implies some underlying issues with our database
// that might affect the entire program
error!(
"We failed to update daily uptimes of active nodes - {}",
err
)
}
}
_ = shutdown.recv() => {
trace!("UpdateHandler: Received shutdown");
}
}
}
}