From 56a952749768dc984d5f4358a8f71cc4162de965 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 13 Sep 2021 13:30:05 +0100 Subject: [PATCH 01/16] use wrapper functions for send and delegate --- tauri-wallet/src/requests/index.ts | 27 ++++++++++++++++++- .../src/routes/delegate/DelegateForm.tsx | 23 ++++++++++------ tauri-wallet/src/routes/send/SendWizard.tsx | 9 +++---- tauri-wallet/src/types/rust/index.ts | 5 ++-- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/tauri-wallet/src/requests/index.ts b/tauri-wallet/src/requests/index.ts index 62629f72c0..df82000330 100644 --- a/tauri-wallet/src/requests/index.ts +++ b/tauri-wallet/src/requests/index.ts @@ -1,5 +1,13 @@ import { invoke } from '@tauri-apps/api' -import { Coin, Operation, TCreateAccount, TSignInWithMnemonic } from '../types' +import { + Coin, + DelegationResult, + EnumNodeType, + Operation, + TauriTxResult, + TCreateAccount, + TSignInWithMnemonic, +} from '../types' export const createAccount = async (): Promise => await invoke('create_new_account') @@ -17,3 +25,20 @@ export const majorToMinor = async (amount: string): Promise => export const getGasFee = async (operation: Operation): Promise => await invoke('get_fee', { operation }) + +export const delegatedToMixnode = async ({ + type, + identity, + amount, +}: { + type: EnumNodeType + identity: string + amount: Coin +}): Promise => + await invoke(`delegate_to_${type}`, { identity, amount }) + +export const send = async (args: { + amount: Coin + address: string + memo: string +}): Promise => await invoke('send', args) diff --git a/tauri-wallet/src/routes/delegate/DelegateForm.tsx b/tauri-wallet/src/routes/delegate/DelegateForm.tsx index 5b415b00c2..179b85533d 100644 --- a/tauri-wallet/src/routes/delegate/DelegateForm.tsx +++ b/tauri-wallet/src/routes/delegate/DelegateForm.tsx @@ -11,13 +11,12 @@ import { } from '@material-ui/core' import { useForm } from 'react-hook-form' import { NodeTypeSelector } from '../../components/NodeTypeSelector' -import { EnumNodeType, TFee } from '../../types' +import { DelegationResult, EnumNodeType, TFee } from '../../types' import { yupResolver } from '@hookform/resolvers/yup' import { validationSchema } from './validationSchema' -import { invoke } from '@tauri-apps/api' import { Alert } from '@material-ui/lab' import { ClientContext } from '../../context/main' -import { majorToMinor } from '../../requests' +import { delegatedToMixnode, majorToMinor } from '../../requests' type TDelegateForm = { nodeType: EnumNodeType @@ -58,14 +57,22 @@ export const DelegateForm = ({ const onSubmit = async (data: TDelegateForm) => { const amount = await majorToMinor(data.amount) - - await invoke(`delegate_to_${data.nodeType}`, { + console.log({ + type: data.nodeType, identity: data.identity, amount, }) - .then((res: any) => { - console.log(res) - onSuccess(res) + delegatedToMixnode({ + type: data.nodeType, + identity: data.identity, + amount, + }) + .then((res) => { + const successResponse = res as DelegationResult + console.log(successResponse) + onSuccess( + `Successfully delated ${data.amount} punk to ${successResponse.source_address}` + ) getBalance.fetchBalance() }) .catch((e) => { diff --git a/tauri-wallet/src/routes/send/SendWizard.tsx b/tauri-wallet/src/routes/send/SendWizard.tsx index 11468a7e5d..8438f9ad95 100644 --- a/tauri-wallet/src/routes/send/SendWizard.tsx +++ b/tauri-wallet/src/routes/send/SendWizard.tsx @@ -3,14 +3,13 @@ import { useForm, FormProvider } from 'react-hook-form' import { yupResolver } from '@hookform/resolvers/yup' import { Button, Step, StepLabel, Stepper, Theme } from '@material-ui/core' import { useTheme } from '@material-ui/styles' -import { invoke } from '@tauri-apps/api' import { SendForm } from './SendForm' import { SendReview } from './SendReview' import { SendConfirmation } from './SendConfirmation' import { ClientContext } from '../../context/main' import { validationSchema } from './validationSchema' -import { TauriTxResult } from '../../types/rust/tauritxresult' -import { majorToMinor } from '../../requests' +import { TauriTxResult } from '../../types' +import { majorToMinor, send } from '../../requests' const defaultValues = { amount: '', @@ -64,7 +63,7 @@ export const SendWizard = () => { const formState = methods.getValues() const amount = await majorToMinor(formState.amount) - invoke('send', { + send({ amount, address: formState.to, memo: formState.memo, @@ -74,7 +73,7 @@ export const SendWizard = () => { setActiveStep((s) => s + 1) setConfirmedData({ ...details, - amount: { denom: 'punk', amount: formState.amount }, + amount: { denom: 'Major', amount: formState.amount }, }) setIsLoading(false) getBalance.fetchBalance() diff --git a/tauri-wallet/src/types/rust/index.ts b/tauri-wallet/src/types/rust/index.ts index 5d2ff697fc..74ca39a7df 100644 --- a/tauri-wallet/src/types/rust/index.ts +++ b/tauri-wallet/src/types/rust/index.ts @@ -1,8 +1,9 @@ export * from './balance' export * from './coin' -export * from './mixnode' +export * from './delegationresult' +export * from './denom' export * from './gateway' export * from './mixnode' +export * from './operation' export * from './tauritxresult' export * from './transactiondetails' -export * from './operation' From 6954b383a79386864323abc0b577c91706b90fa9 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 13 Sep 2021 14:26:21 +0100 Subject: [PATCH 02/16] form updates --- tauri-wallet/src/requests/index.ts | 11 ++++++++++- tauri-wallet/src/routes/bond/BondForm.tsx | 2 +- tauri-wallet/src/routes/delegate/DelegateForm.tsx | 14 ++++---------- tauri-wallet/src/routes/delegate/index.tsx | 11 ++++++++--- .../src/routes/undelegate/UndelegateForm.tsx | 9 +++++---- tauri-wallet/src/routes/undelegate/index.tsx | 10 ++++++++-- 6 files changed, 36 insertions(+), 21 deletions(-) diff --git a/tauri-wallet/src/requests/index.ts b/tauri-wallet/src/requests/index.ts index df82000330..db63db1b45 100644 --- a/tauri-wallet/src/requests/index.ts +++ b/tauri-wallet/src/requests/index.ts @@ -26,7 +26,7 @@ export const majorToMinor = async (amount: string): Promise => export const getGasFee = async (operation: Operation): Promise => await invoke('get_fee', { operation }) -export const delegatedToMixnode = async ({ +export const delegate = async ({ type, identity, amount, @@ -37,6 +37,15 @@ export const delegatedToMixnode = async ({ }): Promise => await invoke(`delegate_to_${type}`, { identity, amount }) +export const undelegate = async ({ + type, + identity, +}: { + type: EnumNodeType + identity: string +}): Promise => + await invoke(`undelegate_from_${type}`, { identity }) + export const send = async (args: { amount: Coin address: string diff --git a/tauri-wallet/src/routes/bond/BondForm.tsx b/tauri-wallet/src/routes/bond/BondForm.tsx index 710881b6e1..21e89505fa 100644 --- a/tauri-wallet/src/routes/bond/BondForm.tsx +++ b/tauri-wallet/src/routes/bond/BondForm.tsx @@ -104,7 +104,7 @@ export const BondForm = ({ const formattedData = formatData(data) await invoke(`bond_${data.nodeType}`, { [data.nodeType]: formattedData, - bond: { amount: formattedData?.amount, denom: 'punk' }, + bond: { amount: formattedData?.amount, denom: 'Major' }, }) .then((res: any) => { onSuccess(res) diff --git a/tauri-wallet/src/routes/delegate/DelegateForm.tsx b/tauri-wallet/src/routes/delegate/DelegateForm.tsx index 179b85533d..9765837532 100644 --- a/tauri-wallet/src/routes/delegate/DelegateForm.tsx +++ b/tauri-wallet/src/routes/delegate/DelegateForm.tsx @@ -16,7 +16,7 @@ import { yupResolver } from '@hookform/resolvers/yup' import { validationSchema } from './validationSchema' import { Alert } from '@material-ui/lab' import { ClientContext } from '../../context/main' -import { delegatedToMixnode, majorToMinor } from '../../requests' +import { delegate, majorToMinor } from '../../requests' type TDelegateForm = { nodeType: EnumNodeType @@ -57,21 +57,15 @@ export const DelegateForm = ({ const onSubmit = async (data: TDelegateForm) => { const amount = await majorToMinor(data.amount) - console.log({ - type: data.nodeType, - identity: data.identity, - amount, - }) - delegatedToMixnode({ + + await delegate({ type: data.nodeType, identity: data.identity, amount, }) .then((res) => { - const successResponse = res as DelegationResult - console.log(successResponse) onSuccess( - `Successfully delated ${data.amount} punk to ${successResponse.source_address}` + `Successfully delegated ${data.amount} punk to ${res.source_address}` ) getBalance.fetchBalance() }) diff --git a/tauri-wallet/src/routes/delegate/index.tsx b/tauri-wallet/src/routes/delegate/index.tsx index b128c7168c..f329e6c126 100644 --- a/tauri-wallet/src/routes/delegate/index.tsx +++ b/tauri-wallet/src/routes/delegate/index.tsx @@ -8,7 +8,7 @@ import { EnumRequestStatus, RequestStatus, } from '../../components/RequestStatus' -import { Alert } from '@material-ui/lab' +import { Alert, AlertTitle } from '@material-ui/lab' import { TFee } from '../../types' import { getGasFee } from '../../requests' @@ -76,7 +76,12 @@ export const Delegate = () => { An error occurred with the request: {message} } - Success={{message}} + Success={ + + Delegation complete + {message} + + } />
{ setStatus(EnumRequestStatus.initial) }} > - Resend? + Finish
diff --git a/tauri-wallet/src/routes/undelegate/UndelegateForm.tsx b/tauri-wallet/src/routes/undelegate/UndelegateForm.tsx index b724bb5586..61bdf0b920 100644 --- a/tauri-wallet/src/routes/undelegate/UndelegateForm.tsx +++ b/tauri-wallet/src/routes/undelegate/UndelegateForm.tsx @@ -10,12 +10,12 @@ import { } from '@material-ui/core' import { Alert } from '@material-ui/lab' import { useTheme } from '@material-ui/styles' -import { invoke } from '@tauri-apps/api' import { yupResolver } from '@hookform/resolvers/yup' import { validationSchema } from './validationSchema' import { NodeTypeSelector } from '../../components/NodeTypeSelector' import { EnumNodeType, TFee } from '../../types' import { ClientContext } from '../../context/main' +import { minorToMajor, undelegate } from '../../requests' type TFormData = { nodeType: EnumNodeType @@ -50,11 +50,12 @@ export const UndelegateForm = ({ const { getBalance } = useContext(ClientContext) const onSubmit = async (data: TFormData) => { - await invoke(`undelegate_from_${data.nodeType}`, { + await undelegate({ + type: data.nodeType, identity: data.identity, }) - .then((res: any) => { - onSuccess(res) + .then(async (res) => { + onSuccess(`Successfully undelegated from ${res.source_address}`) getBalance.fetchBalance() }) .catch((e) => onError(e)) diff --git a/tauri-wallet/src/routes/undelegate/index.tsx b/tauri-wallet/src/routes/undelegate/index.tsx index f51c4cf3b1..833825ad4b 100644 --- a/tauri-wallet/src/routes/undelegate/index.tsx +++ b/tauri-wallet/src/routes/undelegate/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react' -import { Alert } from '@material-ui/lab' +import { Alert, AlertTitle } from '@material-ui/lab' import { useTheme } from '@material-ui/styles' import { NymCard } from '../../components' import { UndelegateForm } from './UndelegateForm' @@ -76,7 +76,13 @@ export const Undelegate = () => { An error occurred with the request: {message} } - Success={{message}} + Success={ + + {' '} + Undelegation complete + {message} + + } /> )} From 9934b9bc8aee9751b500d3ab9564e772712fe8df Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 13 Sep 2021 15:20:56 +0100 Subject: [PATCH 03/16] update working --- tauri-wallet/src/routes/bond/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tauri-wallet/src/routes/bond/index.tsx b/tauri-wallet/src/routes/bond/index.tsx index 7aee7ce80f..a6ab4079bd 100644 --- a/tauri-wallet/src/routes/bond/index.tsx +++ b/tauri-wallet/src/routes/bond/index.tsx @@ -90,7 +90,7 @@ export const Bond = () => { setStatus(EnumRequestStatus.initial) }} > - Resend? + Again? From e130131f16c0b1fd34ebd10b0a44235481ded9fe Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 15 Sep 2021 20:37:24 +0100 Subject: [PATCH 04/16] finish bonding and unbonding setup --- .../src/components/NodeTypeSelector.tsx | 4 ++ tauri-wallet/src/context/main.tsx | 2 +- tauri-wallet/src/hooks/useCheckOwnership.ts | 39 +++++++++++ tauri-wallet/src/hooks/useGetBalance.tsx | 6 +- tauri-wallet/src/requests/index.ts | 20 ++++++ tauri-wallet/src/routes/bond/BondForm.tsx | 70 +++++++++++-------- tauri-wallet/src/routes/bond/index.tsx | 59 +++++++++++----- tauri-wallet/src/routes/sign-in.tsx | 4 +- tauri-wallet/src/routes/unbond/UnbondForm.tsx | 34 --------- tauri-wallet/src/routes/unbond/index.tsx | 59 +++++++++++++++- tauri-wallet/src/types/global.ts | 4 +- tauri-wallet/src/utils/index.ts | 1 - 12 files changed, 214 insertions(+), 88 deletions(-) create mode 100644 tauri-wallet/src/hooks/useCheckOwnership.ts delete mode 100644 tauri-wallet/src/routes/unbond/UnbondForm.tsx diff --git a/tauri-wallet/src/components/NodeTypeSelector.tsx b/tauri-wallet/src/components/NodeTypeSelector.tsx index db235fe78e..b949eb00d3 100644 --- a/tauri-wallet/src/components/NodeTypeSelector.tsx +++ b/tauri-wallet/src/components/NodeTypeSelector.tsx @@ -9,9 +9,11 @@ import React from 'react' import { EnumNodeType } from '../types/global' export const NodeTypeSelector = ({ + disabled, nodeType, setNodeType, }: { + disabled: boolean nodeType: EnumNodeType setNodeType: (nodeType: EnumNodeType) => void }) => { @@ -32,11 +34,13 @@ export const NodeTypeSelector = ({ value={EnumNodeType.mixnode} control={} label="Mixnode" + disabled={disabled} /> } label="Gateway" + disabled={disabled} /> diff --git a/tauri-wallet/src/context/main.tsx b/tauri-wallet/src/context/main.tsx index 9e8dfe7482..da14162777 100644 --- a/tauri-wallet/src/context/main.tsx +++ b/tauri-wallet/src/context/main.tsx @@ -1,6 +1,6 @@ import React, { createContext, useEffect, useState } from 'react' import { useHistory } from 'react-router-dom' -import { Coin, TClientDetails, TSignInWithMnemonic } from '../types' +import { TClientDetails, TSignInWithMnemonic } from '../types' import { TUseGetBalance, useGetBalance } from '../hooks/useGetBalance' export const ADMIN_ADDRESS = 'punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0' diff --git a/tauri-wallet/src/hooks/useCheckOwnership.ts b/tauri-wallet/src/hooks/useCheckOwnership.ts new file mode 100644 index 0000000000..6022fd0a4b --- /dev/null +++ b/tauri-wallet/src/hooks/useCheckOwnership.ts @@ -0,0 +1,39 @@ +import { useEffect, useState } from 'react' +import { checkGatewayOwnership, checkMixnodeOwnership } from '../requests' +import { EnumNodeType, TNodeOwnership } from '../types' + +export const useCheckOwnership = () => { + const [ownership, setOwnership] = useState({ + hasOwnership: false, + nodeType: undefined, + }) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState() + + const checkOwnership = async () => { + const status = {} as TNodeOwnership + + setIsLoading(true) + + try { + const ownsMixnode = await checkMixnodeOwnership() + const ownsGateway = await checkGatewayOwnership() + + if (ownsMixnode) { + status.hasOwnership = true + status.nodeType = EnumNodeType.mixnode + } + + if (ownsGateway) { + status.hasOwnership = true + status.nodeType = EnumNodeType.gateway + } + + setOwnership(status) + } catch (e) { + setError(e as string) + } + } + + return { isLoading, error, ownership, checkOwnership } +} diff --git a/tauri-wallet/src/hooks/useGetBalance.tsx b/tauri-wallet/src/hooks/useGetBalance.tsx index b60e1bb4f0..013e9bd550 100644 --- a/tauri-wallet/src/hooks/useGetBalance.tsx +++ b/tauri-wallet/src/hooks/useGetBalance.tsx @@ -11,17 +11,17 @@ export type TUseGetBalance = { export const useGetBalance = (): TUseGetBalance => { const [balance, setBalance] = useState() - const [error, setEror] = useState() + const [error, setErorr] = useState() const [isLoading, setIsLoading] = useState(false) const fetchBalance = () => { setIsLoading(true) - setEror(undefined) + setErorr(undefined) invoke('get_balance') .then((balance) => { setBalance(balance as Balance) }) - .catch((e) => setEror(e)) + .catch((e) => setErorr(e)) setTimeout(() => { setIsLoading(false) }, 1000) diff --git a/tauri-wallet/src/requests/index.ts b/tauri-wallet/src/requests/index.ts index db63db1b45..8f08803b16 100644 --- a/tauri-wallet/src/requests/index.ts +++ b/tauri-wallet/src/requests/index.ts @@ -3,6 +3,8 @@ import { Coin, DelegationResult, EnumNodeType, + Gateway, + MixNode, Operation, TauriTxResult, TCreateAccount, @@ -51,3 +53,21 @@ export const send = async (args: { address: string memo: string }): Promise => await invoke('send', args) +export const checkMixnodeOwnership = async (): Promise => + await invoke('owns_mixnode') + +export const checkGatewayOwnership = async (): Promise => + await invoke('owns_gateway') + +export const bond = async ({ + type, + data, + amount, +}: { + type: EnumNodeType + data: MixNode | Gateway + amount: Coin +}): Promise => await invoke(`bond_${type}`, { [type]: data, bond: amount }) + +export const unbond = async (type: EnumNodeType) => + await invoke(`unbond_${type}`) diff --git a/tauri-wallet/src/routes/bond/BondForm.tsx b/tauri-wallet/src/routes/bond/BondForm.tsx index 21e89505fa..c0ed3cab81 100644 --- a/tauri-wallet/src/routes/bond/BondForm.tsx +++ b/tauri-wallet/src/routes/bond/BondForm.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useContext } from 'react' import { Button, Checkbox, @@ -11,14 +11,15 @@ import { Theme, } from '@material-ui/core' import { useTheme } from '@material-ui/styles' -import { useForm } from 'react-hook-form' +import { Alert } from '@material-ui/lab' import { yupResolver } from '@hookform/resolvers/yup' +import { useForm } from 'react-hook-form' import { EnumNodeType } from '../../types/global' import { NodeTypeSelector } from '../../components/NodeTypeSelector' +import { bond, majorToMinor } from '../../requests' import { validationSchema } from './validationSchema' import { Coin, Gateway, MixNode } from '../../types' -import { invoke } from '@tauri-apps/api' -import { Alert } from '@material-ui/lab' +import { ClientContext } from '../../context/main' type TBondFormFields = { withAdvancedOptions: boolean @@ -57,29 +58,27 @@ const formatData = (data: TBondFormFields) => { host: data.host, version: data.version, mix_port: data.mixPort, - amount: data.amount, - nodeType: data.nodeType, } if (data.nodeType === EnumNodeType.mixnode) { payload.verloc_port = data.verlocPort payload.http_api_port = data.httpApiPort - return payload as MixNode & { amount: number; nodeType: EnumNodeType } - } - - if (data.nodeType == EnumNodeType.gateway) { + return payload as MixNode + } else { payload.clients_port = data.clientsPort payload.location = data.location - return payload as Gateway & { amount: number; nodeType: EnumNodeType } + return payload as Gateway } } export const BondForm = ({ + disabled, fees, onError, onSuccess, }: { - fees: { [key in EnumNodeType]: Coin } + disabled: boolean + fees?: { [key in EnumNodeType]: Coin } onError: (message?: string) => void onSuccess: (message?: string) => void }) => { @@ -94,6 +93,8 @@ export const BondForm = ({ defaultValues, }) + const { getBalance } = useContext(ClientContext) + const watchNodeType = watch('nodeType', defaultValues.nodeType) const watchAdvancedOptions = watch( 'withAdvancedOptions', @@ -102,12 +103,12 @@ export const BondForm = ({ const onSubmit = async (data: TBondFormFields) => { const formattedData = formatData(data) - await invoke(`bond_${data.nodeType}`, { - [data.nodeType]: formattedData, - bond: { amount: formattedData?.amount, denom: 'Major' }, - }) - .then((res: any) => { - onSuccess(res) + const amount = await majorToMinor(data.amount) + + await bond({ type: data.nodeType, data: formattedData, amount }) + .then(() => { + getBalance.fetchBalance() + onSuccess(`Successfully bonded to ${data.identityKey}`) }) .catch((e) => { onError(e) @@ -129,17 +130,20 @@ export const BondForm = ({ if (nodeType === EnumNodeType.mixnode) setValue('location', undefined) }} + disabled={disabled} /> - - - {`A fee of ${ - watchNodeType === EnumNodeType.mixnode - ? fees.mixnode.amount - : fees.gateway.amount - } PUNK will apply to this transaction`} - - + {fees && ( + + + {`A fee of ${ + watchNodeType === EnumNodeType.mixnode + ? fees.mixnode.amount + : fees.gateway.amount + } PUNK will apply to this transaction`} + + + )} @@ -165,6 +170,7 @@ export const BondForm = ({ error={!!errors.sphinxKey} helperText={errors.sphinxKey?.message} fullWidth + disabled={disabled} /> @@ -183,6 +189,7 @@ export const BondForm = ({ punks ), }} + disabled={disabled} /> @@ -197,6 +204,7 @@ export const BondForm = ({ fullWidth error={!!errors.host} helperText={errors.host?.message} + disabled={disabled} /> @@ -213,6 +221,7 @@ export const BondForm = ({ fullWidth error={!!errors.location} helperText={errors.location?.message} + disabled={disabled} /> )} @@ -228,6 +237,7 @@ export const BondForm = ({ fullWidth error={!!errors.version} helperText={errors.version?.message} + disabled={disabled} /> @@ -275,6 +285,7 @@ export const BondForm = ({ helperText={ errors.mixPort?.message && 'A valid port value is required' } + disabled={disabled} /> {watchNodeType === EnumNodeType.mixnode ? ( @@ -292,6 +303,7 @@ export const BondForm = ({ errors.verlocPort?.message && 'A valid port value is required' } + disabled={disabled} /> @@ -308,6 +320,7 @@ export const BondForm = ({ errors.httpApiPort?.message && 'A valid port value is required' } + disabled={disabled} /> @@ -325,6 +338,7 @@ export const BondForm = ({ errors.clientsPort?.message && 'A valid port value is required' } + disabled={disabled} /> )} @@ -343,7 +357,7 @@ export const BondForm = ({ }} > + } + style={{ margin: theme.spacing(2) }} + > + {`Looks like you already have a ${ownership.nodeType} bonded.`} + + )} {status === EnumRequestStatus.loading && ( { )} {status === EnumRequestStatus.initial && ( { setMessage(e) setStatus(EnumRequestStatus.error) @@ -59,6 +84,7 @@ export const Bond = () => { setMessage(message) setStatus(EnumRequestStatus.success) }} + disabled={ownership?.hasOwnership} /> )} {(status === EnumRequestStatus.error || @@ -88,6 +114,7 @@ export const Bond = () => { - - - ) -} diff --git a/tauri-wallet/src/routes/unbond/index.tsx b/tauri-wallet/src/routes/unbond/index.tsx index ff768ab63e..9ae5ed6f01 100644 --- a/tauri-wallet/src/routes/unbond/index.tsx +++ b/tauri-wallet/src/routes/unbond/index.tsx @@ -1,13 +1,68 @@ -import React from 'react' +import React, { useContext, useEffect, useState } from 'react' import { NymCard } from '../../components' import { UnbondForm } from './UnbondForm' import { Layout } from '../../layouts' +import { useCheckOwnership } from '../../hooks/useCheckOwnership' +import { Alert } from '@material-ui/lab' +import { Box, Button, CircularProgress, Theme } from '@material-ui/core' +import { ClientContext } from '../../context/main' +import { unbond } from '../../requests' +import { useTheme } from '@material-ui/styles' export const Unbond = () => { + const [isLoading, setIsLoading] = useState(false) + const { checkOwnership, ownership } = useCheckOwnership() + const { getBalance } = useContext(ClientContext) + + const theme: Theme = useTheme() + + useEffect(() => { + const initialiseForm = async () => { + await checkOwnership() + } + initialiseForm() + }, [ownership.hasOwnership, checkOwnership]) + return ( - + {ownership?.hasOwnership && ( + { + setIsLoading(true) + await unbond(ownership.nodeType!) + getBalance.fetchBalance() + setIsLoading(false) + }} + > + Unbond + + } + style={{ margin: theme.spacing(2) }} + > + {`Looks like you already have a ${ownership.nodeType} bonded.`} + + )} + {!ownership.hasOwnership && ( + + You don't currently have a bonded node + + )} + {isLoading && ( + + + + )} ) diff --git a/tauri-wallet/src/types/global.ts b/tauri-wallet/src/types/global.ts index cfb41568f2..672f1629d6 100644 --- a/tauri-wallet/src/types/global.ts +++ b/tauri-wallet/src/types/global.ts @@ -6,8 +6,8 @@ export enum EnumNodeType { } export type TNodeOwnership = { - ownsMixnode: boolean - ownsGateway: boolean + hasOwnership: boolean + nodeType?: EnumNodeType } export type TClientDetails = { diff --git a/tauri-wallet/src/utils/index.ts b/tauri-wallet/src/utils/index.ts index f64530eee5..fe47b62c3e 100644 --- a/tauri-wallet/src/utils/index.ts +++ b/tauri-wallet/src/utils/index.ts @@ -75,7 +75,6 @@ export const validateVersion = (version: string): boolean => { const validVersion = valid(version) return validVersion !== null && minorVersion >= 11 } catch (e) { - console.log(e) return false } } From 9dc8ba7b77f860c3439719e906c8a94b3a7b00c4 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 15 Sep 2021 21:39:18 +0100 Subject: [PATCH 05/16] funds allocation check when bonding/sending/delegating --- tauri-wallet/src/requests/index.ts | 4 ++ tauri-wallet/src/routes/bond/BondForm.tsx | 7 +++ .../src/routes/delegate/DelegateForm.tsx | 9 ++++ tauri-wallet/src/routes/send/SendWizard.tsx | 54 +++++++++++-------- tauri-wallet/src/utils/index.ts | 7 +++ 5 files changed, 59 insertions(+), 22 deletions(-) diff --git a/tauri-wallet/src/requests/index.ts b/tauri-wallet/src/requests/index.ts index 8f08803b16..ccf86222a6 100644 --- a/tauri-wallet/src/requests/index.ts +++ b/tauri-wallet/src/requests/index.ts @@ -1,5 +1,6 @@ import { invoke } from '@tauri-apps/api' import { + Balance, Coin, DelegationResult, EnumNodeType, @@ -71,3 +72,6 @@ export const bond = async ({ export const unbond = async (type: EnumNodeType) => await invoke(`unbond_${type}`) + +export const getBalance = async (): Promise => + await invoke('get_balance') diff --git a/tauri-wallet/src/routes/bond/BondForm.tsx b/tauri-wallet/src/routes/bond/BondForm.tsx index c0ed3cab81..8d1152bed9 100644 --- a/tauri-wallet/src/routes/bond/BondForm.tsx +++ b/tauri-wallet/src/routes/bond/BondForm.tsx @@ -20,6 +20,7 @@ import { bond, majorToMinor } from '../../requests' import { validationSchema } from './validationSchema' import { Coin, Gateway, MixNode } from '../../types' import { ClientContext } from '../../context/main' +import { checkHasEnoughFunds } from '../../utils' type TBondFormFields = { withAdvancedOptions: boolean @@ -86,6 +87,7 @@ export const BondForm = ({ register, handleSubmit, setValue, + setError, watch, formState: { errors, isSubmitting }, } = useForm({ @@ -102,6 +104,11 @@ export const BondForm = ({ ) const onSubmit = async (data: TBondFormFields) => { + const hasEnoughFunds = await checkHasEnoughFunds(data.amount) + if (!hasEnoughFunds) { + return setError('amount', { message: 'Not enough funds in wallet' }) + } + const formattedData = formatData(data) const amount = await majorToMinor(data.amount) diff --git a/tauri-wallet/src/routes/delegate/DelegateForm.tsx b/tauri-wallet/src/routes/delegate/DelegateForm.tsx index 9765837532..d4c0ae7887 100644 --- a/tauri-wallet/src/routes/delegate/DelegateForm.tsx +++ b/tauri-wallet/src/routes/delegate/DelegateForm.tsx @@ -17,6 +17,7 @@ import { validationSchema } from './validationSchema' import { Alert } from '@material-ui/lab' import { ClientContext } from '../../context/main' import { delegate, majorToMinor } from '../../requests' +import { checkHasEnoughFunds } from '../../utils' type TDelegateForm = { nodeType: EnumNodeType @@ -45,6 +46,7 @@ export const DelegateForm = ({ setValue, watch, handleSubmit, + setError, formState: { errors, isSubmitting }, } = useForm({ defaultValues, @@ -56,6 +58,13 @@ export const DelegateForm = ({ const { getBalance } = useContext(ClientContext) const onSubmit = async (data: TDelegateForm) => { + const hasEnoughFunds = await checkHasEnoughFunds(data.amount) + if (!hasEnoughFunds) { + return setError('amount', { + message: 'Not enough funds in wallet', + }) + } + const amount = await majorToMinor(data.amount) await delegate({ diff --git a/tauri-wallet/src/routes/send/SendWizard.tsx b/tauri-wallet/src/routes/send/SendWizard.tsx index 8438f9ad95..ee03bab9aa 100644 --- a/tauri-wallet/src/routes/send/SendWizard.tsx +++ b/tauri-wallet/src/routes/send/SendWizard.tsx @@ -10,6 +10,7 @@ import { ClientContext } from '../../context/main' import { validationSchema } from './validationSchema' import { TauriTxResult } from '../../types' import { majorToMinor, send } from '../../requests' +import { checkHasEnoughFunds } from '../../utils' const defaultValues = { amount: '', @@ -58,31 +59,40 @@ export const SendWizard = () => { } const handleSend = async () => { - setIsLoading(true) - setActiveStep((s) => s + 1) const formState = methods.getValues() - const amount = await majorToMinor(formState.amount) - send({ - amount, - address: formState.to, - memo: formState.memo, - }) - .then((res: any) => { - const { details } = res as TauriTxResult - setActiveStep((s) => s + 1) - setConfirmedData({ - ...details, - amount: { denom: 'Major', amount: formState.amount }, + const hasEnoughFunds = await checkHasEnoughFunds(formState.amount) + if (!hasEnoughFunds) { + methods.setError('amount', { + message: 'Not enough funds in wallet', + }) + return handlePreviousStep() + } else { + setIsLoading(true) + setActiveStep((s) => s + 1) + const amount = await majorToMinor(formState.amount) + + send({ + amount, + address: formState.to, + memo: formState.memo, + }) + .then((res: any) => { + const { details } = res as TauriTxResult + setActiveStep((s) => s + 1) + setConfirmedData({ + ...details, + amount: { denom: 'Major', amount: formState.amount }, + }) + setIsLoading(false) + getBalance.fetchBalance() }) - setIsLoading(false) - getBalance.fetchBalance() - }) - .catch((e) => { - setRequestError(e) - setIsLoading(false) - console.log(e) - }) + .catch((e) => { + setRequestError(e) + setIsLoading(false) + console.log(e) + }) + } } return ( diff --git a/tauri-wallet/src/utils/index.ts b/tauri-wallet/src/utils/index.ts index fe47b62c3e..d07271ad0c 100644 --- a/tauri-wallet/src/utils/index.ts +++ b/tauri-wallet/src/utils/index.ts @@ -1,6 +1,7 @@ import { invoke } from '@tauri-apps/api' import bs58 from 'bs58' import { minor, valid } from 'semver' +import { getBalance, majorToMinor } from '../requests' import { Coin } from '../types' export const validateKey = (key: string): boolean => { @@ -89,3 +90,9 @@ export const validateRawPort = (rawPort: number): boolean => export const truncate = (text: string, trim: number) => text.substring(0, trim) + '...' + +export const checkHasEnoughFunds = async (allocationValue: string) => { + const minorValue = await majorToMinor(allocationValue) + const walletValue = await getBalance() + return !(+walletValue.coin.amount - +minorValue.amount < 0) +} From 63bc42cd5f040e7bcb71501333246bcdfc4e8821 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 16 Sep 2021 10:01:53 +0100 Subject: [PATCH 06/16] update title --- tauri-wallet/public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tauri-wallet/public/index.html b/tauri-wallet/public/index.html index 7d20a22d58..7255670d07 100644 --- a/tauri-wallet/public/index.html +++ b/tauri-wallet/public/index.html @@ -1,7 +1,7 @@ - Tauri Web Wallet + Nym Wallet
From 424230c3bb09bd86db8e57f48271a5b79e2453b5 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Thu, 16 Sep 2021 17:33:20 +0200 Subject: [PATCH 07/16] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit cddd9e8e4ca95f82ee7d4abf8317d15bafd0c971 Merge: 40fbdff0 976dd7aa Author: Drazen Urch Date: Thu Sep 16 17:27:27 2021 +0200 Merge branch 'develop' into tauri-wallet commit 976dd7aae2e7ee6c8a0fdbe368f393b1c26ad704 Author: Drazen Urch Date: Wed Sep 15 17:28:49 2021 +0200 Add block_height method to Delegation (#778) Co-authored-by: Drazen Urch commit 0d21f4e937b1222820260a426c02b10d420963aa Merge: e84af4f6 1403449a Author: Fouad Date: Wed Sep 15 12:41:29 2021 +0100 Merge pull request #776 from nymtech/update/re-enable-bonding re-enable bonding commit 1403449ad51ec2cba19997af88e60f1435512171 Author: fmtabbara Date: Tue Sep 14 16:00:21 2021 +0100 enable bonding commit e84af4f6018772feb4896b31702f2d33ee4c1fb1 Author: Drazen Urch Date: Tue Sep 14 15:15:26 2021 +0200 Migrate legacy delegation data (#771) * Skip ReadOnlyBucket deserialization errors * empty migration * clippy * cargo schema * Drop invalid delegation data * Dont drop old data * Add todo * Unify on type param * gateways are different * cargo fmt Co-authored-by: Drazen Urch commit 26b032c15c7c8ec8f1442f23d2416bafce65323a Merge: e1ddaff0 cba36253 Author: Mark Sinclair Date: Tue Sep 14 10:09:14 2021 +0100 Merge pull request #774 from nymtech/feature/explorer-api-delegations Explorer-api: add API resource to show the delegations for each mix node commit cba3625394dae3f736dab23018df5fcc3c9a4f5d Author: Mark Sinclair Date: Mon Sep 13 10:33:41 2021 +0100 explorer-api: add API resource to show the delegations for each mix node commit e1ddaff04d5077969dd0dbb1e645653035aa8e23 Merge: 0b9c03ca 66ab5de4 Author: Fouad Date: Fri Sep 10 17:17:14 2021 +0100 Merge pull request #772 from nymtech/update/disable-bonding add app alert commit 66ab5de442de91a50556117b62ef028b6b249f6f Author: fmtabbara Date: Fri Sep 10 16:16:58 2021 +0100 add app alert commit 0b9c03ca900a834856efa37706dafac7685b5f79 Author: Dave Hrycyszyn Date: Fri Sep 10 11:23:21 2021 +0300 Adding deps for building the Tauri wallet under Ubuntu (#770) commit c9dce0c1da29a99b1c483009a0658b2236c3a4e8 Author: Bogdan-Ștefan Neacşu Date: Thu Sep 9 11:21:45 2021 +0200 Feature/consumable bandwidth (#766) * Set actual value for bandwidth Also put it as a public attribute, such that it can be actively used by the credential consumer * Switch from sending Attribute structs to sending the actual attribute bytes over the wire * Add atomic bandwidth value to gateway * Consume bandwidth based on the mix packet size * Use Bandwidth struct for specific functionality * Move bandwidth code outside the dependency path of wasm client * Use u64 instead of AtomicU64, as the handling is not parallel commit e00e77db15c2c18699c58bf771940c16f63a610f Author: Bogdan-Ștefan Neacşu Date: Wed Sep 8 15:07:24 2021 +0200 Feature/bond blockstamp (#760) * Add block_height to MixNode/GatewayBond * Reward based on blockstamp of bonded node or of delegation * Add specific tests * Add migration code * Apply doc nit commit 1074449f91a1978e2d03f190c5854ae162bae907 Merge: 08276e6e 9a3d824a Author: Fouad Date: Tue Sep 7 23:45:10 2021 +0100 Merge pull request #767 from nymtech/update/remove-app-alert remove alert commit 08276e6e427679f1b437e370a66dd1140631b429 Author: Bogdan-Ștefan Neacşu Date: Tue Sep 7 16:33:30 2021 +0200 Remove migration code (#759) commit 9a3d824a4a451d4906219607f4a7bcd27dbbeba4 Author: fmtabbara Date: Mon Sep 6 20:39:24 2021 +0100 remove alert commit 2789ee8f18768bf7fd9b26a63ee928b286996049 Author: Bogdan-Ștefan Neacşu Date: Fri Sep 3 15:30:45 2021 +0300 Update coconut-rs and use hash_to_scalar from there (#765) Failed tests are due to some nightly issue, not related to this PR commit a7ba643c354da7ec40ecfb1b0fcae46b9f57f0e2 Merge: 28be53ee c42f3c68 Author: Fouad Date: Fri Sep 3 09:14:50 2021 +0100 Merge pull request #762 from nymtech/feature/app-alert add app alert banner commit 28be53eefb9a1a6950b0b84e8117c6829df93c37 Author: Bogdan-Ștefan Neacşu Date: Thu Sep 2 18:26:40 2021 +0300 Add block_height in the Delegation structure as well (#757) commit 219c45a352584ab7f9efb957107a671a4e19d13e Author: Jędrzej Stuczyński Date: Thu Sep 2 15:48:29 2021 +0100 Updated cosmos-sdk (#761) * Updated cosmos-sdk * Re-exposing more things commit 1a3b83752e0f5e4eed5515ba0b90f3551c1f0a81 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Sep 2 15:45:56 2021 +0100 Bump next from 11.1.0 to 11.1.1 in /wallet-web (#758) Bumps [next](https://github.com/vercel/next.js) from 11.1.0 to 11.1.1. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v11.1.0...v11.1.1) --- updated-dependencies: - dependency-name: next dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit c42f3c684425d0e718ca55adacbe5afa99502603 Author: fmtabbara Date: Thu Sep 2 12:29:47 2021 +0100 add app alert banner commit a5d3ba390029818fcaa7c640a0c6eb6ec7916a96 Merge: 92e13a5d cdf0d443 Author: Mark Sinclair Date: Tue Aug 31 15:59:11 2021 +0100 Merge pull request #755 from nymtech/bugfix/explorer-api-ping Explorer API: port test now split out address resolution and add units tests commit cdf0d443411618e0952c35bfb5972fc82023cdfa Author: Mark Sinclair Date: Tue Aug 31 14:37:28 2021 +0100 explorer-api: turned down logging from `error` to `warn` commit 92f976a45d2d05c1250d6351392a3acc4eb1c01f Author: Mark Sinclair Date: Fri Aug 27 11:34:21 2021 +0100 explorer-api: sanitize hostname before running checks to avoid leading or trailing spaces that are known to exist in the current test net commit 2bc858cde351cb299c8986fd1045da6d832acbdb Author: Mark Sinclair Date: Fri Aug 27 09:32:26 2021 +0100 explorer-api: port test: split out address resolution and add units tests commit 92e13a5d005d3093717c77332cb5837cec4cfd34 Author: Bogdan-Ștefan Neacşu Date: Tue Aug 31 14:51:15 2021 +0300 Feature/add blockstamp (#756) * Add RawDelegationData * Fix current tests for the new stored data * Added migration commit. Will be reverted after doing the migration * New tests for block height * Use current blockstamp instead of 24h old one * Put _alot_ of migration stuff in the migrate function scope commit 122f5d9f2e5c1ced96e3b9ba0c74ef8b7dbde2c7 Author: Bogdan-Ștefan Neacşu Date: Mon Aug 30 10:27:20 2021 +0300 Feature/cred after handshake (#745) * Call perform_initial_authentication instead of register in clients * Refactor the register/authenticate functions a bit * Introduce Bandwidth request type * Add encryption layer to cred * Remove cred pass and check from handshake * Replaced unreachable! with error * Changed decrypt_tagged signature to not take mutable ownership of data * Put handle_bandwidth work inside a function * Add check before unwrap * Remove unnecessary async * Decouple bandwidth credential from authentication * Use new_error for ServerResponse:Error * Send a fresh IV each time the BandwidthCredential request is sent * Remove unwrap of bincode::serialize * Add comment regarding Bandwidth response * Remove _mut from naming * Leave Debug trait alone, as the initial error doesn't reproduce anymore * Pass iv as Vec instead of base58 string * Renamed AuthenticationIV to IV, as it is now used for more the just authentication * Did some IV refactorization commit 982ee0266c92628909ea0b5691b2abd31950f084 Author: Bogdan-Ștefan Neacşu Date: Fri Aug 27 16:02:34 2021 +0300 Feature/get own delegations (#748) * Introduce reverse delegation bucket * Add client command * Fix clippy error * Added tests in queries * Add tests in transactions * Migration code. Will be reverted after it's called on testnet * Replace unwrap with expect * Move some test code in the right file... ... to remove unnecessary auxiliary function. * Reduce the scope to migration auxiliary functions * Rename everything from [node]reverse to reverse[node] * Fix fmt commit 5f42a9bd052aebc496d639be4cb2aeadc4a575cb Author: Jędrzej Stuczyński Date: Fri Aug 27 13:52:18 2021 +0100 NetworkMonitorBuilder - starting the monitor after rocket has launched (#754) * NetworkMonitorBuilder - starting the monitor after rocket has launched * Removed unused import commit 1811df9ddb696279e2fc4cb58eeb9df55f2b1843 Author: Jędrzej Stuczyński Date: Fri Aug 27 13:52:10 2021 +0100 Enabled validators api argument (#753) commit 6bdfe7f8954177240894945dee86926eb3df08ab Author: Jędrzej Stuczyński Date: Thu Aug 26 11:21:01 2021 +0100 Correctly bounding nominator of uptime calculation (#752) commit c6b286a1dba0dd1a1a1df021cc4e8e45873eed15 Author: Jędrzej Stuczyński Date: Wed Aug 25 14:50:57 2021 +0100 Fixed argument parsing for ipv6 'good' topology (#751) commit b3568a26f57faaffe7b8d20ab90cb4cb456eae5e Author: Bogdan-Ștefan Neacşu Date: Tue Aug 24 11:25:05 2021 +0300 Revert "Migration commit, will be reverted after the testnet contract is updated" (#749) This reverts commit 38d868bcce3738c00d0f60bbc40373efd3cf7c6e. commit 15ae0f521e46336c0e8518e9b749dde9d702077a Author: Jędrzej Stuczyński Date: Mon Aug 23 10:26:51 2021 +0100 Feature/more reliable uptime calculation (#747) * New database table holding monitor run info * SQL interface for new table * Updated uptime calculation to instead rely on number of monitor test runs commit 2923d4b8725f04a0097461852862cf348b0998da Author: Bogdan-Ștefan Neacşu Date: Thu Aug 19 22:03:07 2021 +0300 Update template toml key (#746) --- Cargo.lock | 159 ++- clients/client-core/src/client/key_manager.rs | 4 +- clients/native/src/client/config/template.rs | 2 +- clients/native/src/commands/init.rs | 4 +- clients/socks5/src/client/config/template.rs | 2 +- clients/socks5/src/commands/init.rs | 4 +- clients/tauri-client/README.md | 16 + clients/tauri-client/src-tauri/src/main.rs | 24 +- .../client-libs/gateway-client/src/client.rs | 75 +- .../client-libs/gateway-client/src/error.rs | 10 + .../client-libs/validator-client/Cargo.toml | 6 +- .../validator-client/src/client.rs | 112 +- .../src/nymd/cosmwasm_client/client.rs | 22 +- .../src/nymd/cosmwasm_client/helpers.rs | 4 +- .../src/nymd/cosmwasm_client/logs.rs | 2 +- .../src/nymd/cosmwasm_client/mod.rs | 2 +- .../nymd/cosmwasm_client/signing_client.rs | 18 +- .../src/nymd/cosmwasm_client/types.rs | 12 +- .../validator-client/src/nymd/error.rs | 4 +- .../validator-client/src/nymd/fee_helpers.rs | 4 +- .../validator-client/src/nymd/gas_price.rs | 2 +- .../validator-client/src/nymd/mod.rs | 57 +- .../validator-client/src/nymd/wallet/mod.rs | 12 +- common/coconut-interface/Cargo.toml | 3 +- common/coconut-interface/src/lib.rs | 52 +- common/credentials/src/bandwidth.rs | 17 +- common/credentials/src/error.rs | 12 + common/credentials/src/utils.rs | 16 +- common/mixnet-contract/src/delegation.rs | 76 +- common/mixnet-contract/src/gateway.rs | 7 +- common/mixnet-contract/src/lib.rs | 12 +- common/mixnet-contract/src/mixnode.rs | 13 +- common/mixnet-contract/src/msg.rs | 10 + common/mixnet-contract/src/types.rs | 6 - contracts/mixnet/Cargo.lock | 4 +- contracts/mixnet/schema/execute_msg.json | 349 ++++++ contracts/mixnet/schema/handle_msg.json | 134 --- .../{init_msg.json => instantiate_msg.json} | 2 +- contracts/mixnet/schema/mix_node_bond.json | 64 +- contracts/mixnet/schema/query_msg.json | 273 ++++- contracts/mixnet/schema/state.json | 82 +- contracts/mixnet/src/contract.rs | 111 +- contracts/mixnet/src/queries.rs | 507 +++++++- contracts/mixnet/src/state.rs | 4 - contracts/mixnet/src/storage.rs | 431 ++++++- contracts/mixnet/src/support/tests.rs | 13 +- contracts/mixnet/src/transactions.rs | 1022 ++++++++++++++--- explorer-api/Cargo.toml | 2 +- explorer-api/src/mix_node/http.rs | 10 +- explorer-api/src/mix_nodes/mod.rs | 33 +- explorer-api/src/ping/http.rs | 120 +- gateway/gateway-requests/Cargo.toml | 1 + .../src/authentication/encrypted_address.rs | 11 +- .../src/authentication/mod.rs | 1 - .../src/{authentication => }/iv.rs | 20 +- gateway/gateway-requests/src/lib.rs | 6 +- .../src/registration/handshake/client.rs | 9 +- .../src/registration/handshake/error.rs | 2 - .../src/registration/handshake/gateway.rs | 20 +- .../src/registration/handshake/mod.rs | 15 +- .../src/registration/handshake/shared_key.rs | 38 +- .../src/registration/handshake/state.rs | 18 +- gateway/gateway-requests/src/types.rs | 70 +- gateway/src/node/client_handling/bandwidth.rs | 85 ++ .../node/client_handling/clients_handler.rs | 6 +- gateway/src/node/client_handling/mod.rs | 1 + .../websocket/connection_handler.rs | 92 +- .../client_handling/websocket/listener.rs | 4 + .../src/node/client_handling/websocket/mod.rs | 4 +- gateway/src/node/storage/ledger.rs | 4 +- tauri-wallet/Cargo.lock | 24 +- tauri-wallet/src-tauri/Cargo.toml | 6 +- tauri-wallet/src-tauri/src/coconut.rs | 140 --- tauri-wallet/src-tauri/src/coin.rs | 13 +- tauri-wallet/src-tauri/src/main.rs | 13 +- tauri-wallet/src-tauri/src/state.rs | 28 - .../20210819120000_monitor_runs.sql | 7 + validator-api/src/main.rs | 69 +- validator-api/src/network_monitor/mod.rs | 176 +-- .../src/network_monitor/monitor/mod.rs | 12 + validator-api/src/node_status_api/models.rs | 25 +- validator-api/src/node_status_api/utils.rs | 69 +- validator-api/src/storage/manager.rs | 59 +- validator-api/src/storage/mod.rs | 152 ++- wallet-web/components/AppAlert.tsx | 30 + wallet-web/package-lock.json | 395 +++---- wallet-web/package.json | 2 +- wallet-web/pages/_app.tsx | 85 +- wallet-web/yarn.lock | 111 +- 89 files changed, 4273 insertions(+), 1492 deletions(-) create mode 100644 contracts/mixnet/schema/execute_msg.json delete mode 100644 contracts/mixnet/schema/handle_msg.json rename contracts/mixnet/schema/{init_msg.json => instantiate_msg.json} (73%) rename gateway/gateway-requests/src/{authentication => }/iv.rs (81%) create mode 100644 gateway/src/node/client_handling/bandwidth.rs delete mode 100644 tauri-wallet/src-tauri/src/coconut.rs create mode 100644 validator-api/migrations/20210819120000_monitor_runs.sql create mode 100644 wallet-web/components/AppAlert.tsx diff --git a/Cargo.lock b/Cargo.lock index a371ec67d1..2f92a77fde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,7 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -# Copyright 2020 - Nym Technologies SA -# SPDX-License-Identifier: Apache-2.0 version = 3 [[package]] @@ -120,9 +118,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" +checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" [[package]] name = "app" @@ -460,6 +458,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397" dependencies = [ + "digest 0.9.0", "ff", "group", "pairing", @@ -729,13 +728,12 @@ dependencies = [ "coconut-rs", "getset", "serde", - "sha2", ] [[package]] name = "coconut-rs" -version = "0.4.0" -source = "git+https://github.com/nymtech/coconut.git?branch=0.4.0#183cff805aa73a908b681ac3fcdff7d084c4f42b" +version = "0.5.0" +source = "git+https://github.com/nymtech/coconut.git?branch=0.5.0#a1b72d51aa2a67b73b9f58d707030ae6dc70af7f" dependencies = [ "bls12_381", "bs58", @@ -748,8 +746,6 @@ dependencies = [ "serde", "serde_derive", "sha2", - "sha3", - "subtle 2.4.1", "thiserror", ] @@ -819,9 +815,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c32f031ea41b4291d695026c023b95d59db2d8a2c7640800ed56bc8f510f22" +checksum = "fdab415d6744056100f40250a66bc430c1a46f7a02e20bc11c94c79a0f0464df" [[package]] name = "const_fn" @@ -936,8 +932,9 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" -version = "0.6.0" -source = "git+https://github.com/cosmos/cosmos-rust/?rev=ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30#ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7115eae4b8518967b8e737a3ef76025f2d007de7906d88c18f00b8bbfc70f51e" dependencies = [ "prost", "prost-types", @@ -945,9 +942,10 @@ dependencies = [ ] [[package]] -name = "cosmos_sdk" -version = "0.2.0" -source = "git+https://github.com/cosmos/cosmos-rust/?rev=ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30#ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30" +name = "cosmrs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "609eba7eee6d9929927cbdf15f9fe96d250c316c5e1b983c4a47a10f60da5ea7" dependencies = [ "bip32", "cosmos-sdk-proto", @@ -1124,9 +1122,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e49339137316df1914fdb54a5eae75a73f45068fd0d2178fe235b11d93238a6e" +checksum = "43b0ca41e2a75a407a44812f110ecd40e1efacb8993f612746491e12d5b929fe" dependencies = [ "generic-array 0.14.4", "rand_core 0.6.3", @@ -1343,9 +1341,9 @@ dependencies = [ [[package]] name = "der" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e21d2d0f22cde6e88694108429775c0219760a07779bf96503b434a03d7412" +checksum = "2adca118c71ecd9ae094d4b68257b3fdfcb711a612b9eec7b5a0d27a5a70a5b4" dependencies = [ "const-oid", ] @@ -1768,9 +1766,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80edafed416a46fb378521624fab1cfa2eb514784fd8921adbe8a8d8321da811" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" dependencies = [ "cfg-if 1.0.0", "crc32fast", @@ -2019,6 +2017,7 @@ dependencies = [ "bincode", "bs58", "coconut-interface", + "credentials", "crypto", "futures", "log", @@ -2167,9 +2166,9 @@ checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" [[package]] name = "gio" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a4c12fcba7a6402ae843a0085ec16d3658a87901763b6a7f0a7c5d60e555a5" +checksum = "f3a29d8062af72045518271a2cd98b4e1617ce43f5b4223ad0fb9a0eff8f718c" dependencies = [ "bitflags", "futures-channel", @@ -2235,7 +2234,7 @@ checksum = "2aad66361f66796bfc73f530c51ef123970eb895ffba991a234fcf7bea89e518" dependencies = [ "anyhow", "heck", - "proc-macro-crate 1.0.0", + "proc-macro-crate 1.1.0", "proc-macro-error", "proc-macro2", "quote", @@ -2364,7 +2363,7 @@ checksum = "21de1da96dc117443fb03c2e270b2d34b7de98d0a79a19bbb689476173745b79" dependencies = [ "anyhow", "heck", - "proc-macro-crate 1.0.0", + "proc-macro-crate 1.1.0", "proc-macro-error", "proc-macro2", "quote", @@ -2891,9 +2890,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1866b355d9c878e5e607473cbe3f63282c0b7aad2db1dbebf55076c686918254" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" dependencies = [ "wasm-bindgen", ] @@ -2943,9 +2942,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.101" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" [[package]] name = "libm" @@ -3389,7 +3388,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" dependencies = [ - "proc-macro-crate 1.0.0", + "proc-macro-crate 1.1.0", "proc-macro2", "quote", "syn", @@ -4137,9 +4136,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbee84ed13e44dd82689fa18348a49934fa79cc774a344c42fc9b301c71b140a" +checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" dependencies = [ "der", "spki", @@ -4219,9 +4218,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92" +checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" dependencies = [ "thiserror", "toml", @@ -5194,9 +5193,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" dependencies = [ "itoa", "ryu", @@ -5359,9 +5358,9 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" [[package]] name = "sled" -version = "0.34.6" +version = "0.34.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0132f3e393bcb7390c60bb45769498cf4550bcb7a21d7f95c02b69f6362cdc" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" dependencies = [ "crc32fast", "crossbeam-epoch", @@ -5411,9 +5410,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765f090f0e423d2b55843402a07915add955e7d60657db13707a159727326cad" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" dependencies = [ "libc", "winapi", @@ -5479,23 +5478,21 @@ checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" [[package]] name = "spki" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "987637c5ae6b3121aba9d513f869bd2bff11c4cc086c22473befd6649c0bd521" +checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" dependencies = [ "der", ] [[package]] name = "sqlformat" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684001e7985ec1a9a66963b77ed151ef22a7876b3fdd7e37a57ec774f54b7d96" +checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" dependencies = [ - "lazy_static", - "maplit", + "itertools 0.10.1", "nom", - "regex", "unicode_categories", ] @@ -6525,9 +6522,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" +checksum = "c2ba9ab62b7d6497a8638dfda5e5c4fb3b2d5a7fca4118f2b96151c8ef1a437e" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", @@ -6537,9 +6534,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" +checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77" dependencies = [ "proc-macro2", "quote", @@ -6548,9 +6545,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ca517f43f0fb96e0c3072ed5c275fe5eece87e8cb52f4a77b69226d3b1c9df8" +checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf" dependencies = [ "lazy_static", ] @@ -6764,7 +6761,7 @@ dependencies = [ "bip39", "coconut-interface", "config", - "cosmos_sdk", + "cosmrs", "cosmwasm-std", "flate2", "itertools 0.10.1", @@ -6859,9 +6856,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e68338db6becec24d3c7977b5bf8a48be992c934b5d07177e3931f5dc9b076c" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" dependencies = [ "cfg-if 1.0.0", "serde", @@ -6871,9 +6868,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" dependencies = [ "bumpalo", "lazy_static", @@ -6886,9 +6883,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a87d738d4abc4cf22f6eb142f5b9a81301331ee3c767f2fef2fda4e325492060" +checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -6898,9 +6895,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d5a6580be83b19dc570a8f9c324251687ab2184e57086f71625feb57ec77c8" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6908,9 +6905,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3775a030dc6f5a0afd8a84981a21cc92a781eb429acef9ecce476d0c9113e92" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" dependencies = [ "proc-macro2", "quote", @@ -6921,15 +6918,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c279e376c7a8e8752a8f1eaa35b7b0bee6bb9fb0cdacfa97cc3f1f289c87e2b4" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" [[package]] name = "wasm-bindgen-test" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02129da5406bad6e3b2042d0e19f963c03b84f0b05908599fc7f0980afb3158b" +checksum = "96f1aa7971fdf61ef0f353602102dbea75a56e225ed036c1e3740564b91e6b7e" dependencies = [ "console_error_panic_hook", "js-sys", @@ -6941,9 +6938,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7848675a1de39f7470ba747c4aa689f2977e190ab9589cdce6bd8a77c5743bd4" +checksum = "6006f79628dfeb96a86d4db51fbf1344cd7fd8408f06fc9aa3c84913a4789688" dependencies = [ "proc-macro2", "quote", @@ -6963,9 +6960,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a84d70d1ec7d2da2d26a5bd78f4bca1b8c3254805363ce743b7a05bc30d195a" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" dependencies = [ "js-sys", "wasm-bindgen", @@ -7047,9 +7044,9 @@ dependencies = [ [[package]] name = "webview2" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fab1ccfdabb098b047293c8d496c1914d1c654b68fdaa3bb77cfa47c4bca2c7" +checksum = "1b132bb76313456e93b17037262a030d54c9fe0f11838ef1593f845e3807ef8a" dependencies = [ "com", "once_cell", @@ -7060,9 +7057,9 @@ dependencies = [ [[package]] name = "webview2-sys" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5288cef1e0cbcf7a0b961e6271e33589b8989c80b2e11078504e989b5346ff" +checksum = "24b7889e893ac4c50d7346356be3ce13a85e56512c38b8fde0526559b8012a4c" dependencies = [ "com", "winapi", @@ -7195,9 +7192,9 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" +checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077" dependencies = [ "curve25519-dalek", "rand_core 0.5.1", @@ -7221,9 +7218,9 @@ checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" [[package]] name = "zeroize" -version = "1.4.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "377db0846015f7ae377174787dd452e1c5f5a9050bc6f954911d01f116daa0cd" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" dependencies = [ "zeroize_derive", ] diff --git a/clients/client-core/src/client/key_manager.rs b/clients/client-core/src/client/key_manager.rs index cad8f726d4..f852af3806 100644 --- a/clients/client-core/src/client/key_manager.rs +++ b/clients/client-core/src/client/key_manager.rs @@ -61,8 +61,8 @@ impl KeyManager { // I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute #[allow(dead_code)] /// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`]. - pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: SharedKeys) { - self.gateway_shared_key = Some(Arc::new(gateway_shared_key)) + pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc) { + self.gateway_shared_key = Some(gateway_shared_key) } /// Loads previously stored keys from the disk. diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs index b2ca0ec46b..66b33d7e3c 100644 --- a/clients/native/src/client/config/template.rs +++ b/clients/native/src/client/config/template.rs @@ -20,7 +20,7 @@ version = '{{ client.version }}' id = '{{ client.id }}' # Addresses to APIs running on validator from which the client gets the view of the network. -validator_rest_urls = [ +validator_api_urls = [ {{#each client.validator_api_urls }} '{{this}}', {{/each}} diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index 2035daf78c..cb37d96b27 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -79,7 +79,7 @@ async fn register_with_gateway( gateway: &gateway::Node, our_identity: Arc, validator_urls: Vec, -) -> SharedKeys { +) -> Arc { let timeout = Duration::from_millis(1500); let coconut_credential = prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await; @@ -95,7 +95,7 @@ async fn register_with_gateway( .await .expect("failed to establish connection with the gateway!"); gateway_client - .register() + .perform_initial_authentication() .await .expect("failed to register with the gateway!") } diff --git a/clients/socks5/src/client/config/template.rs b/clients/socks5/src/client/config/template.rs index 0bdce7b650..70e196d3c1 100644 --- a/clients/socks5/src/client/config/template.rs +++ b/clients/socks5/src/client/config/template.rs @@ -20,7 +20,7 @@ version = '{{ client.version }}' id = '{{ client.id }}' # Addresses to APIs running on validator from which the client gets the view of the network. -validator_rest_urls = [ +validator_api_urls = [ {{#each client.validator_api_urls }} '{{this}}', {{/each}} diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 99e8eff716..58916dd45c 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -79,7 +79,7 @@ async fn register_with_gateway( gateway: &gateway::Node, our_identity: Arc, validator_urls: Vec, -) -> SharedKeys { +) -> Arc { let timeout = Duration::from_millis(1500); let coconut_credential = prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await; @@ -95,7 +95,7 @@ async fn register_with_gateway( .await .expect("failed to establish connection with the gateway!"); gateway_client - .register() + .perform_initial_authentication() .await .expect("failed to register with the gateway!") } diff --git a/clients/tauri-client/README.md b/clients/tauri-client/README.md index 865d0c6426..849b049db4 100644 --- a/clients/tauri-client/README.md +++ b/clients/tauri-client/README.md @@ -1,3 +1,19 @@ +## Prerequisites + +On Ubuntu-ish systems (not tested on Debian but may work): + +``` +sudo apt update && sudo apt install libwebkit2gtk-4.0-dev \ + build-essential \ + curl \ + wget \ + libssl-dev \ + libgtk-3-dev \ + libappindicator3-dev \ + patchelf \ + librsvg2-dev``` + + ## Getting started diff --git a/clients/tauri-client/src-tauri/src/main.rs b/clients/tauri-client/src-tauri/src/main.rs index 7c5bb06df7..9e065821fa 100644 --- a/clients/tauri-client/src-tauri/src/main.rs +++ b/clients/tauri-client/src-tauri/src/main.rs @@ -4,7 +4,7 @@ )] use coconut_interface::{ - self, Attribute, Credential, Parameters, Signature, Theta, VerificationKey, + self, hash_to_scalar, Attribute, Credential, Parameters, Signature, Theta, VerificationKey, }; use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key}; use std::sync::Arc; @@ -15,19 +15,29 @@ struct State { signatures: Vec, n_attributes: u32, params: Parameters, + public_attributes_bytes: Vec>, public_attributes: Vec, private_attributes: Vec, aggregated_verification_key: Option, } impl State { - fn init(public_attributes: Vec, private_attributes: Vec) -> State { - let n_attributes = (public_attributes.len() + private_attributes.len()) as u32; + fn init(public_attributes_bytes: Vec>, private_attributes_bytes: Vec>) -> State { + let n_attributes = (public_attributes_bytes.len() + private_attributes_bytes.len()) as u32; let params = Parameters::new(n_attributes).unwrap(); + let public_attributes = public_attributes_bytes + .iter() + .map(hash_to_scalar) + .collect::>(); + let private_attributes = private_attributes_bytes + .iter() + .map(hash_to_scalar) + .collect::>(); State { signatures: Vec::new(), n_attributes, params, + public_attributes_bytes, public_attributes, private_attributes, aggregated_verification_key: None, @@ -137,14 +147,14 @@ async fn verify_credential( let credential = Credential::new( state.n_attributes, theta, - &state.public_attributes, + state.public_attributes_bytes.clone(), state .signatures .get(idx) .ok_or("Got invalid signature idx")?, ); - Ok(credential.verify(&verification_key).await) + Ok(credential.verify(&verification_key)) } #[tauri::command] @@ -170,8 +180,8 @@ async fn get_credential( } fn main() { - let public_attributes = vec![coconut_interface::hash_to_scalar("public_key")]; - let private_attributes = vec![coconut_interface::hash_to_scalar("private_key")]; + let public_attributes = vec![b"public_key".to_vec()]; + let private_attributes = vec![b"private_key".to_vec()]; tauri::Builder::default() .manage(Arc::new(RwLock::new(State::init( public_attributes, diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index 52bdc40a35..d250bc720d 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -12,7 +12,7 @@ use coconut_interface::Credential; use crypto::asymmetric::identity; use futures::{FutureExt, SinkExt, StreamExt}; use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; -use gateway_requests::authentication::iv::AuthenticationIV; +use gateway_requests::iv::IV; use gateway_requests::registration::handshake::{client_handshake, SharedKeys}; use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse}; use log::*; @@ -36,6 +36,8 @@ const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5); pub struct GatewayClient { authenticated: bool, + // TODO: This should be replaced by an actual bandwidth value, with 0 meaning no bandwidth + has_bandwidth: bool, gateway_address: String, gateway_identity: identity::PublicKey, local_identity: Arc, @@ -70,6 +72,7 @@ impl GatewayClient { ) -> Self { GatewayClient { authenticated: false, + has_bandwidth: false, gateway_address, gateway_identity, local_identity, @@ -114,6 +117,7 @@ impl GatewayClient { GatewayClient { authenticated: false, + has_bandwidth: false, gateway_address, gateway_identity, local_identity, @@ -362,7 +366,7 @@ impl GatewayClient { } } - pub async fn register(&mut self) -> Result { + async fn register(&mut self) -> Result<(), GatewayClientError> { if !self.connection.is_established() { return Err(GatewayClientError::ConnectionNotEstablished); } @@ -379,21 +383,26 @@ impl GatewayClient { ws_stream, self.local_identity.as_ref(), self.gateway_identity, - self.coconut_credential.clone(), ) .await .map_err(GatewayClientError::RegistrationFailure), _ => unreachable!(), }?; - - self.authenticated = true; - Ok(shared_key) + self.authenticated = match self.read_control_response().await? { + ServerResponse::Register { status } => Ok(status), + ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)), + _ => Err(GatewayClientError::UnexpectedResponse), + }?; + if self.authenticated { + self.shared_key = Some(Arc::new(shared_key)); + } + Ok(()) } - pub async fn authenticate( + async fn authenticate( &mut self, shared_key: Option, - ) -> Result { + ) -> Result<(), GatewayClientError> { if shared_key.is_none() && self.shared_key.is_none() { return Err(GatewayClientError::NoSharedKeyAvailable); } @@ -409,7 +418,7 @@ impl GatewayClient { let shared_key = shared_key .as_ref() .unwrap_or_else(|| self.shared_key.as_ref().unwrap()); - let iv = AuthenticationIV::new_random(&mut rng); + let iv = IV::new_random(&mut rng); let self_address = self .local_identity .as_ref() @@ -420,15 +429,14 @@ impl GatewayClient { let msg = ClientControlRequest::new_authenticate(self_address, encrypted_address, iv).into(); - let authenticated = match self.send_websocket_message(msg).await? { + match self.send_websocket_message(msg).await? { ServerResponse::Authenticate { status } => { self.authenticated = status; - Ok(status) + Ok(()) } ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)), - _ => unreachable!(), - }?; - Ok(authenticated) + _ => Err(GatewayClientError::UnexpectedResponse), + } } /// Helper method to either call register or authenticate based on self.shared_key value @@ -438,8 +446,7 @@ impl GatewayClient { if self.shared_key.is_some() { self.authenticate(None).await?; } else { - let shared_key = self.register().await?; - self.shared_key = Some(Arc::new(shared_key)); + self.register().await?; } if self.authenticated { // if we are authenticated it means we MUST have an associated shared_key @@ -449,6 +456,32 @@ impl GatewayClient { } } + pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError> { + if !self.authenticated { + return Err(GatewayClientError::NotAuthenticated); + } + if self.shared_key.is_none() { + return Err(GatewayClientError::NoSharedKeyAvailable); + } + + let mut rng = OsRng; + let iv = IV::new_random(&mut rng); + + let msg = ClientControlRequest::new_enc_bandwidth_credential( + &self.coconut_credential, + self.shared_key.as_ref().unwrap(), + iv, + ) + .ok_or(GatewayClientError::SerializeCredential)? + .into(); + self.has_bandwidth = match self.send_websocket_message(msg).await? { + ServerResponse::Bandwidth { status } => Ok(status), + ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)), + _ => Err(GatewayClientError::UnexpectedResponse), + }?; + Ok(()) + } + pub async fn batch_send_mix_packets( &mut self, packets: Vec, @@ -456,6 +489,9 @@ impl GatewayClient { if !self.authenticated { return Err(GatewayClientError::NotAuthenticated); } + if !self.has_bandwidth { + return Err(GatewayClientError::NotEnoughBandwidth); + } if !self.connection.is_established() { return Err(GatewayClientError::ConnectionNotEstablished); } @@ -514,6 +550,9 @@ impl GatewayClient { if !self.authenticated { return Err(GatewayClientError::NotAuthenticated); } + if !self.has_bandwidth { + return Err(GatewayClientError::NotEnoughBandwidth); + } if !self.connection.is_established() { return Err(GatewayClientError::ConnectionNotEstablished); } @@ -559,6 +598,9 @@ impl GatewayClient { if !self.authenticated { return Err(GatewayClientError::NotAuthenticated); } + if !self.has_bandwidth { + return Err(GatewayClientError::NotEnoughBandwidth); + } if self.connection.is_partially_delegated() { return Ok(()); } @@ -591,6 +633,7 @@ impl GatewayClient { self.establish_connection().await?; } let shared_key = self.perform_initial_authentication().await?; + self.claim_bandwidth().await?; // this call is NON-blocking self.start_listening_for_mixnet_messages()?; diff --git a/common/client-libs/gateway-client/src/error.rs b/common/client-libs/gateway-client/src/error.rs index 2f9938b57a..170314e484 100644 --- a/common/client-libs/gateway-client/src/error.rs +++ b/common/client-libs/gateway-client/src/error.rs @@ -21,7 +21,10 @@ pub enum GatewayClientError { NoSharedKeyAvailable, ConnectionAbruptlyClosed, MalformedResponse, + SerializeCredential, NotAuthenticated, + NotEnoughBandwidth, + UnexpectedResponse, ConnectionInInvalidState, RegistrationFailure(HandshakeError), AuthenticationFailure, @@ -98,6 +101,13 @@ impl fmt::Display for GatewayClientError { GatewayClientError::GatewayError(err) => { write!(f, "gateway returned an error response - {}", err) } + GatewayClientError::UnexpectedResponse => write!(f, "received an unexpected response"), + GatewayClientError::NotEnoughBandwidth => { + write!(f, "client does not have enough bandwidth") + } + GatewayClientError::SerializeCredential => { + write!(f, "credential could not be serialized") + } } } } diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index bbb8a6f311..78c7519e4f 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -24,8 +24,8 @@ network-defaults = { path = "../../network-defaults" } # perhaps after https://github.com/cosmos/cosmos-rust/pull/97 is resolved (and tendermint-rs is updated) async-trait = { version = "0.1.51", optional = true } bip39 = { version = "1", features = ["rand"], optional = true } -config = { path = "../../config", optional = true} -cosmos_sdk = { git = "https://github.com/cosmos/cosmos-rust/", rev="ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30", features = ["rpc", "bip32", "cosmwasm"], optional = true } +config = { path = "../../config", optional = true } +cosmrs = { version = "0.1", features = ["rpc", "bip32", "cosmwasm"], optional = true } prost = { version = "0.7", default-features = false, optional = true } flate2 = { version = "1.0.20", optional = true } sha2 = { version = "0.9.5", optional = true } @@ -34,4 +34,4 @@ cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-up ts-rs = "3.0" [features] -nymd-client = ["async-trait", "bip39", "config", "cosmos_sdk", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"] +nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"] diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 595b97c185..c093b08d9d 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -14,7 +14,7 @@ use url::Url; pub struct Config { api_url: Url, nymd_url: Url, - mixnet_contract_address: Option, + mixnet_contract_address: Option, mixnode_page_limit: Option, gateway_page_limit: Option, @@ -27,7 +27,7 @@ impl Config { pub fn new( nymd_url: Url, api_url: Url, - mixnet_contract_address: Option, + mixnet_contract_address: Option, ) -> Self { Config { nymd_url, @@ -63,7 +63,7 @@ impl Config { #[cfg(feature = "nymd-client")] pub struct Client { - mixnet_contract_address: Option, + mixnet_contract_address: Option, mnemonic: Option, mixnode_page_limit: Option, @@ -154,7 +154,7 @@ impl Client { // use case: somebody initialised client without a contract in order to upload and initialise one // and now they want to actually use it without making new client - pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmos_sdk::AccountId) { + pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmrs::AccountId) { self.mixnet_contract_address = Some(mixnet_contract_address) } @@ -243,6 +243,58 @@ impl Client { Ok(delegations) } + pub async fn get_all_nymd_reverse_mixnode_delegations( + &self, + delegation_owner: &cosmrs::AccountId, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut delegations = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .nymd + .get_reverse_mix_delegations_paged( + mixnet_contract::Addr::unchecked(delegation_owner.as_ref()), + start_after.take(), + self.mixnode_delegations_page_limit, + ) + .await?; + delegations.append(&mut paged_response.delegated_nodes); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(delegations) + } + + pub async fn get_all_nymd_mixnode_delegations_of_owner( + &self, + delegation_owner: &cosmrs::AccountId, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut delegations = Vec::new(); + for node_identity in self + .get_all_nymd_reverse_mixnode_delegations(delegation_owner) + .await? + { + let delegation = self + .nymd + .get_mix_delegation(node_identity, delegation_owner) + .await?; + delegations.push(delegation); + } + + Ok(delegations) + } + pub async fn get_all_nymd_gateway_delegations( &self, identity: mixnet_contract::IdentityKey, @@ -273,6 +325,58 @@ impl Client { Ok(delegations) } + pub async fn get_all_nymd_reverse_gateway_delegations( + &self, + delegation_owner: &cosmrs::AccountId, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut delegations = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .nymd + .get_reverse_gateway_delegations_paged( + mixnet_contract::Addr::unchecked(delegation_owner.as_ref()), + start_after.take(), + self.mixnode_delegations_page_limit, + ) + .await?; + delegations.append(&mut paged_response.delegated_nodes); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(delegations) + } + + pub async fn get_all_nymd_gateway_delegations_of_owner( + &self, + delegation_owner: &cosmrs::AccountId, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut delegations = Vec::new(); + for node_identity in self + .get_all_nymd_reverse_gateway_delegations(delegation_owner) + .await? + { + let delegation = self + .nymd + .get_gateway_delegation(node_identity, delegation_owner) + .await?; + delegations.push(delegation); + } + + Ok(delegations) + } + pub async fn blind_sign( &self, request_body: &BlindSignRequestBody, diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs index 1a78c9c33c..8bd8a0b207 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs @@ -8,21 +8,21 @@ use crate::nymd::cosmwasm_client::types::{ }; use crate::nymd::error::NymdError; use async_trait::async_trait; -use cosmos_sdk::proto::cosmos::auth::v1beta1::{ +use cosmrs::proto::cosmos::auth::v1beta1::{ BaseAccount, QueryAccountRequest, QueryAccountResponse, }; -use cosmos_sdk::proto::cosmos::bank::v1beta1::{ +use cosmrs::proto::cosmos::bank::v1beta1::{ QueryAllBalancesRequest, QueryAllBalancesResponse, QueryBalanceRequest, QueryBalanceResponse, }; -use cosmos_sdk::proto::cosmwasm::wasm::v1beta1::*; -use cosmos_sdk::rpc::endpoint::block::Response as BlockResponse; -use cosmos_sdk::rpc::endpoint::broadcast; -use cosmos_sdk::rpc::endpoint::tx::Response as TxResponse; -use cosmos_sdk::rpc::query::Query; -use cosmos_sdk::rpc::{self, HttpClient, Order}; -use cosmos_sdk::tendermint::abci::Transaction; -use cosmos_sdk::tendermint::{abci, block, chain}; -use cosmos_sdk::{AccountId, Coin, Denom}; +use cosmrs::proto::cosmwasm::wasm::v1beta1::*; +use cosmrs::rpc::endpoint::block::Response as BlockResponse; +use cosmrs::rpc::endpoint::broadcast; +use cosmrs::rpc::endpoint::tx::Response as TxResponse; +use cosmrs::rpc::query::Query; +use cosmrs::rpc::{self, HttpClient, Order}; +use cosmrs::tendermint::abci::Transaction; +use cosmrs::tendermint::{abci, block, chain}; +use cosmrs::{AccountId, Coin, Denom}; use prost::Message; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs index 66a736aa52..2b4ef52226 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::nymd::error::NymdError; -use cosmos_sdk::proto::cosmos::base::query::v1beta1::PageRequest; -use cosmos_sdk::rpc::endpoint::broadcast; +use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest; +use cosmrs::rpc::endpoint::broadcast; use flate2::write::GzEncoder; use flate2::Compression; use std::io::Write; diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs index 115ffa1abc..462b5bb580 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::nymd::error::NymdError; -use cosmos_sdk::tendermint::abci; +use cosmrs::tendermint::abci; use itertools::Itertools; use serde::Deserialize; diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs index 5b04e0d47d..86a489fca0 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs @@ -3,7 +3,7 @@ use crate::nymd::error::NymdError; use crate::nymd::wallet::DirectSecp256k1HdWallet; -use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl}; +use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl}; use std::convert::TryInto; pub mod client; diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs index afd1392680..3eb6a32042 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs @@ -8,13 +8,13 @@ use crate::nymd::cosmwasm_client::types::*; use crate::nymd::error::NymdError; use crate::nymd::wallet::DirectSecp256k1HdWallet; use async_trait::async_trait; -use cosmos_sdk::bank::MsgSend; -use cosmos_sdk::distribution::MsgWithdrawDelegatorReward; -use cosmos_sdk::rpc::endpoint::broadcast; -use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest}; -use cosmos_sdk::staking::{MsgDelegate, MsgUndelegate}; -use cosmos_sdk::tx::{Fee, Msg, MsgType, SignDoc, SignerInfo}; -use cosmos_sdk::{cosmwasm, rpc, tx, AccountId, Coin}; +use cosmrs::bank::MsgSend; +use cosmrs::distribution::MsgWithdrawDelegatorReward; +use cosmrs::rpc::endpoint::broadcast; +use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest}; +use cosmrs::staking::{MsgDelegate, MsgUndelegate}; +use cosmrs::tx::{Fee, Msg, MsgType, SignDoc, SignerInfo}; +use cosmrs::{cosmwasm, rpc, tx, AccountId, Coin}; use serde::Serialize; use sha2::Digest; use sha2::Sha256; @@ -287,7 +287,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { let delegate_msg = MsgDelegate { delegator_address: delegator_address.to_owned(), validator_address: validator_address.to_owned(), - amount: Some(amount), + amount, } .to_msg() .map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?; @@ -407,7 +407,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { let auth_info = signer_info.auth_info(fee); // ideally I'd prefer to have the entire error put into the NymdError::SigningFailure - // but I'm super hesitant to trying to downcast the eyre::Report to cosmos_sdk::error::Error + // but I'm super hesitant to trying to downcast the eyre::Report to cosmrs::error::Error let sign_doc = SignDoc::new( &tx_body, &auth_info, diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs index 179447001f..65767c3ed5 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs @@ -5,15 +5,15 @@ use crate::nymd::cosmwasm_client::logs::Log; use crate::nymd::error::NymdError; -use cosmos_sdk::crypto::PublicKey; -use cosmos_sdk::proto::cosmos::auth::v1beta1::BaseAccount; -use cosmos_sdk::proto::cosmwasm::wasm::v1beta1::{ +use cosmrs::crypto::PublicKey; +use cosmrs::proto::cosmos::auth::v1beta1::BaseAccount; +use cosmrs::proto::cosmwasm::wasm::v1beta1::{ CodeInfoResponse, ContractCodeHistoryEntry as ProtoContractCodeHistoryEntry, ContractCodeHistoryOperationType, ContractInfo as ProtoContractInfo, }; -use cosmos_sdk::tendermint::chain; -use cosmos_sdk::tx::{AccountNumber, SequenceNumber}; -use cosmos_sdk::{tx, AccountId, Coin}; +use cosmrs::tendermint::chain; +use cosmrs::tx::{AccountNumber, SequenceNumber}; +use cosmrs::{tx, AccountId, Coin}; use serde::Serialize; use std::convert::TryFrom; diff --git a/common/client-libs/validator-client/src/nymd/error.rs b/common/client-libs/validator-client/src/nymd/error.rs index d4b0b15f15..01f730f477 100644 --- a/common/client-libs/validator-client/src/nymd/error.rs +++ b/common/client-libs/validator-client/src/nymd/error.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::nymd::cosmwasm_client::types::ContractCodeId; -use cosmos_sdk::tendermint::block; -use cosmos_sdk::{bip32, rpc, tx, AccountId}; +use cosmrs::tendermint::block; +use cosmrs::{bip32, rpc, tx, AccountId}; use std::io; use thiserror::Error; diff --git a/common/client-libs/validator-client/src/nymd/fee_helpers.rs b/common/client-libs/validator-client/src/nymd/fee_helpers.rs index cff3c1aeb9..a8cc7fd096 100644 --- a/common/client-libs/validator-client/src/nymd/fee_helpers.rs +++ b/common/client-libs/validator-client/src/nymd/fee_helpers.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::nymd::GasPrice; -use cosmos_sdk::tx::{Fee, Gas}; -use cosmos_sdk::Coin; +use cosmrs::tx::{Fee, Gas}; +use cosmrs::Coin; use cosmwasm_std::Uint128; use serde::{Deserialize, Serialize}; use std::fmt; diff --git a/common/client-libs/validator-client/src/nymd/gas_price.rs b/common/client-libs/validator-client/src/nymd/gas_price.rs index 12278c9c5a..6a76d6b5a5 100644 --- a/common/client-libs/validator-client/src/nymd/gas_price.rs +++ b/common/client-libs/validator-client/src/nymd/gas_price.rs @@ -3,7 +3,7 @@ use crate::nymd::error::NymdError; use config::defaults; -use cosmos_sdk::Denom; +use cosmrs::Denom; use cosmwasm_std::Decimal; use std::str::FromStr; diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 06ccc97aab..a096539ca8 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -9,16 +9,17 @@ use crate::nymd::cosmwasm_client::types::{ use crate::nymd::error::NymdError; use crate::nymd::fee_helpers::Operation; use crate::nymd::wallet::DirectSecp256k1HdWallet; -use cosmos_sdk::rpc::endpoint::broadcast; -use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClientUrl}; -use cosmos_sdk::tx::{Fee, Gas}; -use cosmos_sdk::Coin as CosmosCoin; -use cosmos_sdk::{AccountId, Denom}; +use cosmrs::rpc::endpoint::broadcast; +use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl}; +use cosmrs::tx::{Fee, Gas}; + use cosmwasm_std::Coin; use mixnet_contract::{ Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey, LayerDistribution, MixNode, MixOwnershipResponse, PagedGatewayDelegationsResponse, - PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse, QueryMsg, StateParams, + PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse, + PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, QueryMsg, + StateParams, }; use serde::Serialize; use std::collections::HashMap; @@ -27,7 +28,9 @@ use std::convert::TryInto; pub use crate::nymd::cosmwasm_client::client::CosmWasmClient; pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; pub use crate::nymd::gas_price::GasPrice; -pub use cosmos_sdk::rpc::HttpClient as QueryNymdClient; +pub use cosmrs::rpc::HttpClient as QueryNymdClient; +pub use cosmrs::Coin as CosmosCoin; +pub use cosmrs::{AccountId, Denom}; pub use signing_client::Client as SigningNymdClient; pub mod cosmwasm_client; @@ -268,6 +271,26 @@ impl NymdClient { .await } + /// Gets list of all the mixnodes on which a particular address delegated. + pub async fn get_reverse_mix_delegations_paged( + &self, + delegation_owner: Addr, + start_after: Option, + page_limit: Option, + ) -> Result + where + C: CosmWasmClient + Sync, + { + let request = QueryMsg::GetReverseMixDelegations { + delegation_owner, + start_after, + limit: page_limit, + }; + self.client + .query_contract_smart(self.contract_address()?, &request) + .await + } + /// Checks value of delegation of given client towards particular mixnode. pub async fn get_mix_delegation( &self, @@ -306,6 +329,26 @@ impl NymdClient { .await } + /// Gets list of all the gateways on which a particular address delegated. + pub async fn get_reverse_gateway_delegations_paged( + &self, + delegation_owner: Addr, + start_after: Option, + page_limit: Option, + ) -> Result + where + C: CosmWasmClient + Sync, + { + let request = QueryMsg::GetReverseGatewayDelegations { + delegation_owner, + start_after, + limit: page_limit, + }; + self.client + .query_contract_smart(self.contract_address()?, &request) + .await + } + /// Checks value of delegation of given client towards particular gateway. pub async fn get_gateway_delegation( &self, diff --git a/common/client-libs/validator-client/src/nymd/wallet/mod.rs b/common/client-libs/validator-client/src/nymd/wallet/mod.rs index c1e53631db..2a1c1d64c7 100644 --- a/common/client-libs/validator-client/src/nymd/wallet/mod.rs +++ b/common/client-libs/validator-client/src/nymd/wallet/mod.rs @@ -3,11 +3,11 @@ use crate::nymd::error::NymdError; use config::defaults; -use cosmos_sdk::bip32::{DerivationPath, XPrv}; -use cosmos_sdk::crypto::secp256k1::SigningKey; -use cosmos_sdk::crypto::PublicKey; -use cosmos_sdk::tx::SignDoc; -use cosmos_sdk::{tx, AccountId}; +use cosmrs::bip32::{DerivationPath, XPrv}; +use cosmrs::crypto::secp256k1::SigningKey; +use cosmrs::crypto::PublicKey; +use cosmrs::tx::SignDoc; +use cosmrs::{tx, AccountId}; /// Derivation information required to derive a keypair and an address from a mnemonic. #[derive(Debug)] @@ -97,7 +97,7 @@ impl DirectSecp256k1HdWallet { sign_doc: SignDoc, ) -> Result { // ideally I'd prefer to have the entire error put into the NymdError::SigningFailure - // but I'm super hesitant to trying to downcast the eyre::Report to cosmos_sdk::error::Error + // but I'm super hesitant to trying to downcast the eyre::Report to cosmrs::error::Error sign_doc .sign(&signer.private_key) .map_err(|_| NymdError::SigningFailure) diff --git a/common/coconut-interface/Cargo.toml b/common/coconut-interface/Cargo.toml index 2a5ed40a53..3303688d9f 100644 --- a/common/coconut-interface/Cargo.toml +++ b/common/coconut-interface/Cargo.toml @@ -6,7 +6,6 @@ description = "Crutch library until there is proper SerDe support for coconut st [dependencies] serde = { version = "1.0", features = ["derive"] } -sha2 = "0.9.3" getset = "0.1.1" -coconut-rs = { git = "https://github.com/nymtech/coconut.git", branch = "0.4.0" } +coconut-rs = { git = "https://github.com/nymtech/coconut.git", branch = "0.5.0" } diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 823dfb562d..3d0a09881d 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -3,10 +3,6 @@ use getset::{CopyGetters, Getters}; use serde::{Deserialize, Serialize}; -use sha2::{ - digest::{generic_array::typenum::Unsigned, Digest}, - Sha256, -}; pub use coconut_rs::*; @@ -16,7 +12,7 @@ pub struct Credential { n_params: u32, #[getset(get = "pub")] theta: Theta, - public_attributes: Vec, + public_attributes: Vec>, #[getset(get = "pub")] signature: Signature, } @@ -24,35 +20,29 @@ impl Credential { pub fn new( n_params: u32, theta: Theta, - public_attributes: &[Attribute], + public_attributes: Vec>, signature: &Signature, ) -> Credential { Credential { n_params, theta, - public_attributes: public_attributes - .iter() - .map(|attr| attr.to_bs58()) - .collect(), + public_attributes, signature: *signature, } } - pub fn public_attributes(&self) -> Vec { - self.public_attributes - .iter() - .map(|x| Attribute::try_from_bs58(x).unwrap()) - .collect() + pub fn public_attributes(&self) -> Vec> { + self.public_attributes.clone() } - pub async fn verify(&self, verification_key: &VerificationKey) -> bool { + pub fn verify(&self, verification_key: &VerificationKey) -> bool { let params = Parameters::new(self.n_params).unwrap(); - coconut_rs::verify_credential( - ¶ms, - verification_key, - &self.theta, - &self.public_attributes(), - ) + let public_attributes = self + .public_attributes + .iter() + .map(hash_to_scalar) + .collect::>(); + coconut_rs::verify_credential(¶ms, verification_key, &self.theta, &public_attributes) } } @@ -147,21 +137,3 @@ impl VerificationKeyResponse { VerificationKeyResponse { key } } } - -pub fn hash_to_scalar(msg: M) -> Attribute -where - M: AsRef<[u8]>, -{ - let mut h = Sha256::new(); - h.update(msg); - let digest = h.finalize(); - - let mut bytes = [0u8; 64]; - let pad_size = 64usize - .checked_sub(::OutputSize::to_usize()) - .unwrap_or_default(); - - bytes[pad_size..].copy_from_slice(&digest); - - Attribute::from_bytes_wide(&bytes) -} diff --git a/common/credentials/src/bandwidth.rs b/common/credentials/src/bandwidth.rs index 66096f732b..0dff055a30 100644 --- a/common/credentials/src/bandwidth.rs +++ b/common/credentials/src/bandwidth.rs @@ -6,12 +6,13 @@ // right now this has no double-spending protection, spender binding, etc // it's the simplest possible case +use url::Url; + use crate::error::Error; use crate::utils::{obtain_aggregate_signature, prepare_credential_for_spending}; use coconut_interface::{hash_to_scalar, Credential, Parameters, Signature, VerificationKey}; -use url::Url; -const BANDWIDTH_VALUE: &str = "Bandwidth: infinite (for now)"; +const BANDWIDTH_VALUE: u64 = 1024 * 1024; // 1 MB pub const PUBLIC_ATTRIBUTES: u32 = 1; pub const PRIVATE_ATTRIBUTES: u32 = 1; @@ -19,8 +20,8 @@ pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES; // TODO: this definitely has to be moved somewhere else. It's just a temporary solution pub async fn obtain_signature(raw_identity: &[u8], validators: &[Url]) -> Result { - let public_attributes = vec![hash_to_scalar(raw_identity)]; - let private_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE)]; + let public_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes())]; + let private_attributes = vec![hash_to_scalar(raw_identity)]; let params = Parameters::new(TOTAL_ATTRIBUTES)?; @@ -32,15 +33,15 @@ pub fn prepare_for_spending( signature: &Signature, verification_key: &VerificationKey, ) -> Result { - let public_attributes = vec![hash_to_scalar(raw_identity)]; - let private_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE)]; + let public_attributes = vec![BANDWIDTH_VALUE.to_be_bytes().to_vec()]; + let private_attributes = vec![raw_identity.to_vec()]; let params = Parameters::new(TOTAL_ATTRIBUTES)?; prepare_credential_for_spending( ¶ms, - &public_attributes, - &private_attributes, + public_attributes, + private_attributes, signature, verification_key, ) diff --git a/common/credentials/src/error.rs b/common/credentials/src/error.rs index 552d346d6d..f2161ca9ac 100644 --- a/common/credentials/src/error.rs +++ b/common/credentials/src/error.rs @@ -18,4 +18,16 @@ pub enum Error { #[error("Run into a validato client error - {0}")] ValidatorClientError(#[from] ValidatorClientError), + + #[error("Not enough public attributes were specified")] + NotEnoughPublicAttributes, + + #[error("Bandwidth is expected to be represented on 8 bytes")] + InvalidBandwidthSize, + + #[error("Bandwidth operation overflowed. {0}")] + BandwidthOverflow(String), + + #[error("There is not associated bandwidth for the given client")] + MissingBandwidth, } diff --git a/common/credentials/src/utils.rs b/common/credentials/src/utils.rs index d12ab4decc..b4f7438f74 100644 --- a/common/credentials/src/utils.rs +++ b/common/credentials/src/utils.rs @@ -3,9 +3,9 @@ use crate::error::Error; use coconut_interface::{ - aggregate_signature_shares, aggregate_verification_keys, prepare_blind_sign, prove_credential, - Attribute, BlindSignRequestBody, Credential, Parameters, Signature, SignatureShare, - VerificationKey, + aggregate_signature_shares, aggregate_verification_keys, hash_to_scalar, prepare_blind_sign, + prove_credential, Attribute, BlindSignRequestBody, Credential, Parameters, Signature, + SignatureShare, VerificationKey, }; use url::Url; @@ -118,12 +118,16 @@ pub async fn obtain_aggregate_signature( // TODO: better type flow pub fn prepare_credential_for_spending( params: &Parameters, - public_attributes: &[Attribute], - private_attributes: &[Attribute], + public_attributes: Vec>, + private_attributes: Vec>, signature: &Signature, verification_key: &VerificationKey, ) -> Result { - let theta = prove_credential(params, verification_key, signature, private_attributes)?; + let private_attributes = private_attributes + .iter() + .map(hash_to_scalar) + .collect::>(); + let theta = prove_credential(params, verification_key, signature, &private_attributes)?; Ok(Credential::new( (public_attributes.len() + private_attributes.len()) as u32, diff --git a/common/mixnet-contract/src/delegation.rs b/common/mixnet-contract/src/delegation.rs index e17f588e46..f0efe37297 100644 --- a/common/mixnet-contract/src/delegation.rs +++ b/common/mixnet-contract/src/delegation.rs @@ -2,20 +2,40 @@ #![allow(clippy::field_reassign_with_default)] use crate::{Addr, IdentityKey}; -use cosmwasm_std::Coin; +use cosmwasm_std::{Coin, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::Display; +#[derive(Debug, Deserialize, PartialEq, Serialize)] +pub struct RawDelegationData { + pub amount: Uint128, + pub block_height: u64, +} + +impl RawDelegationData { + pub fn new(amount: Uint128, block_height: u64) -> Self { + RawDelegationData { + amount, + block_height, + } + } +} + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct Delegation { owner: Addr, amount: Coin, + block_height: u64, } impl Delegation { - pub fn new(owner: Addr, amount: Coin) -> Self { - Delegation { owner, amount } + pub fn new(owner: Addr, amount: Coin, block_height: u64) -> Self { + Delegation { + owner, + amount, + block_height, + } } pub fn amount(&self) -> &Coin { @@ -25,14 +45,18 @@ impl Delegation { pub fn owner(&self) -> Addr { self.owner.clone() } + + pub fn block_height(&self) -> u64 { + self.block_height + } } impl Display for Delegation { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, - "{} {} delegated by {}", - self.amount.amount, self.amount.denom, self.owner + "{} {} delegated by {} at block {}", + self.amount.amount, self.amount.denom, self.owner, self.block_height ) } } @@ -58,6 +82,27 @@ impl PagedMixDelegationsResponse { } } +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] +pub struct PagedReverseMixDelegationsResponse { + pub delegation_owner: Addr, + pub delegated_nodes: Vec, + pub start_next_after: Option, +} + +impl PagedReverseMixDelegationsResponse { + pub fn new( + delegation_owner: Addr, + delegated_nodes: Vec, + start_next_after: Option, + ) -> Self { + PagedReverseMixDelegationsResponse { + delegation_owner, + delegated_nodes, + start_next_after, + } + } +} + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct PagedGatewayDelegationsResponse { pub node_identity: IdentityKey, @@ -78,3 +123,24 @@ impl PagedGatewayDelegationsResponse { } } } + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] +pub struct PagedReverseGatewayDelegationsResponse { + pub delegation_owner: Addr, + pub delegated_nodes: Vec, + pub start_next_after: Option, +} + +impl PagedReverseGatewayDelegationsResponse { + pub fn new( + delegation_owner: Addr, + delegated_nodes: Vec, + start_next_after: Option, + ) -> Self { + PagedReverseGatewayDelegationsResponse { + delegation_owner, + delegated_nodes, + start_next_after, + } + } +} diff --git a/common/mixnet-contract/src/gateway.rs b/common/mixnet-contract/src/gateway.rs index eb63fb4dae..68acfc96de 100644 --- a/common/mixnet-contract/src/gateway.rs +++ b/common/mixnet-contract/src/gateway.rs @@ -8,6 +8,8 @@ use serde::{Deserialize, Serialize}; use std::fmt::Display; use ts_rs::TS; +use crate::current_block_height; + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema, TS)] pub struct Gateway { pub host: String, @@ -25,15 +27,18 @@ pub struct GatewayBond { pub bond_amount: Coin, pub total_delegation: Coin, pub owner: Addr, + #[serde(default = "current_block_height")] + pub block_height: u64, pub gateway: Gateway, } impl GatewayBond { - pub fn new(bond_amount: Coin, owner: Addr, gateway: Gateway) -> Self { + pub fn new(bond_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self { GatewayBond { total_delegation: coin(0, &bond_amount.denom), bond_amount, owner, + block_height, gateway, } } diff --git a/common/mixnet-contract/src/lib.rs b/common/mixnet-contract/src/lib.rs index 6779514b29..a3ad1a7fc2 100644 --- a/common/mixnet-contract/src/lib.rs +++ b/common/mixnet-contract/src/lib.rs @@ -8,10 +8,18 @@ mod msg; mod types; pub use cosmwasm_std::{Addr, Coin}; -pub use delegation::{Delegation, PagedGatewayDelegationsResponse, PagedMixDelegationsResponse}; +pub use delegation::{ + Delegation, PagedGatewayDelegationsResponse, PagedMixDelegationsResponse, + PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, RawDelegationData, +}; pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse}; pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse}; pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; pub use types::{IdentityKey, IdentityKeyRef, LayerDistribution, SphinxKey, StateParams}; -pub use types::default_delegation_reward; +use std::sync::atomic::{AtomicU64, Ordering}; +pub static CURRENT_BLOCK_HEIGHT: AtomicU64 = AtomicU64::new(0); + +pub fn current_block_height() -> u64 { + CURRENT_BLOCK_HEIGHT.load(Ordering::Relaxed) +} diff --git a/common/mixnet-contract/src/mixnode.rs b/common/mixnet-contract/src/mixnode.rs index 5822d46618..2c3303b3a9 100644 --- a/common/mixnet-contract/src/mixnode.rs +++ b/common/mixnet-contract/src/mixnode.rs @@ -9,6 +9,8 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use std::fmt::Display; use ts_rs::TS; +use crate::current_block_height; + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema, TS)] pub struct MixNode { pub host: String, @@ -36,16 +38,25 @@ pub struct MixNodeBond { pub total_delegation: Coin, pub owner: Addr, pub layer: Layer, + #[serde(default = "current_block_height")] + pub block_height: u64, pub mix_node: MixNode, } impl MixNodeBond { - pub fn new(bond_amount: Coin, owner: Addr, layer: Layer, mix_node: MixNode) -> Self { + pub fn new( + bond_amount: Coin, + owner: Addr, + layer: Layer, + block_height: u64, + mix_node: MixNode, + ) -> Self { MixNodeBond { total_delegation: coin(0, &bond_amount.denom), bond_amount, owner, layer, + block_height, mix_node, } } diff --git a/common/mixnet-contract/src/msg.rs b/common/mixnet-contract/src/msg.rs index 43fdcc0d90..32cb96da86 100644 --- a/common/mixnet-contract/src/msg.rs +++ b/common/mixnet-contract/src/msg.rs @@ -75,6 +75,11 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, + GetReverseMixDelegations { + delegation_owner: Addr, + start_after: Option, + limit: Option, + }, GetMixDelegation { mix_identity: IdentityKey, address: Addr, @@ -84,6 +89,11 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, + GetReverseGatewayDelegations { + delegation_owner: Addr, + start_after: Option, + limit: Option, + }, GetGatewayDelegation { gateway_identity: IdentityKey, address: Addr, diff --git a/common/mixnet-contract/src/types.rs b/common/mixnet-contract/src/types.rs index 8a202fde61..9d0404daf0 100644 --- a/common/mixnet-contract/src/types.rs +++ b/common/mixnet-contract/src/types.rs @@ -26,10 +26,6 @@ impl LayerDistribution { } } -pub fn default_delegation_reward() -> Decimal { - Decimal::percent(110) -} - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct StateParams { pub epoch_length: u32, // length of an epoch, expressed in hours @@ -38,9 +34,7 @@ pub struct StateParams { pub minimum_gateway_bond: Uint128, // minimum amount a gateway must bond to get into the system pub mixnode_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25 pub gateway_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25 - #[serde(default = "default_delegation_reward")] pub mixnode_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25 - #[serde(default = "default_delegation_reward")] pub gateway_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25 pub mixnode_active_set_size: u32, } diff --git a/contracts/mixnet/Cargo.lock b/contracts/mixnet/Cargo.lock index ed361dcf24..b36c6fb5a5 100644 --- a/contracts/mixnet/Cargo.lock +++ b/contracts/mixnet/Cargo.lock @@ -727,9 +727,9 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" [[package]] name = "syn" -version = "1.0.60" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663" dependencies = [ "proc-macro2", "quote", diff --git a/contracts/mixnet/schema/execute_msg.json b/contracts/mixnet/schema/execute_msg.json new file mode 100644 index 0000000000..b329acb2a8 --- /dev/null +++ b/contracts/mixnet/schema/execute_msg.json @@ -0,0 +1,349 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "anyOf": [ + { + "type": "object", + "required": [ + "bond_mixnode" + ], + "properties": { + "bond_mixnode": { + "type": "object", + "required": [ + "mix_node" + ], + "properties": { + "mix_node": { + "$ref": "#/definitions/MixNode" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_mixnode" + ], + "properties": { + "unbond_mixnode": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond_gateway" + ], + "properties": { + "bond_gateway": { + "type": "object", + "required": [ + "gateway" + ], + "properties": { + "gateway": { + "$ref": "#/definitions/Gateway" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_gateway" + ], + "properties": { + "unbond_gateway": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_state_params" + ], + "properties": { + "update_state_params": { + "$ref": "#/definitions/StateParams" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delegate_to_mixnode" + ], + "properties": { + "delegate_to_mixnode": { + "type": "object", + "required": [ + "mix_identity" + ], + "properties": { + "mix_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "undelegate_from_mixnode" + ], + "properties": { + "undelegate_from_mixnode": { + "type": "object", + "required": [ + "mix_identity" + ], + "properties": { + "mix_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delegate_to_gateway" + ], + "properties": { + "delegate_to_gateway": { + "type": "object", + "required": [ + "gateway_identity" + ], + "properties": { + "gateway_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "undelegate_from_gateway" + ], + "properties": { + "undelegate_from_gateway": { + "type": "object", + "required": [ + "gateway_identity" + ], + "properties": { + "gateway_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reward_mixnode" + ], + "properties": { + "reward_mixnode": { + "type": "object", + "required": [ + "identity", + "uptime" + ], + "properties": { + "identity": { + "type": "string" + }, + "uptime": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reward_gateway" + ], + "properties": { + "reward_gateway": { + "type": "object", + "required": [ + "identity", + "uptime" + ], + "properties": { + "identity": { + "type": "string" + }, + "uptime": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Gateway": { + "type": "object", + "required": [ + "clients_port", + "host", + "identity_key", + "location", + "mix_port", + "sphinx_key", + "version" + ], + "properties": { + "clients_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "type": "string" + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", + "type": "string" + }, + "location": { + "type": "string" + }, + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "MixNode": { + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "type": "string" + }, + "http_api_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "type": "string" + }, + "verloc_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "type": "string" + } + } + }, + "StateParams": { + "type": "object", + "required": [ + "epoch_length", + "gateway_bond_reward_rate", + "gateway_delegation_reward_rate", + "minimum_gateway_bond", + "minimum_mixnode_bond", + "mixnode_active_set_size", + "mixnode_bond_reward_rate", + "mixnode_delegation_reward_rate" + ], + "properties": { + "epoch_length": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "gateway_bond_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "gateway_delegation_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "minimum_gateway_bond": { + "$ref": "#/definitions/Uint128" + }, + "minimum_mixnode_bond": { + "$ref": "#/definitions/Uint128" + }, + "mixnode_active_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mixnode_bond_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "mixnode_delegation_reward_rate": { + "$ref": "#/definitions/Decimal" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/handle_msg.json b/contracts/mixnet/schema/handle_msg.json deleted file mode 100644 index 8e3729ceb6..0000000000 --- a/contracts/mixnet/schema/handle_msg.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "register_mixnode" - ], - "properties": { - "register_mixnode": { - "type": "object", - "required": [ - "mix_node" - ], - "properties": { - "mix_node": { - "$ref": "#/definitions/MixNode" - } - } - } - } - }, - { - "type": "object", - "required": [ - "un_register_mixnode" - ], - "properties": { - "un_register_mixnode": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "bond_gateway" - ], - "properties": { - "bond_gateway": { - "type": "object", - "required": [ - "gateway" - ], - "properties": { - "gateway": { - "$ref": "#/definitions/Gateway" - } - } - } - } - }, - { - "type": "object", - "required": [ - "unbond_gateway" - ], - "properties": { - "unbond_gateway": { - "type": "object" - } - } - } - ], - "definitions": { - "Gateway": { - "type": "object", - "required": [ - "clients_host", - "identity_key", - "location", - "mix_host", - "sphinx_key", - "version" - ], - "properties": { - "clients_host": { - "type": "string" - }, - "identity_key": { - "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", - "type": "string" - }, - "location": { - "type": "string" - }, - "mix_host": { - "type": "string" - }, - "sphinx_key": { - "type": "string" - }, - "version": { - "type": "string" - } - } - }, - "MixNode": { - "type": "object", - "required": [ - "host", - "identity_key", - "layer", - "location", - "sphinx_key", - "version" - ], - "properties": { - "host": { - "type": "string" - }, - "identity_key": { - "description": "Base58 encoded ed25519 EdDSA public key.", - "type": "string" - }, - "layer": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "location": { - "type": "string" - }, - "sphinx_key": { - "type": "string" - }, - "version": { - "type": "string" - } - } - } - } -} diff --git a/contracts/mixnet/schema/init_msg.json b/contracts/mixnet/schema/instantiate_msg.json similarity index 73% rename from contracts/mixnet/schema/init_msg.json rename to contracts/mixnet/schema/instantiate_msg.json index 2b274b4e6d..44588cf226 100644 --- a/contracts/mixnet/schema/init_msg.json +++ b/contracts/mixnet/schema/instantiate_msg.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitMsg", + "title": "InstantiateMsg", "type": "object" } diff --git a/contracts/mixnet/schema/mix_node_bond.json b/contracts/mixnet/schema/mix_node_bond.json index bc6e6c97a2..21c52482ec 100644 --- a/contracts/mixnet/schema/mix_node_bond.json +++ b/contracts/mixnet/schema/mix_node_bond.json @@ -3,25 +3,40 @@ "title": "MixNodeBond", "type": "object", "required": [ - "amount", + "bond_amount", + "layer", "mix_node", - "owner" + "owner", + "total_delegation" ], "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + "block_height": { + "default": 0, + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "bond_amount": { + "$ref": "#/definitions/Coin" + }, + "layer": { + "$ref": "#/definitions/Layer" }, "mix_node": { "$ref": "#/definitions/MixNode" }, "owner": { - "$ref": "#/definitions/HumanAddr" + "$ref": "#/definitions/Addr" + }, + "total_delegation": { + "$ref": "#/definitions/Coin" } }, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Coin": { "type": "object", "required": [ @@ -37,44 +52,59 @@ } } }, - "HumanAddr": { - "type": "string" + "Layer": { + "type": "string", + "enum": [ + "Gateway", + "One", + "Two", + "Three" + ] }, "MixNode": { "type": "object", "required": [ "host", + "http_api_port", "identity_key", - "layer", - "location", + "mix_port", "sphinx_key", + "verloc_port", "version" ], "properties": { "host": { "type": "string" }, + "http_api_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, "identity_key": { "description": "Base58 encoded ed25519 EdDSA public key.", "type": "string" }, - "layer": { + "mix_port": { "type": "integer", - "format": "uint64", + "format": "uint16", "minimum": 0.0 }, - "location": { - "type": "string" - }, "sphinx_key": { "type": "string" }, + "verloc_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, "version": { "type": "string" } } }, "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" } } diff --git a/contracts/mixnet/schema/query_msg.json b/contracts/mixnet/schema/query_msg.json index 43c7bff800..5a9c13e1fd 100644 --- a/contracts/mixnet/schema/query_msg.json +++ b/contracts/mixnet/schema/query_msg.json @@ -20,18 +20,15 @@ "minimum": 0.0 }, "start_after": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } + "type": [ + "string", + "null" ] } } } - } + }, + "additionalProperties": false }, { "type": "object", @@ -50,10 +47,96 @@ "format": "uint32", "minimum": 0.0 }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "owns_mixnode" + ], + "properties": { + "owns_mixnode": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "owns_gateway" + ], + "properties": { + "owns_gateway": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "state_params" + ], + "properties": { + "state_params": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_mix_delegations" + ], + "properties": { + "get_mix_delegations": { + "type": "object", + "required": [ + "mix_identity" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "mix_identity": { + "type": "string" + }, "start_after": { "anyOf": [ { - "$ref": "#/definitions/HumanAddr" + "$ref": "#/definitions/Addr" }, { "type": "null" @@ -62,11 +145,179 @@ } } } - } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_reverse_mix_delegations" + ], + "properties": { + "get_reverse_mix_delegations": { + "type": "object", + "required": [ + "delegation_owner" + ], + "properties": { + "delegation_owner": { + "$ref": "#/definitions/Addr" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_mix_delegation" + ], + "properties": { + "get_mix_delegation": { + "type": "object", + "required": [ + "address", + "mix_identity" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "mix_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_gateway_delegations" + ], + "properties": { + "get_gateway_delegations": { + "type": "object", + "required": [ + "gateway_identity" + ], + "properties": { + "gateway_identity": { + "type": "string" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_reverse_gateway_delegations" + ], + "properties": { + "get_reverse_gateway_delegations": { + "type": "object", + "required": [ + "delegation_owner" + ], + "properties": { + "delegation_owner": { + "$ref": "#/definitions/Addr" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_gateway_delegation" + ], + "properties": { + "get_gateway_delegation": { + "type": "object", + "required": [ + "address", + "gateway_identity" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "gateway_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "layer_distribution" + ], + "properties": { + "layer_distribution": { + "type": "object" + } + }, + "additionalProperties": false } ], "definitions": { - "HumanAddr": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "type": "string" } } diff --git a/contracts/mixnet/schema/state.json b/contracts/mixnet/schema/state.json index d559e403ee..33ea553981 100644 --- a/contracts/mixnet/schema/state.json +++ b/contracts/mixnet/schema/state.json @@ -3,15 +3,91 @@ "title": "State", "type": "object", "required": [ - "owner" + "gateway_epoch_bond_reward", + "gateway_epoch_delegation_reward", + "mixnode_epoch_bond_reward", + "mixnode_epoch_delegation_reward", + "network_monitor_address", + "owner", + "params" ], "properties": { + "gateway_epoch_bond_reward": { + "$ref": "#/definitions/Decimal" + }, + "gateway_epoch_delegation_reward": { + "$ref": "#/definitions/Decimal" + }, + "mixnode_epoch_bond_reward": { + "$ref": "#/definitions/Decimal" + }, + "mixnode_epoch_delegation_reward": { + "$ref": "#/definitions/Decimal" + }, + "network_monitor_address": { + "$ref": "#/definitions/Addr" + }, "owner": { - "$ref": "#/definitions/HumanAddr" + "$ref": "#/definitions/Addr" + }, + "params": { + "$ref": "#/definitions/StateParams" } }, "definitions": { - "HumanAddr": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "StateParams": { + "type": "object", + "required": [ + "epoch_length", + "gateway_bond_reward_rate", + "gateway_delegation_reward_rate", + "minimum_gateway_bond", + "minimum_mixnode_bond", + "mixnode_active_set_size", + "mixnode_bond_reward_rate", + "mixnode_delegation_reward_rate" + ], + "properties": { + "epoch_length": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "gateway_bond_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "gateway_delegation_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "minimum_gateway_bond": { + "$ref": "#/definitions/Uint128" + }, + "minimum_mixnode_bond": { + "$ref": "#/definitions/Uint128" + }, + "mixnode_active_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mixnode_bond_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "mixnode_delegation_reward_rate": { + "$ref": "#/definitions/Decimal" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" } } diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 9dd24a7eb6..88b76a96bf 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -3,14 +3,16 @@ use crate::helpers::calculate_epoch_reward_rate; use crate::state::State; -use crate::storage::{config, config_read, layer_distribution}; +use crate::storage::{config, layer_distribution}; use crate::{error::ContractError, queries, transactions}; use config::defaults::NETWORK_MONITOR_ADDRESS; use cosmwasm_std::{ entry_point, to_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128, }; -use mixnet_contract::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StateParams}; +use mixnet_contract::{ + ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, RawDelegationData, StateParams, +}; pub const INITIAL_DEFAULT_EPOCH_LENGTH: u32 = 2; @@ -89,32 +91,36 @@ pub fn instantiate( #[entry_point] pub fn execute( deps: DepsMut, - _env: Env, + env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::BondMixnode { mix_node } => transactions::try_add_mixnode(deps, info, mix_node), + ExecuteMsg::BondMixnode { mix_node } => { + transactions::try_add_mixnode(deps, env, info, mix_node) + } ExecuteMsg::UnbondMixnode {} => transactions::try_remove_mixnode(deps, info), - ExecuteMsg::BondGateway { gateway } => transactions::try_add_gateway(deps, info, gateway), + ExecuteMsg::BondGateway { gateway } => { + transactions::try_add_gateway(deps, env, info, gateway) + } ExecuteMsg::UnbondGateway {} => transactions::try_remove_gateway(deps, info), ExecuteMsg::UpdateStateParams(params) => { transactions::try_update_state_params(deps, info, params) } ExecuteMsg::RewardMixnode { identity, uptime } => { - transactions::try_reward_mixnode(deps, info, identity, uptime) + transactions::try_reward_mixnode(deps, env, info, identity, uptime) } ExecuteMsg::RewardGateway { identity, uptime } => { - transactions::try_reward_gateway(deps, info, identity, uptime) + transactions::try_reward_gateway(deps, env, info, identity, uptime) } ExecuteMsg::DelegateToMixnode { mix_identity } => { - transactions::try_delegate_to_mixnode(deps, info, mix_identity) + transactions::try_delegate_to_mixnode(deps, env, info, mix_identity) } ExecuteMsg::UndelegateFromMixnode { mix_identity } => { transactions::try_remove_delegation_from_mixnode(deps, info, mix_identity) } ExecuteMsg::DelegateToGateway { gateway_identity } => { - transactions::try_delegate_to_gateway(deps, info, gateway_identity) + transactions::try_delegate_to_gateway(deps, env, info, gateway_identity) } ExecuteMsg::UndelegateFromGateway { gateway_identity } => { transactions::try_remove_delegation_from_gateway(deps, info, gateway_identity) @@ -149,6 +155,16 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result to_binary(&queries::query_reverse_mixnode_delegations_paged( + deps, + delegation_owner, + start_after, + limit, + )?), QueryMsg::GetMixDelegation { mix_identity, address, @@ -167,6 +183,16 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result to_binary(&queries::query_reverse_gateway_delegations_paged( + deps, + delegation_owner, + start_after, + limit, + )?), QueryMsg::GetGatewayDelegation { gateway_identity, address, @@ -181,9 +207,70 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Result { - let state = config_read(deps.storage).load().unwrap(); - config(deps.storage).save(&state)?; +pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + use crate::storage::{ + gateway_delegations, gateway_delegations_read, gateway_delegations_read_old, gateways_read, + mix_delegations, mix_delegations_read, mix_delegations_read_old, mixnodes_read, + }; + use crate::transactions::delegations; + use cosmwasm_std::{Order, StdResult}; + use mixnet_contract::{GatewayBond, MixNodeBond}; + + // Read existing delegations data, drop invalid values, and rewrite delegations data with valid data only + fn overwrite_mixnode_delegations_data( + identity: &str, + deps: &mut DepsMut, + ) -> Result<(), ContractError> { + let delegations_bucket = mix_delegations_read(deps.storage, identity); + let old_delegations_bucket = mix_delegations_read_old(deps.storage, identity); + let mut delegations_vec = delegations(delegations_bucket)?; + let old_delegations = delegations::(old_delegations_bucket)?; + for delegation in old_delegations { + delegations_vec.push((delegation.0, RawDelegationData::new(delegation.1, 1))) + } + + for (key, delegation) in delegations_vec { + mix_delegations(deps.storage, identity).save(&key, &delegation)?; + } + Ok(()) + } + + fn overwrite_gateway_delegations_data( + identity: &str, + deps: &mut DepsMut, + ) -> Result<(), ContractError> { + let delegations_bucket = gateway_delegations_read(deps.storage, identity); + let old_delegations_bucket = gateway_delegations_read_old(deps.storage, identity); + let mut delegations_vec = delegations(delegations_bucket)?; + let old_delegations = delegations::(old_delegations_bucket)?; + for delegation in old_delegations { + delegations_vec.push((delegation.0, RawDelegationData::new(delegation.1, 1))) + } + + for (key, delegation) in delegations_vec { + gateway_delegations(deps.storage, identity).save(&key, &delegation)?; + } + Ok(()) + } + + let mixnet_bonds = mixnodes_read(deps.storage) + .range(None, None, Order::Ascending) + .map(|res| res.map(|item| item.1)) + .collect::>>()?; + + for bond in mixnet_bonds { + overwrite_mixnode_delegations_data(bond.identity(), &mut deps)?; + } + + let gateway_bonds = gateways_read(deps.storage) + .range(None, None, Order::Ascending) + .map(|res| res.map(|item| item.1)) + .collect::>>()?; + + for bond in gateway_bonds { + overwrite_gateway_delegations_data(bond.identity(), &mut deps)?; + } + Ok(Default::default()) } diff --git a/contracts/mixnet/src/queries.rs b/contracts/mixnet/src/queries.rs index ed546a72d3..a9643a372e 100644 --- a/contracts/mixnet/src/queries.rs +++ b/contracts/mixnet/src/queries.rs @@ -5,6 +5,7 @@ use crate::error::ContractError; use crate::storage::{ gateway_delegations_read, gateways_owners_read, gateways_read, mix_delegations_read, mixnodes_owners_read, mixnodes_read, read_layer_distribution, read_state_params, + reverse_gateway_delegations_read, reverse_mix_delegations_read, }; use config::defaults::DENOM; use cosmwasm_std::Deps; @@ -14,7 +15,8 @@ use cosmwasm_std::{coin, Addr}; use mixnet_contract::{ Delegation, GatewayBond, GatewayOwnershipResponse, IdentityKey, LayerDistribution, MixNodeBond, MixOwnershipResponse, PagedGatewayDelegationsResponse, PagedGatewayResponse, - PagedMixDelegationsResponse, PagedMixnodeResponse, StateParams, + PagedMixDelegationsResponse, PagedMixnodeResponse, PagedReverseGatewayDelegationsResponse, + PagedReverseMixDelegationsResponse, StateParams, }; const BOND_PAGE_MAX_LIMIT: u32 = 100; @@ -123,8 +125,11 @@ pub(crate) fn query_mixnode_delegations_paged( .map(|res| { res.map(|entry| { Delegation::new( - Addr::unchecked(String::from_utf8(entry.0).unwrap()), - coin(entry.1.u128(), DENOM), + Addr::unchecked(String::from_utf8(entry.0).expect( + "Non-UTF8 address used as key in bucket. The storage is corrupted!", + )), + coin(entry.1.amount.u128(), DENOM), + entry.1.block_height, ) }) }) @@ -139,6 +144,37 @@ pub(crate) fn query_mixnode_delegations_paged( )) } +pub(crate) fn query_reverse_mixnode_delegations_paged( + deps: Deps, + delegation_owner: Addr, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit + .unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT) + .min(DELEGATION_PAGE_MAX_LIMIT) as usize; + let start = calculate_start_value(start_after); + + let delegations = reverse_mix_delegations_read(deps.storage, &delegation_owner) + .range(start.as_deref(), None, Order::Ascending) + .take(limit) + .map(|res| { + res.map(|entry| { + String::from_utf8(entry.0) + .expect("Non-UTF8 address used as key in bucket. The storage is corrupted!") + }) + }) + .collect::>>()?; + + let start_next_after = delegations.last().cloned(); + + Ok(PagedReverseMixDelegationsResponse::new( + delegation_owner, + delegations, + start_next_after, + )) +} + // queries for delegation value of given address for particular node pub(crate) fn query_mixnode_delegation( deps: Deps, @@ -148,7 +184,8 @@ pub(crate) fn query_mixnode_delegation( match mix_delegations_read(deps.storage, &mix_identity).may_load(address.as_bytes())? { Some(delegation_value) => Ok(Delegation::new( address, - coin(delegation_value.u128(), DENOM), + coin(delegation_value.amount.u128(), DENOM), + delegation_value.block_height, )), None => Err(ContractError::NoMixnodeDelegationFound { identity: mix_identity, @@ -174,8 +211,11 @@ pub(crate) fn query_gateway_delegations_paged( .map(|res| { res.map(|entry| { Delegation::new( - Addr::unchecked(String::from_utf8(entry.0).unwrap()), - coin(entry.1.u128(), DENOM), + Addr::unchecked(String::from_utf8(entry.0).expect( + "Non-UTF8 address used as key in bucket. The storage is corrupted!", + )), + coin(entry.1.amount.u128(), DENOM), + entry.1.block_height, ) }) }) @@ -190,6 +230,37 @@ pub(crate) fn query_gateway_delegations_paged( )) } +pub(crate) fn query_reverse_gateway_delegations_paged( + deps: Deps, + delegation_owner: Addr, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit + .unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT) + .min(DELEGATION_PAGE_MAX_LIMIT) as usize; + let start = calculate_start_value(start_after); + + let delegations = reverse_gateway_delegations_read(deps.storage, &delegation_owner) + .range(start.as_deref(), None, Order::Ascending) + .take(limit) + .map(|res| { + res.map(|entry| { + String::from_utf8(entry.0) + .expect("Non-UTF8 address used as key in bucket. The storage is corrupted!") + }) + }) + .collect::>>()?; + + let start_next_after = delegations.last().cloned(); + + Ok(PagedReverseGatewayDelegationsResponse::new( + delegation_owner, + delegations, + start_next_after, + )) +} + // queries for delegation value of given address for particular node pub(crate) fn query_gateway_delegation( deps: Deps, @@ -199,7 +270,8 @@ pub(crate) fn query_gateway_delegation( match gateway_delegations_read(deps.storage, &gateway_identity).may_load(address.as_bytes())? { Some(delegation_value) => Ok(Delegation::new( address, - coin(delegation_value.u128(), DENOM), + coin(delegation_value.amount.u128(), DENOM), + delegation_value.block_height, )), None => Err(ContractError::NoGatewayDelegationFound { identity: gateway_identity, @@ -214,11 +286,13 @@ mod tests { use crate::state::State; use crate::storage::{config, gateway_delegations, gateways, mix_delegations, mixnodes}; use crate::support::tests::helpers; - use crate::support::tests::helpers::{good_gateway_bond, good_mixnode_bond}; + use crate::support::tests::helpers::{ + good_gateway_bond, good_mixnode_bond, raw_delegation_fixture, + }; use crate::transactions; - use cosmwasm_std::testing::mock_info; - use cosmwasm_std::{Addr, Storage, Uint128}; - use mixnet_contract::{Gateway, MixNode}; + use cosmwasm_std::testing::{mock_env, mock_info}; + use cosmwasm_std::{Addr, Storage}; + use mixnet_contract::{Gateway, MixNode, RawDelegationData}; #[test] fn mixnodes_empty_on_init() { @@ -472,8 +546,13 @@ mod tests { identity_key: "bobsnode".into(), ..helpers::mix_node_fixture() }; - transactions::try_add_mixnode(deps.as_mut(), mock_info("bob", &good_mixnode_bond()), node) - .unwrap(); + transactions::try_add_mixnode( + deps.as_mut(), + mock_env(), + mock_info("bob", &good_mixnode_bond()), + node, + ) + .unwrap(); let res = query_owns_mixnode(deps.as_ref(), Addr::unchecked("fred")).unwrap(); assert!(!res.has_node); @@ -483,8 +562,13 @@ mod tests { identity_key: "fredsnode".into(), ..helpers::mix_node_fixture() }; - transactions::try_add_mixnode(deps.as_mut(), mock_info("fred", &good_mixnode_bond()), node) - .unwrap(); + transactions::try_add_mixnode( + deps.as_mut(), + mock_env(), + mock_info("fred", &good_mixnode_bond()), + node, + ) + .unwrap(); let res = query_owns_mixnode(deps.as_ref(), Addr::unchecked("fred")).unwrap(); assert!(res.has_node); @@ -509,8 +593,13 @@ mod tests { identity_key: "bobsnode".into(), ..helpers::gateway_fixture() }; - transactions::try_add_gateway(deps.as_mut(), mock_info("bob", &good_gateway_bond()), node) - .unwrap(); + transactions::try_add_gateway( + deps.as_mut(), + mock_env(), + mock_info("bob", &good_gateway_bond()), + node, + ) + .unwrap(); let res = query_owns_gateway(deps.as_ref(), Addr::unchecked("fred")).unwrap(); assert!(!res.has_gateway); @@ -520,8 +609,13 @@ mod tests { identity_key: "fredsnode".into(), ..helpers::gateway_fixture() }; - transactions::try_add_gateway(deps.as_mut(), mock_info("fred", &good_gateway_bond()), node) - .unwrap(); + transactions::try_add_gateway( + deps.as_mut(), + mock_env(), + mock_info("fred", &good_gateway_bond()), + node, + ) + .unwrap(); let res = query_owns_gateway(deps.as_ref(), Addr::unchecked("fred")).unwrap(); assert!(res.has_gateway); @@ -564,14 +658,12 @@ mod tests { #[cfg(test)] mod querying_for_mixnode_delegations_paged { use super::*; - use crate::storage::mix_delegations; - use cosmwasm_std::Uint128; fn store_n_delegations(n: u32, storage: &mut dyn Storage, node_identity: &IdentityKey) { for i in 0..n { let address = format!("address{}", i); mix_delegations(storage, node_identity) - .save(address.as_bytes(), &Uint128(42)) + .save(address.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); } } @@ -643,7 +735,7 @@ mod tests { let node_identity: IdentityKey = "foo".into(); mix_delegations(&mut deps.storage, &node_identity) - .save("1".as_bytes(), &Uint128(42)) + .save("1".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); let per_page = 2; @@ -660,7 +752,7 @@ mod tests { // save another mix_delegations(&mut deps.storage, &node_identity) - .save("2".as_bytes(), &Uint128(42)) + .save("2".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); // page1 should have 2 results on it @@ -674,7 +766,7 @@ mod tests { assert_eq!(2, page1.delegations.len()); mix_delegations(&mut deps.storage, &node_identity) - .save("3".as_bytes(), &Uint128(42)) + .save("3".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); // page1 still has 2 results @@ -701,7 +793,7 @@ mod tests { // save another one mix_delegations(&mut deps.storage, &node_identity) - .save("4".as_bytes(), &Uint128(42)) + .save("4".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); let start_after = Addr::unchecked("2"); @@ -725,11 +817,18 @@ mod tests { let delegation_owner = Addr::unchecked("bar"); mix_delegations(&mut deps.storage, &node_identity) - .save(delegation_owner.as_bytes(), &Uint128(42)) + .save( + delegation_owner.as_bytes(), + &RawDelegationData::new(42u128.into(), 12_345), + ) .unwrap(); assert_eq!( - Ok(Delegation::new(delegation_owner.clone(), coin(42, DENOM))), + Ok(Delegation::new( + delegation_owner.clone(), + coin(42, DENOM), + 12_345 + )), query_mixnode_delegation(deps.as_ref(), node_identity, delegation_owner) ) } @@ -757,7 +856,7 @@ mod tests { // add delegation from a different address mix_delegations(&mut deps.storage, &node_identity1) - .save(delegation_owner2.as_bytes(), &Uint128(42)) + .save(delegation_owner2.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); assert_eq!( @@ -774,7 +873,7 @@ mod tests { // add delegation for a different node mix_delegations(&mut deps.storage, &node_identity2) - .save(delegation_owner1.as_bytes(), &Uint128(42)) + .save(delegation_owner1.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); assert_eq!( @@ -786,17 +885,177 @@ mod tests { ) } + #[cfg(test)] + mod querying_for_reverse_mixnode_delegations_paged { + use super::*; + use crate::storage::reverse_mix_delegations; + + fn store_n_reverse_delegations(n: u32, storage: &mut dyn Storage, delegation_owner: &Addr) { + for i in 0..n { + let node_identity = format!("node{}", i); + reverse_mix_delegations(storage, delegation_owner) + .save(node_identity.as_bytes(), &()) + .unwrap(); + } + } + + #[test] + fn retrieval_obeys_limits() { + let mut deps = helpers::init_contract(); + let limit = 2; + let delegation_owner = Addr::unchecked("foo"); + store_n_reverse_delegations(100, &mut deps.storage, &delegation_owner); + + let page1 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner, + None, + Option::from(limit), + ) + .unwrap(); + assert_eq!(limit, page1.delegated_nodes.len() as u32); + } + + #[test] + fn retrieval_has_default_limit() { + let mut deps = helpers::init_contract(); + let delegation_owner = Addr::unchecked("foo"); + store_n_reverse_delegations( + DELEGATION_PAGE_DEFAULT_LIMIT * 10, + &mut deps.storage, + &delegation_owner, + ); + + // query without explicitly setting a limit + let page1 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner, + None, + None, + ) + .unwrap(); + assert_eq!( + DELEGATION_PAGE_DEFAULT_LIMIT, + page1.delegated_nodes.len() as u32 + ); + } + + #[test] + fn retrieval_has_max_limit() { + let mut deps = helpers::init_contract(); + let delegation_owner = Addr::unchecked("foo"); + store_n_reverse_delegations( + DELEGATION_PAGE_DEFAULT_LIMIT * 10, + &mut deps.storage, + &delegation_owner, + ); + + // query with a crazy high limit in an attempt to use too many resources + let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT; + let page1 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner, + None, + Option::from(crazy_limit), + ) + .unwrap(); + + // we default to a decent sized upper bound instead + let expected_limit = DELEGATION_PAGE_MAX_LIMIT; + assert_eq!(expected_limit, page1.delegated_nodes.len() as u32); + } + + #[test] + fn pagination_works() { + let mut deps = helpers::init_contract(); + let delegation_owner = Addr::unchecked("bar"); + + reverse_mix_delegations(&mut deps.storage, &delegation_owner) + .save("1".as_bytes(), &()) + .unwrap(); + + let per_page = 2; + let page1 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + None, + Option::from(per_page), + ) + .unwrap(); + + // page should have 1 result on it + assert_eq!(1, page1.delegated_nodes.len()); + + // save another + reverse_mix_delegations(&mut deps.storage, &delegation_owner) + .save("2".as_bytes(), &()) + .unwrap(); + + // page1 should have 2 results on it + let page1 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + None, + Option::from(per_page), + ) + .unwrap(); + assert_eq!(2, page1.delegated_nodes.len()); + + reverse_mix_delegations(&mut deps.storage, &delegation_owner) + .save("3".as_bytes(), &()) + .unwrap(); + + // page1 still has 2 results + let page1 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + None, + Option::from(per_page), + ) + .unwrap(); + assert_eq!(2, page1.delegated_nodes.len()); + + // retrieving the next page should start after the last key on this page + let start_after: IdentityKey = String::from("2"); + let page2 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + Option::from(start_after), + Option::from(per_page), + ) + .unwrap(); + + assert_eq!(1, page2.delegated_nodes.len()); + + // save another one + reverse_mix_delegations(&mut deps.storage, &delegation_owner) + .save("4".as_bytes(), &()) + .unwrap(); + + let start_after = String::from("2"); + let page2 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner, + Option::from(start_after), + Option::from(per_page), + ) + .unwrap(); + + // now we have 2 pages, with 2 results on the second page + assert_eq!(2, page2.delegated_nodes.len()); + } + } + #[cfg(test)] mod querying_for_gateway_delegations_paged { use super::*; use crate::storage::gateway_delegations; - use cosmwasm_std::Uint128; fn store_n_delegations(n: u32, storage: &mut dyn Storage, node_identity: &IdentityKey) { for i in 0..n { let address = format!("address{}", i); gateway_delegations(storage, node_identity) - .save(address.as_bytes(), &Uint128(42)) + .save(address.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); } } @@ -868,7 +1127,7 @@ mod tests { let node_identity: IdentityKey = "foo".into(); gateway_delegations(&mut deps.storage, &node_identity) - .save("1".as_bytes(), &Uint128(42)) + .save("1".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); let per_page = 2; @@ -885,7 +1144,7 @@ mod tests { // save another gateway_delegations(&mut deps.storage, &node_identity) - .save("2".as_bytes(), &Uint128(42)) + .save("2".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); // page1 should have 2 results on it @@ -899,7 +1158,7 @@ mod tests { assert_eq!(2, page1.delegations.len()); gateway_delegations(&mut deps.storage, &node_identity) - .save("3".as_bytes(), &Uint128(42)) + .save("3".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); // page1 still has 2 results @@ -926,7 +1185,7 @@ mod tests { // save another one gateway_delegations(&mut deps.storage, &node_identity) - .save("4".as_bytes(), &Uint128(42)) + .save("4".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); let start_after = Addr::unchecked("2"); @@ -950,11 +1209,18 @@ mod tests { let delegation_owner = Addr::unchecked("bar"); gateway_delegations(&mut deps.storage, &node_identity) - .save(delegation_owner.as_bytes(), &Uint128(42)) + .save( + delegation_owner.as_bytes(), + &&RawDelegationData::new(42u128.into(), 12_345), + ) .unwrap(); assert_eq!( - Ok(Delegation::new(delegation_owner.clone(), coin(42, DENOM))), + Ok(Delegation::new( + delegation_owner.clone(), + coin(42, DENOM), + 12_345 + )), query_gateway_delegation(deps.as_ref(), node_identity, delegation_owner) ) } @@ -982,7 +1248,7 @@ mod tests { // add delegation from a different address gateway_delegations(&mut deps.storage, &node_identity1) - .save(delegation_owner2.as_bytes(), &Uint128(42)) + .save(delegation_owner2.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); assert_eq!( @@ -999,7 +1265,7 @@ mod tests { // add delegation for a different node gateway_delegations(&mut deps.storage, &node_identity2) - .save(delegation_owner1.as_bytes(), &Uint128(42)) + .save(delegation_owner1.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); assert_eq!( @@ -1010,4 +1276,165 @@ mod tests { query_gateway_delegation(deps.as_ref(), node_identity1, delegation_owner1) ) } + + #[cfg(test)] + mod querying_for_reverse_gateway_delegations_paged { + use super::*; + use crate::storage::reverse_gateway_delegations; + + fn store_n_reverse_delegations(n: u32, storage: &mut dyn Storage, delegation_owner: &Addr) { + for i in 0..n { + let node_identity = format!("node{}", i); + reverse_gateway_delegations(storage, delegation_owner) + .save(node_identity.as_bytes(), &()) + .unwrap(); + } + } + + #[test] + fn retrieval_obeys_limits() { + let mut deps = helpers::init_contract(); + let limit = 2; + let delegation_owner = Addr::unchecked("foo"); + store_n_reverse_delegations(100, &mut deps.storage, &delegation_owner); + + let page1 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner, + None, + Option::from(limit), + ) + .unwrap(); + assert_eq!(limit, page1.delegated_nodes.len() as u32); + } + + #[test] + fn retrieval_has_default_limit() { + let mut deps = helpers::init_contract(); + let delegation_owner = Addr::unchecked("foo"); + store_n_reverse_delegations( + DELEGATION_PAGE_DEFAULT_LIMIT * 10, + &mut deps.storage, + &delegation_owner, + ); + + // query without explicitly setting a limit + let page1 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner, + None, + None, + ) + .unwrap(); + assert_eq!( + DELEGATION_PAGE_DEFAULT_LIMIT, + page1.delegated_nodes.len() as u32 + ); + } + + #[test] + fn retrieval_has_max_limit() { + let mut deps = helpers::init_contract(); + let delegation_owner = Addr::unchecked("foo"); + store_n_reverse_delegations( + DELEGATION_PAGE_DEFAULT_LIMIT * 10, + &mut deps.storage, + &delegation_owner, + ); + + // query with a crazy high limit in an attempt to use too many resources + let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT; + let page1 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner, + None, + Option::from(crazy_limit), + ) + .unwrap(); + + // we default to a decent sized upper bound instead + let expected_limit = DELEGATION_PAGE_MAX_LIMIT; + assert_eq!(expected_limit, page1.delegated_nodes.len() as u32); + } + + #[test] + fn pagination_works() { + let mut deps = helpers::init_contract(); + let delegation_owner = Addr::unchecked("bar"); + + reverse_gateway_delegations(&mut deps.storage, &delegation_owner) + .save("1".as_bytes(), &()) + .unwrap(); + + let per_page = 2; + let page1 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + None, + Option::from(per_page), + ) + .unwrap(); + + // page should have 1 result on it + assert_eq!(1, page1.delegated_nodes.len()); + + // save another + reverse_gateway_delegations(&mut deps.storage, &delegation_owner) + .save("2".as_bytes(), &()) + .unwrap(); + + // page1 should have 2 results on it + let page1 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + None, + Option::from(per_page), + ) + .unwrap(); + assert_eq!(2, page1.delegated_nodes.len()); + + reverse_gateway_delegations(&mut deps.storage, &delegation_owner) + .save("3".as_bytes(), &()) + .unwrap(); + + // page1 still has 2 results + let page1 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + None, + Option::from(per_page), + ) + .unwrap(); + assert_eq!(2, page1.delegated_nodes.len()); + + // retrieving the next page should start after the last key on this page + let start_after: IdentityKey = String::from("2"); + let page2 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + Option::from(start_after), + Option::from(per_page), + ) + .unwrap(); + + assert_eq!(1, page2.delegated_nodes.len()); + + // save another one + reverse_gateway_delegations(&mut deps.storage, &delegation_owner) + .save("4".as_bytes(), &()) + .unwrap(); + + let start_after = String::from("2"); + let page2 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner, + Option::from(start_after), + Option::from(per_page), + ) + .unwrap(); + + // now we have 2 pages, with 2 results on the second page + assert_eq!(2, page2.delegated_nodes.len()); + } + } } diff --git a/contracts/mixnet/src/state.rs b/contracts/mixnet/src/state.rs index d4f0034ff6..c537366f07 100644 --- a/contracts/mixnet/src/state.rs +++ b/contracts/mixnet/src/state.rs @@ -6,8 +6,6 @@ use mixnet_contract::StateParams; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use mixnet_contract::default_delegation_reward; - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct State { pub owner: Addr, // only the owner account can update state @@ -17,8 +15,6 @@ pub struct State { // helper values to avoid having to recalculate them on every single payment operation pub mixnode_epoch_bond_reward: Decimal, // reward per epoch expressed as a decimal like 0.05 pub gateway_epoch_bond_reward: Decimal, // reward per epoch expressed as a decimal like 0.05 - #[serde(default = "default_delegation_reward")] pub mixnode_epoch_delegation_reward: Decimal, // reward per epoch expressed as a decimal like 0.05 - #[serde(default = "default_delegation_reward")] pub gateway_epoch_delegation_reward: Decimal, // reward per epoch expressed as a decimal like 0.05 } diff --git a/contracts/mixnet/src/storage.rs b/contracts/mixnet/src/storage.rs index 76eb357f86..43f1398d89 100644 --- a/contracts/mixnet/src/storage.rs +++ b/contracts/mixnet/src/storage.rs @@ -3,13 +3,15 @@ use crate::queries; use crate::state::State; +use crate::transactions::MINIMUM_BLOCK_AGE_FOR_REWARDING; use cosmwasm_std::{Decimal, Order, StdResult, Storage, Uint128}; use cosmwasm_storage::{ bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, Singleton, }; use mixnet_contract::{ - GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNodeBond, StateParams, + Addr, GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNodeBond, + RawDelegationData, StateParams, }; // storage prefixes @@ -29,9 +31,13 @@ const PREFIX_GATEWAYS_OWNERS: &[u8] = b"go"; const PREFIX_MIX_DELEGATION: &[u8] = b"md"; const PREFIX_GATEWAY_DELEGATION: &[u8] = b"gd"; +const PREFIX_REVERSE_MIX_DELEGATION: &[u8] = b"dm"; +const PREFIX_REVERSE_GATEWAY_DELEGATION: &[u8] = b"dg"; // Contract-level stuff +// TODO Unify bucket and mixnode storage functions + pub fn config(storage: &mut dyn Storage) -> Singleton { singleton(storage, CONFIG_KEY) } @@ -161,6 +167,7 @@ pub(crate) fn increase_mix_delegated_stakes( storage: &mut dyn Storage, mix_identity: IdentityKeyRef, scaled_reward_rate: Decimal, + reward_blockstamp: u64, ) -> StdResult { let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize; @@ -190,11 +197,15 @@ pub(crate) fn increase_mix_delegated_stakes( ); // and for each of them increase the stake proportionally to the reward - for (delegator_address, amount) in delegations_chunk.into_iter() { - let reward = amount * scaled_reward_rate; - let new_amount = amount + reward; - total_rewarded += reward; - mix_delegations(storage, mix_identity).save(&delegator_address, &new_amount)?; + // if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created + // since they delegated + for (delegator_address, mut delegation) in delegations_chunk.into_iter() { + if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= reward_blockstamp { + let reward = delegation.amount * scaled_reward_rate; + delegation.amount += reward; + total_rewarded += reward; + mix_delegations(storage, mix_identity).save(&delegator_address, &delegation)?; + } } } @@ -205,6 +216,7 @@ pub(crate) fn increase_gateway_delegated_stakes( storage: &mut dyn Storage, gateway_identity: IdentityKeyRef, scaled_reward_rate: Decimal, + reward_blockstamp: u64, ) -> StdResult { let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize; @@ -234,11 +246,16 @@ pub(crate) fn increase_gateway_delegated_stakes( ); // and for each of them increase the stake proportionally to the reward - for (delegator_address, amount) in delegations_chunk.into_iter() { - let reward = amount * scaled_reward_rate; - let new_amount = amount + reward; - total_rewarded += reward; - gateway_delegations(storage, gateway_identity).save(&delegator_address, &new_amount)?; + // if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created + // since they delegated + for (delegator_address, mut delegation) in delegations_chunk.into_iter() { + if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= reward_blockstamp { + let reward = delegation.amount * scaled_reward_rate; + delegation.amount += reward; + total_rewarded += reward; + gateway_delegations(storage, gateway_identity) + .save(&delegator_address, &delegation)?; + } } } @@ -290,21 +307,40 @@ pub fn gateways_owners_read(storage: &dyn Storage) -> ReadonlyBucket( storage: &'a mut dyn Storage, mix_identity: IdentityKeyRef, -) -> Bucket<'a, Uint128> { +) -> Bucket<'a, RawDelegationData> { Bucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) } pub fn mix_delegations_read<'a>( storage: &'a dyn Storage, mix_identity: IdentityKeyRef, +) -> ReadonlyBucket<'a, RawDelegationData> { + ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) +} + +// https://github.com/nymtech/nym/blob/122f5d9f2e5c1ced96e3b9ba0c74ef8b7dbde2c7/contracts/mixnet/src/storage.rs +pub fn mix_delegations_read_old<'a>( + storage: &'a dyn Storage, + mix_identity: IdentityKeyRef, ) -> ReadonlyBucket<'a, Uint128> { ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) } +pub fn reverse_mix_delegations<'a>(storage: &'a mut dyn Storage, owner: &Addr) -> Bucket<'a, ()> { + Bucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()]) +} + +pub fn reverse_mix_delegations_read<'a>( + storage: &'a dyn Storage, + owner: &Addr, +) -> ReadonlyBucket<'a, ()> { + ReadonlyBucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()]) +} + pub fn gateway_delegations<'a>( storage: &'a mut dyn Storage, gateway_identity: IdentityKeyRef, -) -> Bucket<'a, Uint128> { +) -> Bucket<'a, RawDelegationData> { Bucket::multilevel( storage, &[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()], @@ -314,6 +350,16 @@ pub fn gateway_delegations<'a>( pub fn gateway_delegations_read<'a>( storage: &'a dyn Storage, gateway_identity: IdentityKeyRef, +) -> ReadonlyBucket<'a, RawDelegationData> { + ReadonlyBucket::multilevel( + storage, + &[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()], + ) +} + +pub fn gateway_delegations_read_old<'a>( + storage: &'a dyn Storage, + gateway_identity: IdentityKeyRef, ) -> ReadonlyBucket<'a, Uint128> { ReadonlyBucket::multilevel( storage, @@ -321,6 +367,26 @@ pub fn gateway_delegations_read<'a>( ) } +pub fn reverse_gateway_delegations<'a>( + storage: &'a mut dyn Storage, + owner: &Addr, +) -> Bucket<'a, ()> { + Bucket::multilevel( + storage, + &[PREFIX_REVERSE_GATEWAY_DELEGATION, owner.as_bytes()], + ) +} + +pub fn reverse_gateway_delegations_read<'a>( + storage: &'a dyn Storage, + owner: &Addr, +) -> ReadonlyBucket<'a, ()> { + ReadonlyBucket::multilevel( + storage, + &[PREFIX_REVERSE_GATEWAY_DELEGATION, owner.as_bytes()], + ) +} + // currently not used outside tests #[cfg(test)] pub(crate) fn read_gateway_bond( @@ -348,6 +414,7 @@ mod tests { use super::*; use crate::support::tests::helpers::{ gateway_bond_fixture, gateway_fixture, mix_node_fixture, mixnode_bond_fixture, + raw_delegation_fixture, }; use config::defaults::DENOM; use cosmwasm_std::testing::MockStorage; @@ -400,6 +467,7 @@ mod tests { total_delegation: coin(0, DENOM), owner: node_owner.clone(), layer: Layer::One, + block_height: 12_345, mix_node: MixNode { identity_key: node_identity.clone(), ..mix_node_fixture() @@ -433,6 +501,7 @@ mod tests { bond_amount: coin(bond_value, DENOM), total_delegation: coin(0, DENOM), owner: node_owner.clone(), + block_height: 12_345, gateway: Gateway { identity_key: node_identity.clone(), ..gateway_fixture() @@ -463,9 +532,13 @@ mod tests { // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); - let total_increase = - increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward) - .unwrap(); + let total_increase = increase_mix_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + 42, + ) + .unwrap(); // there was no increase assert!(total_increase.is_zero()); @@ -483,23 +556,88 @@ mod tests { fn when_there_is_a_single_delegation() { let mut deps = mock_dependencies(&[]); let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); let delegator_address = Addr::unchecked("bob"); mix_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &Uint128(1000)) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) .unwrap(); - let total_increase = - increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward) - .unwrap(); + let total_increase = increase_mix_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING, + ) + .unwrap(); assert_eq!(Uint128(1), total_increase); + // amount is incremented, block height remains the same assert_eq!( - Uint128(1001), + RawDelegationData::new(1001u128.into(), 42), + mix_delegations_read(&mut deps.storage, &node_identity) + .load(delegator_address.as_bytes()) + .unwrap() + ) + } + + #[test] + fn when_there_is_a_single_delegation_depending_on_blockstamp() { + let mut deps = mock_dependencies(&[]); + let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; + + // 0.001 + let reward = Decimal::from_ratio(1u128, 1000u128); + + let delegator_address = Addr::unchecked("bob"); + mix_delegations(&mut deps.storage, &node_identity) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) + .unwrap(); + + let total_increase = increase_mix_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING - 1, + ) + .unwrap(); + + // there was no increase + assert!(total_increase.is_zero()); + + // amount is not incremented + assert_eq!( + RawDelegationData::new(1000u128.into(), delegation_blockstamp), + mix_delegations_read(&mut deps.storage, &node_identity) + .load(delegator_address.as_bytes()) + .unwrap() + ); + + let total_increase = increase_mix_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING, + ) + .unwrap(); + + // there is an increase now, that the lock period has passed + assert_eq!(Uint128(1), total_increase); + + // amount is incremented + assert_eq!( + RawDelegationData::new(1001u128.into(), delegation_blockstamp), mix_delegations_read(&mut deps.storage, &node_identity) .load(delegator_address.as_bytes()) .unwrap() @@ -510,6 +648,7 @@ mod tests { fn when_there_are_multiple_delegations() { let mut deps = mock_dependencies(&[]); let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); @@ -517,20 +656,27 @@ mod tests { for i in 0..100 { let delegator_address = Addr::unchecked(format!("address{}", i)); mix_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &Uint128(1000)) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) .unwrap(); } - let total_increase = - increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward) - .unwrap(); + let total_increase = increase_mix_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING, + ) + .unwrap(); assert_eq!(Uint128(100), total_increase); for i in 0..100 { let delegator_address = Addr::unchecked(format!("address{}", i)); assert_eq!( - Uint128(1001), + raw_delegation_fixture(1001), mix_delegations_read(&mut deps.storage, &node_identity) .load(delegator_address.as_bytes()) .unwrap() @@ -542,6 +688,7 @@ mod tests { fn when_there_are_more_delegations_than_page_size() { let mut deps = mock_dependencies(&[]); let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); @@ -549,13 +696,20 @@ mod tests { for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 { let delegator_address = Addr::unchecked(format!("address{}", i)); mix_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &Uint128(1000)) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) .unwrap(); } - let total_increase = - increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward) - .unwrap(); + let total_increase = increase_mix_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING, + ) + .unwrap(); assert_eq!( Uint128(queries::DELEGATION_PAGE_MAX_LIMIT as u128 * 10), @@ -565,7 +719,7 @@ mod tests { for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 { let delegator_address = Addr::unchecked(format!("address{}", i)); assert_eq!( - Uint128(1001), + raw_delegation_fixture(1001), mix_delegations_read(&mut deps.storage, &node_identity) .load(delegator_address.as_bytes()) .unwrap() @@ -574,6 +728,71 @@ mod tests { } } + #[cfg(test)] + mod reverse_mix_delegations { + use super::*; + use crate::support::tests::helpers; + + #[test] + fn reverse_mix_delegation_exists() { + let mut deps = helpers::init_contract(); + let node_identity: IdentityKey = "foo".into(); + let delegation_owner = Addr::unchecked("bar"); + + reverse_mix_delegations(&mut deps.storage, &delegation_owner) + .save(node_identity.as_bytes(), &()) + .unwrap(); + + assert!( + reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner) + .may_load(node_identity.as_bytes()) + .unwrap() + .is_some(), + ); + } + + #[test] + fn reverse_mix_delegation_returns_none_if_delegation_doesnt_exist() { + let mut deps = helpers::init_contract(); + + let node_identity1: IdentityKey = "foo1".into(); + let node_identity2: IdentityKey = "foo2".into(); + let delegation_owner1 = Addr::unchecked("bar"); + let delegation_owner2 = Addr::unchecked("bar2"); + + assert!( + reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1) + .may_load(node_identity1.as_bytes()) + .unwrap() + .is_none() + ); + + // add delegation for a different node + reverse_mix_delegations(&mut deps.storage, &delegation_owner1) + .save(node_identity2.as_bytes(), &()) + .unwrap(); + + assert!( + reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1) + .may_load(node_identity1.as_bytes()) + .unwrap() + .is_none() + ); + + // add delegation from a different owner + reverse_mix_delegations(&mut deps.storage, &delegation_owner2) + .save(node_identity1.as_bytes(), &()) + .unwrap(); + + assert!( + reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1) + .may_load(node_identity1.as_bytes()) + .unwrap() + .is_none() + ); + } + } + #[cfg(test)] mod increasing_gateway_delegated_stakes { use super::*; @@ -592,6 +811,7 @@ mod tests { &mut deps.storage, node_identity.as_ref(), reward, + 42, ) .unwrap(); @@ -611,26 +831,88 @@ mod tests { fn when_there_is_a_single_delegation() { let mut deps = mock_dependencies(&[]); let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); let delegator_address = Addr::unchecked("bob"); gateway_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &Uint128(1000)) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) .unwrap(); let total_increase = increase_gateway_delegated_stakes( &mut deps.storage, node_identity.as_ref(), reward, + delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING, ) .unwrap(); assert_eq!(Uint128(1), total_increase); + // amount is incremented, block height remains the same assert_eq!( - Uint128(1001), + RawDelegationData::new(1001u128.into(), 42), + gateway_delegations_read(&mut deps.storage, &node_identity) + .load(delegator_address.as_bytes()) + .unwrap() + ) + } + + #[test] + fn when_there_is_a_single_delegation_depending_on_blockstamp() { + let mut deps = mock_dependencies(&[]); + let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; + + // 0.001 + let reward = Decimal::from_ratio(1u128, 1000u128); + + let delegator_address = Addr::unchecked("bob"); + gateway_delegations(&mut deps.storage, &node_identity) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) + .unwrap(); + + let total_increase = increase_gateway_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING - 1, + ) + .unwrap(); + + // there was no increase + assert!(total_increase.is_zero()); + + // amount is not incremented + assert_eq!( + RawDelegationData::new(1000u128.into(), delegation_blockstamp), + gateway_delegations_read(&mut deps.storage, &node_identity) + .load(delegator_address.as_bytes()) + .unwrap() + ); + + let total_increase = increase_gateway_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING, + ) + .unwrap(); + + // there is an increase now, that the lock period has passed + assert_eq!(Uint128(1), total_increase); + + // amount is incremented + assert_eq!( + RawDelegationData::new(1001u128.into(), delegation_blockstamp), gateway_delegations_read(&mut deps.storage, &node_identity) .load(delegator_address.as_bytes()) .unwrap() @@ -641,6 +923,7 @@ mod tests { fn when_there_are_multiple_delegations() { let mut deps = mock_dependencies(&[]); let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); @@ -648,7 +931,10 @@ mod tests { for i in 0..100 { let delegator_address = Addr::unchecked(format!("address{}", i)); gateway_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &Uint128(1000)) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) .unwrap(); } @@ -656,6 +942,7 @@ mod tests { &mut deps.storage, node_identity.as_ref(), reward, + delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING, ) .unwrap(); @@ -664,7 +951,7 @@ mod tests { for i in 0..100 { let delegator_address = Addr::unchecked(format!("address{}", i)); assert_eq!( - Uint128(1001), + raw_delegation_fixture(1001), gateway_delegations_read(&mut deps.storage, &node_identity) .load(delegator_address.as_bytes()) .unwrap() @@ -676,6 +963,7 @@ mod tests { fn when_there_are_more_delegations_than_page_size() { let mut deps = mock_dependencies(&[]); let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); @@ -683,7 +971,10 @@ mod tests { for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 { let delegator_address = Addr::unchecked(format!("address{}", i)); gateway_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &Uint128(1000)) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) .unwrap(); } @@ -691,6 +982,7 @@ mod tests { &mut deps.storage, node_identity.as_ref(), reward, + delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING, ) .unwrap(); @@ -702,7 +994,7 @@ mod tests { for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 { let delegator_address = Addr::unchecked(format!("address{}", i)); assert_eq!( - Uint128(1001), + raw_delegation_fixture(1001), gateway_delegations_read(&mut deps.storage, &node_identity) .load(delegator_address.as_bytes()) .unwrap() @@ -710,4 +1002,69 @@ mod tests { } } } + + #[cfg(test)] + mod reverse_gateway_delegations { + use super::*; + use crate::support::tests::helpers; + + #[test] + fn reverse_gateway_delegation_exists() { + let mut deps = helpers::init_contract(); + let node_identity: IdentityKey = "foo".into(); + let delegation_owner = Addr::unchecked("bar"); + + reverse_gateway_delegations(&mut deps.storage, &delegation_owner) + .save(node_identity.as_bytes(), &()) + .unwrap(); + + assert!( + reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner) + .may_load(node_identity.as_bytes()) + .unwrap() + .is_some(), + ); + } + + #[test] + fn reverse_gateway_delegation_returns_none_if_delegation_doesnt_exist() { + let mut deps = helpers::init_contract(); + + let node_identity1: IdentityKey = "foo1".into(); + let node_identity2: IdentityKey = "foo2".into(); + let delegation_owner1 = Addr::unchecked("bar"); + let delegation_owner2 = Addr::unchecked("bar2"); + + assert!( + reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1) + .may_load(node_identity1.as_bytes()) + .unwrap() + .is_none() + ); + + // add delegation for a different node + reverse_gateway_delegations(&mut deps.storage, &delegation_owner1) + .save(node_identity2.as_bytes(), &()) + .unwrap(); + + assert!( + reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1) + .may_load(node_identity1.as_bytes()) + .unwrap() + .is_none() + ); + + // add delegation from a different owner + reverse_gateway_delegations(&mut deps.storage, &delegation_owner2) + .save(node_identity1.as_bytes(), &()) + .unwrap(); + + assert!( + reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1) + .may_load(node_identity1.as_bytes()) + .unwrap() + .is_none() + ); + } + } } diff --git a/contracts/mixnet/src/support/tests.rs b/contracts/mixnet/src/support/tests.rs index 72993e6721..70b761e0fd 100644 --- a/contracts/mixnet/src/support/tests.rs +++ b/contracts/mixnet/src/support/tests.rs @@ -5,7 +5,6 @@ pub mod helpers { use crate::contract::{instantiate, INITIAL_MIXNODE_BOND}; use crate::transactions::{try_add_gateway, try_add_mixnode}; use config::defaults::DENOM; - use cosmwasm_std::coin; use cosmwasm_std::from_binary; use cosmwasm_std::testing::mock_dependencies; use cosmwasm_std::testing::mock_env; @@ -16,10 +15,11 @@ pub mod helpers { use cosmwasm_std::Addr; use cosmwasm_std::Coin; use cosmwasm_std::OwnedDeps; + use cosmwasm_std::{coin, Uint128}; use cosmwasm_std::{Empty, MemoryStorage}; use mixnet_contract::{ Gateway, GatewayBond, InstantiateMsg, Layer, MixNode, MixNodeBond, PagedGatewayResponse, - PagedMixnodeResponse, QueryMsg, + PagedMixnodeResponse, QueryMsg, RawDelegationData, }; pub fn add_mixnode( @@ -31,6 +31,7 @@ pub mod helpers { let key = format!("{}mixnode", sender); try_add_mixnode( deps.as_mut(), + mock_env(), info, MixNode { identity_key: key.clone(), @@ -67,6 +68,7 @@ pub mod helpers { let key = format!("{}gateway", sender); try_add_gateway( deps.as_mut(), + mock_env(), info, Gateway { identity_key: key.clone(), @@ -129,6 +131,7 @@ pub mod helpers { coin(50, DENOM), Addr::unchecked("foo"), Layer::One, + 12_345, mix_node, ) } @@ -156,7 +159,11 @@ pub mod helpers { identity_key: "identity".to_string(), version: "0.10.0".to_string(), }; - GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), gateway) + GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), 12_345, gateway) + } + + pub fn raw_delegation_fixture(amount: u128) -> RawDelegationData { + RawDelegationData::new(Uint128(amount), 42) } pub fn query_contract_balance( diff --git a/contracts/mixnet/src/transactions.rs b/contracts/mixnet/src/transactions.rs index 70c30f014d..781269a3f9 100644 --- a/contracts/mixnet/src/transactions.rs +++ b/contracts/mixnet/src/transactions.rs @@ -7,14 +7,18 @@ use crate::queries; use crate::storage::*; use config::defaults::DENOM; use cosmwasm_std::{ - attr, coins, BankMsg, Coin, Decimal, DepsMut, MessageInfo, Order, Response, StdResult, Uint128, + attr, coins, BankMsg, Coin, Decimal, DepsMut, Env, MessageInfo, Order, Response, StdResult, + Uint128, }; use cosmwasm_storage::ReadonlyBucket; use mixnet_contract::{ - Gateway, GatewayBond, IdentityKey, Layer, MixNode, MixNodeBond, StateParams, + Gateway, GatewayBond, IdentityKey, Layer, MixNode, MixNodeBond, RawDelegationData, StateParams, }; +use serde::de::DeserializeOwned; +use serde::Serialize; const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500; +pub(crate) const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 17280; // Looks for the total amount of delegations towards a particular node. // This function is used only in very specific circumstances: @@ -23,12 +27,15 @@ const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500; // 3. The node unbonds // 4. Some of the addresses that delegated in the past have not removed the delegation yet // 5. The node rebonds with the same identity -fn find_old_delegations(delegations_bucket: ReadonlyBucket) -> StdResult { +pub fn delegations( + delegations_bucket: ReadonlyBucket, +) -> StdResult, T)>> { // I think it's incredibly unlikely to ever read more than that // but in case we do, we should guard ourselves against possible // out of memory errors (wasm contracts can only allocate at most 2MB // of RAM, so we don't want to box the entire iterator) - let mut total_delegation = Coin::new(0, DENOM); + let mut delegations = Vec::new(); + // let mut total_delegation = Coin::new(0, DENOM); let mut start = None; loop { let iterator = delegations_bucket @@ -37,16 +44,23 @@ fn find_old_delegations(delegations_bucket: ReadonlyBucket) -> StdResul let mut iterated = 0; - for delegation in iterator { + for result_tuple in iterator { iterated += 1; - if iterated == OLD_DELEGATIONS_CHUNK_SIZE + 1 { - // we reached start of next chunk, don't process it, mark it for the next iteration of the loop - start = Some(delegation?.0); - continue; + match result_tuple { + Ok((position, delegation)) => { + // We might skip some values due to deserializatio errors + if iterated > OLD_DELEGATIONS_CHUNK_SIZE { + // we reached start of next chunk, don't process it, mark it for the next iteration of the loop + start = Some(position); + continue; + } + delegations.push((position, delegation)) + } + Err(_e) => { + // Skip errors + continue; + } } - - let value = delegation?.1; - total_delegation.amount += value; } if iterated <= OLD_DELEGATIONS_CHUNK_SIZE { @@ -55,7 +69,17 @@ fn find_old_delegations(delegations_bucket: ReadonlyBucket) -> StdResul } } - Ok(total_delegation) + Ok(delegations) +} + +fn total_delegations(delegations_bucket: ReadonlyBucket) -> StdResult { + match delegations(delegations_bucket) { + Ok(delegations) => Ok(Coin::new( + delegations.iter().fold(0, |acc, x| acc + x.1.amount.u128()), + "upunk", + )), + Err(e) => Err(e), + } } fn validate_mixnode_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), ContractError> { @@ -86,6 +110,7 @@ fn validate_mixnode_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), Con pub(crate) fn try_add_mixnode( deps: DepsMut, + env: Env, info: MessageInfo, mix_node: MixNode, ) -> Result { @@ -125,11 +150,17 @@ pub(crate) fn try_add_mixnode( let layer_distribution = queries::query_layer_distribution(deps.as_ref()); let layer = layer_distribution.choose_with_fewest(); - let mut bond = MixNodeBond::new(info.funds[0].clone(), info.sender.clone(), layer, mix_node); + let mut bond = MixNodeBond::new( + info.funds[0].clone(), + info.sender.clone(), + layer, + env.block.height, + mix_node, + ); // this might potentially require more gas if a significant number of delegations was there let delegations_bucket = mix_delegations_read(deps.storage, &bond.mix_node.identity_key); - let existing_delegation = find_old_delegations(delegations_bucket)?; + let existing_delegation = total_delegations(delegations_bucket)?; bond.total_delegation = existing_delegation; let identity = bond.identity(); @@ -215,6 +246,7 @@ fn validate_gateway_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), Con pub(crate) fn try_add_gateway( deps: DepsMut, + env: Env, info: MessageInfo, gateway: Gateway, ) -> Result { @@ -251,11 +283,16 @@ pub(crate) fn try_add_gateway( let minimum_bond = read_state_params(deps.storage).minimum_gateway_bond; validate_gateway_bond(&info.funds, minimum_bond)?; - let mut bond = GatewayBond::new(info.funds[0].clone(), info.sender.clone(), gateway); + let mut bond = GatewayBond::new( + info.funds[0].clone(), + info.sender.clone(), + env.block.height, + gateway, + ); // this might potentially require more gas if a significant number of delegations was there let delegations_bucket = gateway_delegations_read(deps.storage, &bond.gateway.identity_key); - let existing_delegation = find_old_delegations(delegations_bucket)?; + let existing_delegation = total_delegations(delegations_bucket)?; bond.total_delegation = existing_delegation; let identity = bond.identity(); @@ -390,6 +427,7 @@ pub(crate) fn try_update_state_params( pub(crate) fn try_reward_mixnode( deps: DepsMut, + env: Env, info: MessageInfo, mix_identity: IdentityKey, uptime: u32, @@ -430,14 +468,22 @@ pub(crate) fn try_reward_mixnode( let bond_scaled_reward_rate = scale_reward_by_uptime(bond_reward_rate, uptime)?; let delegation_scaled_reward_rate = scale_reward_by_uptime(delegation_reward_rate, uptime)?; - let node_reward = current_bond.bond_amount.amount * bond_scaled_reward_rate; - let total_delegation_reward = - increase_mix_delegated_stakes(deps.storage, &mix_identity, delegation_scaled_reward_rate)?; + let mut node_reward = Uint128(0); + let total_delegation_reward = increase_mix_delegated_stakes( + deps.storage, + &mix_identity, + delegation_scaled_reward_rate, + env.block.height, + )?; // update current bond with the reward given to the node and the delegators - current_bond.bond_amount.amount += node_reward; - current_bond.total_delegation.amount += total_delegation_reward; - mixnodes(deps.storage).save(mix_identity.as_bytes(), ¤t_bond)?; + // if it has been bonded for long enough + if current_bond.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= env.block.height { + node_reward = current_bond.bond_amount.amount * bond_scaled_reward_rate; + current_bond.bond_amount.amount += node_reward; + current_bond.total_delegation.amount += total_delegation_reward; + mixnodes(deps.storage).save(mix_identity.as_bytes(), ¤t_bond)?; + } Ok(Response { submessages: vec![], @@ -452,6 +498,7 @@ pub(crate) fn try_reward_mixnode( pub(crate) fn try_reward_gateway( deps: DepsMut, + env: Env, info: MessageInfo, gateway_identity: IdentityKey, uptime: u32, @@ -492,17 +539,22 @@ pub(crate) fn try_reward_gateway( let scaled_bond_reward_rate = scale_reward_by_uptime(bond_reward_rate, uptime)?; let scaled_delegation_reward_rate = scale_reward_by_uptime(delegation_reward_rate, uptime)?; - let node_reward = current_bond.bond_amount.amount * scaled_bond_reward_rate; + let mut node_reward = Uint128(0); let total_delegation_reward = increase_gateway_delegated_stakes( deps.storage, &gateway_identity, scaled_delegation_reward_rate, + env.block.height, )?; // update current bond with the reward given to the node and the delegators - current_bond.bond_amount.amount += node_reward; - current_bond.total_delegation.amount += total_delegation_reward; - gateways(deps.storage).save(gateway_identity.as_bytes(), ¤t_bond)?; + // if it has been bonded for long enough + if current_bond.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= env.block.height { + node_reward = current_bond.bond_amount.amount * scaled_bond_reward_rate; + current_bond.bond_amount.amount += node_reward; + current_bond.total_delegation.amount += total_delegation_reward; + gateways(deps.storage).save(gateway_identity.as_bytes(), ¤t_bond)?; + } Ok(Response { submessages: vec![], @@ -540,6 +592,7 @@ fn validate_delegation_stake(delegation: &[Coin]) -> Result<(), ContractError> { pub(crate) fn try_delegate_to_mixnode( deps: DepsMut, + env: Env, info: MessageInfo, mix_identity: IdentityKey, ) -> Result { @@ -565,12 +618,15 @@ pub(crate) fn try_delegate_to_mixnode( let sender_bytes = info.sender.as_bytes(); // write the delegation - match delegation_bucket.may_load(sender_bytes)? { - Some(existing_delegation) => { - delegation_bucket.save(sender_bytes, &(existing_delegation + info.funds[0].amount))? - } - None => delegation_bucket.save(sender_bytes, &info.funds[0].amount)?, - } + let new_amount = match delegation_bucket.may_load(sender_bytes)? { + Some(existing_delegation) => existing_delegation.amount + info.funds[0].amount, + None => info.funds[0].amount, + }; + // the block height is reset, if it existed + let new_delegation = RawDelegationData::new(new_amount, env.block.height); + delegation_bucket.save(sender_bytes, &new_delegation)?; + + reverse_mix_delegations(deps.storage, &info.sender).save(mix_identity.as_bytes(), &())?; Ok(Response::default()) } @@ -584,13 +640,14 @@ pub(crate) fn try_remove_delegation_from_mixnode( let sender_bytes = info.sender.as_bytes(); match delegation_bucket.may_load(sender_bytes)? { Some(delegation) => { - // remove delegation from the bucket + // remove delegation from the buckets delegation_bucket.remove(sender_bytes); + reverse_mix_delegations(deps.storage, &info.sender).remove(mix_identity.as_bytes()); // send delegated funds back to the delegation owner let messages = vec![BankMsg::Send { to_address: info.sender.to_string(), - amount: coins(delegation.u128(), DENOM), + amount: coins(delegation.amount.u128(), DENOM), } .into()]; @@ -603,7 +660,7 @@ pub(crate) fn try_remove_delegation_from_mixnode( existing_bond.total_delegation.amount = existing_bond .total_delegation .amount - .checked_sub(delegation) + .checked_sub(delegation.amount) .unwrap(); mixnodes_bucket.save(mix_identity.as_bytes(), &existing_bond)?; } @@ -624,6 +681,7 @@ pub(crate) fn try_remove_delegation_from_mixnode( pub(crate) fn try_delegate_to_gateway( deps: DepsMut, + env: Env, info: MessageInfo, gateway_identity: IdentityKey, ) -> Result { @@ -649,12 +707,16 @@ pub(crate) fn try_delegate_to_gateway( let sender_bytes = info.sender.as_bytes(); // write the delegation - match delegation_bucket.may_load(sender_bytes)? { - Some(existing_delegation) => { - delegation_bucket.save(sender_bytes, &(existing_delegation + info.funds[0].amount))? - } - None => delegation_bucket.save(sender_bytes, &info.funds[0].amount)?, - } + let new_amount = match delegation_bucket.may_load(sender_bytes)? { + Some(existing_delegation) => existing_delegation.amount + info.funds[0].amount, + None => info.funds[0].amount, + }; + // the block height is reset, if it existed + let new_delegation = RawDelegationData::new(new_amount, env.block.height); + delegation_bucket.save(sender_bytes, &new_delegation)?; + + reverse_gateway_delegations(deps.storage, &info.sender) + .save(gateway_identity.as_bytes(), &())?; Ok(Response::default()) } @@ -668,13 +730,15 @@ pub(crate) fn try_remove_delegation_from_gateway( let sender_bytes = info.sender.as_bytes(); match delegation_bucket.may_load(sender_bytes)? { Some(delegation) => { - // remove delegation from the bucket + // remove delegation from the buckets delegation_bucket.remove(sender_bytes); + reverse_gateway_delegations(deps.storage, &info.sender) + .remove(gateway_identity.as_bytes()); // send delegated funds back to the delegation owner let messages = vec![BankMsg::Send { to_address: info.sender.to_string(), - amount: coins(delegation.u128(), DENOM), + amount: coins(delegation.amount.u128(), DENOM), } .into()]; @@ -689,7 +753,7 @@ pub(crate) fn try_remove_delegation_from_gateway( existing_bond.total_delegation.amount = existing_bond .total_delegation .amount - .checked_sub(delegation) + .checked_sub(delegation.amount) .unwrap(); gateways_bucket.save(gateway_identity.as_bytes(), &existing_bond)?; } @@ -726,7 +790,7 @@ pub mod tests { use crate::support::tests::helpers; use crate::support::tests::helpers::{ add_gateway, add_mixnode, gateway_fixture, good_gateway_bond, good_mixnode_bond, - mix_node_fixture, + mix_node_fixture, raw_delegation_fixture, }; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coin, coins, from_binary, Addr, Uint128}; @@ -1069,6 +1133,7 @@ pub mod tests { let info = mock_info("fred", &good_mixnode_bond()); try_add_mixnode( deps.as_mut(), + mock_env(), info, MixNode { identity_key: "fredsmixnode".to_string(), @@ -1468,6 +1533,7 @@ pub mod tests { let info = mock_info("fred", &good_gateway_bond()); try_add_gateway( deps.as_mut(), + mock_env(), info, Gateway { identity_key: "fredsgateway".into(), @@ -1760,6 +1826,7 @@ pub mod tests { #[test] fn rewarding_mixnode() { let mut deps = helpers::init_contract(); + let mut env = mock_env(); let current_state = config(deps.as_mut().storage).load().unwrap(); let network_monitor_address = current_state.network_monitor_address; @@ -1768,12 +1835,13 @@ pub mod tests { // errors out if executed by somebody else than network monitor let info = mock_info("not-the-monitor", &[]); - let res = try_reward_mixnode(deps.as_mut(), info, node_identity.clone(), 100); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100); assert_eq!(res, Err(ContractError::Unauthorized)); // returns bond not found attribute if the target owner hasn't bonded any mixnodes let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_mixnode(deps.as_mut(), info, node_identity.clone(), 100).unwrap(); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); assert_eq!(vec![attr("result", "bond not found")], res.attributes); let initial_bond = 100_000000; @@ -1783,6 +1851,7 @@ pub mod tests { total_delegation: coin(initial_delegation, DENOM), owner: node_owner.clone(), layer: Layer::One, + block_height: env.block.height, mix_node: MixNode { identity_key: node_identity.clone(), ..mix_node_fixture() @@ -1794,9 +1863,14 @@ pub mod tests { .unwrap(); mix_delegations(&mut deps.storage, &node_identity) - .save(b"delegator", &Uint128(initial_delegation)) + .save( + b"delegator", + &RawDelegationData::new(initial_delegation.into(), env.block.height), + ) .unwrap(); + env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING; + let bond_reward_rate = read_mixnode_epoch_bond_reward_rate(deps.as_ref().storage); let delegation_reward_rate = read_mixnode_epoch_delegation_reward_rate(deps.as_ref().storage); @@ -1809,7 +1883,8 @@ pub mod tests { let expected_delegation = expected_delegation_reward + Uint128(initial_delegation); let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_mixnode(deps.as_mut(), info, node_identity.clone(), 100).unwrap(); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); assert_eq!( expected_bond, @@ -1837,7 +1912,136 @@ pub mod tests { let expected_delegation = expected_delegation_reward + expected_delegation; let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_mixnode(deps.as_mut(), info, node_identity.clone(), 20).unwrap(); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 20) + .unwrap(); + + assert_eq!( + expected_bond, + read_mixnode_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + assert_eq!( + expected_delegation, + read_mixnode_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + + assert_eq!( + vec![ + attr("bond increase", expected_bond_reward), + attr("total delegation increase", expected_delegation_reward), + ], + res.attributes + ); + } + + #[test] + fn rewarding_mixnode_blockstamp_based() { + let mut deps = helpers::init_contract(); + let mut env = mock_env(); + let current_state = config(deps.as_mut().storage).load().unwrap(); + let network_monitor_address = current_state.network_monitor_address; + + let node_owner: Addr = Addr::unchecked("node-owner"); + let node_identity: IdentityKey = "nodeidentity".into(); + + let initial_bond = 100_000000; + let initial_delegation = 200_000000; + let mixnode_bond = MixNodeBond { + bond_amount: coin(initial_bond, DENOM), + total_delegation: coin(initial_delegation, DENOM), + owner: node_owner.clone(), + layer: Layer::One, + block_height: env.block.height, + mix_node: MixNode { + identity_key: node_identity.clone(), + ..mix_node_fixture() + }, + }; + + mixnodes(deps.as_mut().storage) + .save(node_identity.as_bytes(), &mixnode_bond) + .unwrap(); + + // delegation happens later, but not later enough + env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING - 1; + + mix_delegations(&mut deps.storage, &node_identity) + .save( + b"delegator", + &RawDelegationData::new(initial_delegation.into(), env.block.height), + ) + .unwrap(); + + let bond_reward_rate = read_mixnode_epoch_bond_reward_rate(deps.as_ref().storage); + let delegation_reward_rate = + read_mixnode_epoch_delegation_reward_rate(deps.as_ref().storage); + let scaled_bond_reward = scale_reward_by_uptime(bond_reward_rate, 100).unwrap(); + let scaled_delegation_reward = scale_reward_by_uptime(delegation_reward_rate, 100).unwrap(); + + // no reward is due + let expected_bond_reward = Uint128(0); + let expected_delegation_reward = Uint128(0); + let expected_bond = expected_bond_reward + Uint128(initial_bond); + let expected_delegation = expected_delegation_reward + Uint128(initial_delegation); + + let info = mock_info(network_monitor_address.as_ref(), &[]); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); + + assert_eq!( + expected_bond, + read_mixnode_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + assert_eq!( + expected_delegation, + read_mixnode_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + + assert_eq!( + vec![ + attr("bond increase", expected_bond_reward), + attr("total delegation increase", expected_delegation_reward), + ], + res.attributes + ); + + // reward can happen now, but only for bonded node + env.block.height += 1; + let expected_bond_reward = expected_bond * scaled_bond_reward; + let expected_delegation_reward = Uint128(0); + let expected_bond = expected_bond_reward + expected_bond; + let expected_delegation = expected_delegation_reward + expected_delegation; + + let info = mock_info(network_monitor_address.as_ref(), &[]); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); + + assert_eq!( + expected_bond, + read_mixnode_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + assert_eq!( + expected_delegation, + read_mixnode_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + + assert_eq!( + vec![ + attr("bond increase", expected_bond_reward), + attr("total delegation increase", expected_delegation_reward), + ], + res.attributes + ); + + // reward happens now, both for node owner and delegators + env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING - 1; + let expected_bond_reward = expected_bond * scaled_bond_reward; + let expected_delegation_reward = expected_delegation * scaled_delegation_reward; + let expected_bond = expected_bond_reward + expected_bond; + let expected_delegation = expected_delegation_reward + expected_delegation; + + let info = mock_info(network_monitor_address.as_ref(), &[]); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); assert_eq!( expected_bond, @@ -1860,6 +2064,7 @@ pub mod tests { #[test] fn rewarding_gateway() { let mut deps = helpers::init_contract(); + let mut env = mock_env(); let current_state = config(deps.as_mut().storage).load().unwrap(); let network_monitor_address = current_state.network_monitor_address; @@ -1868,12 +2073,13 @@ pub mod tests { // errors out if executed by somebody else than network monitor let info = mock_info("not-the-monitor", &[]); - let res = try_reward_gateway(deps.as_mut(), info, node_identity.clone(), 100); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100); assert_eq!(res, Err(ContractError::Unauthorized)); // returns bond not found attribute if the target owner hasn't bonded any gateways let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_gateway(deps.as_mut(), info, node_identity.clone(), 100).unwrap(); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); assert_eq!(vec![attr("result", "bond not found")], res.attributes); let initial_bond = 100_000000; @@ -1882,6 +2088,7 @@ pub mod tests { bond_amount: coin(initial_bond, DENOM), total_delegation: coin(initial_delegation, DENOM), owner: node_owner.clone(), + block_height: env.block.height, gateway: Gateway { identity_key: node_identity.clone(), ..gateway_fixture() @@ -1893,9 +2100,11 @@ pub mod tests { .unwrap(); gateway_delegations(&mut deps.storage, &node_identity) - .save(b"delegator", &Uint128(initial_delegation)) + .save(b"delegator", &raw_delegation_fixture(initial_delegation)) .unwrap(); + env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING; + let bond_reward_rate = read_gateway_epoch_bond_reward_rate(deps.as_ref().storage); let delegation_reward_rate = read_gateway_epoch_delegation_reward_rate(deps.as_ref().storage); @@ -1908,7 +2117,8 @@ pub mod tests { let expected_delegation = expected_delegation_reward + Uint128(initial_delegation); let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_gateway(deps.as_mut(), info, node_identity.clone(), 100).unwrap(); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); assert_eq!( expected_bond, @@ -1937,7 +2147,8 @@ pub mod tests { let expected_delegation = expected_delegation_reward + expected_delegation; let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_gateway(deps.as_mut(), info, node_identity.clone(), 20).unwrap(); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 20) + .unwrap(); assert_eq!( expected_bond, @@ -1958,6 +2169,133 @@ pub mod tests { ); } + #[test] + fn rewarding_gateway_blockstamp_based() { + let mut deps = helpers::init_contract(); + let mut env = mock_env(); + let current_state = config(deps.as_mut().storage).load().unwrap(); + let network_monitor_address = current_state.network_monitor_address; + + let node_owner: Addr = Addr::unchecked("node-owner"); + let node_identity: IdentityKey = "nodeidentity".into(); + + let initial_bond = 100_000000; + let initial_delegation = 200_000000; + let gateway_bond = GatewayBond { + bond_amount: coin(initial_bond, DENOM), + total_delegation: coin(initial_delegation, DENOM), + owner: node_owner.clone(), + block_height: env.block.height, + gateway: Gateway { + identity_key: node_identity.clone(), + ..gateway_fixture() + }, + }; + + gateways(deps.as_mut().storage) + .save(node_identity.as_bytes(), &gateway_bond) + .unwrap(); + + // delegation happens later, but not later enough + env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING - 1; + + gateway_delegations(&mut deps.storage, &node_identity) + .save( + b"delegator", + &RawDelegationData::new(initial_delegation.into(), env.block.height), + ) + .unwrap(); + + let bond_reward_rate = read_gateway_epoch_bond_reward_rate(deps.as_ref().storage); + let delegation_reward_rate = + read_gateway_epoch_delegation_reward_rate(deps.as_ref().storage); + let scaled_bond_reward = scale_reward_by_uptime(bond_reward_rate, 100).unwrap(); + let scaled_delegation_reward = scale_reward_by_uptime(delegation_reward_rate, 100).unwrap(); + + // no reward is due + let expected_bond_reward = Uint128(0); + let expected_delegation_reward = Uint128(0); + let expected_bond = expected_bond_reward + Uint128(initial_bond); + let expected_delegation = expected_delegation_reward + Uint128(initial_delegation); + + let info = mock_info(network_monitor_address.as_ref(), &[]); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); + + assert_eq!( + expected_bond, + read_gateway_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + assert_eq!( + expected_delegation, + read_gateway_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + + assert_eq!( + vec![ + attr("bond increase", expected_bond_reward), + attr("total delegation increase", expected_delegation_reward), + ], + res.attributes + ); + + // reward can happen now, but only for bonded node + env.block.height += 1; + let expected_bond_reward = expected_bond * scaled_bond_reward; + let expected_delegation_reward = Uint128(0); + let expected_bond = expected_bond_reward + expected_bond; + let expected_delegation = expected_delegation_reward + expected_delegation; + + let info = mock_info(network_monitor_address.as_ref(), &[]); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); + + assert_eq!( + expected_bond, + read_gateway_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + assert_eq!( + expected_delegation, + read_gateway_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + + assert_eq!( + vec![ + attr("bond increase", expected_bond_reward), + attr("total delegation increase", expected_delegation_reward), + ], + res.attributes + ); + + // reward happens now, both for node owner and delegators + env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING - 1; + let expected_bond_reward = expected_bond * scaled_bond_reward; + let expected_delegation_reward = expected_delegation * scaled_delegation_reward; + let expected_bond = expected_bond_reward + expected_bond; + let expected_delegation = expected_delegation_reward + expected_delegation; + + let info = mock_info(network_monitor_address.as_ref(), &[]); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); + + assert_eq!( + expected_bond, + read_gateway_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + assert_eq!( + expected_delegation, + read_gateway_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + + assert_eq!( + vec![ + attr("bond increase", expected_bond_reward), + attr("total delegation increase", expected_delegation_reward), + ], + res.attributes + ); + } + #[cfg(test)] mod delegation_stake_validation { use super::*; @@ -2019,6 +2357,7 @@ pub mod tests { }), try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info("sender", &coins(123, DENOM)), "non-existent-mix-identity".into() ) @@ -2030,21 +2369,28 @@ pub mod tests { let mut deps = helpers::init_contract(); let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); let delegation = coin(123, DENOM); assert!(try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &vec![delegation.clone()]), + mock_env(), + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), identity.clone() ) .is_ok()); assert_eq!( - delegation.amount, + RawDelegationData::new(delegation.amount, mock_env().block.height), mix_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() ); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); // node's "total_delegation" is increased assert_eq!( @@ -2062,6 +2408,7 @@ pub mod tests { let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); @@ -2071,7 +2418,8 @@ pub mod tests { }), try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(123, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(123, DENOM)), identity ) ); @@ -2086,20 +2434,27 @@ pub mod tests { try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); let delegation = coin(123, DENOM); + let delegation_owner = Addr::unchecked("sender"); assert!(try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &vec![delegation.clone()]), + mock_env(), + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), identity.clone() ) .is_ok()); assert_eq!( - delegation.amount, + RawDelegationData::new(delegation.amount, mock_env().block.height), mix_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() ); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); // node's "total_delegation" is increased assert_eq!( @@ -2116,30 +2471,41 @@ pub mod tests { let mut deps = helpers::init_contract(); let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); let delegation1 = coin(100, DENOM); let delegation2 = coin(50, DENOM); try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &vec![delegation1.clone()]), + mock_env(), + mock_info(delegation_owner.as_str(), &vec![delegation1.clone()]), identity.clone(), ) .unwrap(); try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &vec![delegation2.clone()]), + mock_env(), + mock_info(delegation_owner.as_str(), &vec![delegation2.clone()]), identity.clone(), ) .unwrap(); assert_eq!( - delegation1.amount + delegation2.amount, + RawDelegationData::new( + delegation1.amount + delegation2.amount, + mock_env().block.height + ), mix_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() ); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); // node's "total_delegation" is sum of both assert_eq!( @@ -2152,16 +2518,118 @@ pub mod tests { ) } + #[test] + fn block_height_is_updated_on_new_delegation() { + let mut deps = helpers::init_contract(); + let mixnode_owner = "bob"; + let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); + let delegation = coin(100, DENOM); + + let env1 = mock_env(); + let mut env2 = mock_env(); + let initial_height = env1.block.height; + let updated_height = initial_height + 42; + // second env has grown in block height + env2.block.height = updated_height; + + try_delegate_to_mixnode( + deps.as_mut(), + env1, + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation.amount, initial_height), + mix_delegations_read(&deps.storage, &identity) + .load(delegation_owner.as_bytes()) + .unwrap() + ); + + try_delegate_to_mixnode( + deps.as_mut(), + env2, + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation.amount + delegation.amount, updated_height), + mix_delegations_read(&deps.storage, &identity) + .load(delegation_owner.as_bytes()) + .unwrap() + ); + } + + #[test] + fn block_height_is_not_updated_on_different_delegator() { + let mut deps = helpers::init_contract(); + let mixnode_owner = "bob"; + let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner1 = Addr::unchecked("sender1"); + let delegation_owner2 = Addr::unchecked("sender2"); + let delegation1 = coin(100, DENOM); + let delegation2 = coin(120, DENOM); + + let env1 = mock_env(); + let mut env2 = mock_env(); + let initial_height = env1.block.height; + let second_height = initial_height + 42; + // second env has grown in block height + env2.block.height = second_height; + + try_delegate_to_mixnode( + deps.as_mut(), + env1, + mock_info(delegation_owner1.as_str(), &vec![delegation1.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation1.amount, initial_height), + mix_delegations_read(&deps.storage, &identity) + .load(delegation_owner1.as_bytes()) + .unwrap() + ); + + try_delegate_to_mixnode( + deps.as_mut(), + env2, + mock_info(delegation_owner2.as_str(), &vec![delegation2.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation1.amount, initial_height), + mix_delegations_read(&deps.storage, &identity) + .load(delegation_owner1.as_bytes()) + .unwrap() + ); + assert_eq!( + RawDelegationData::new(delegation2.amount, second_height), + mix_delegations_read(&deps.storage, &identity) + .load(delegation_owner2.as_bytes()) + .unwrap() + ); + } + #[test] fn is_disallowed_for_already_delegated_node_if_it_unbonded() { let mut deps = helpers::init_contract(); let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2174,7 +2642,8 @@ pub mod tests { }), try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(50, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(50, DENOM)), identity ) ); @@ -2187,35 +2656,46 @@ pub mod tests { let mixnode_owner2 = "fred"; let identity1 = add_mixnode(mixnode_owner1, good_mixnode_bond(), &mut deps); let identity2 = add_mixnode(mixnode_owner2, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); assert!(try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(123, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(123, DENOM)), identity1.clone() ) .is_ok()); assert!(try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(42, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(42, DENOM)), identity2.clone() ) .is_ok()); assert_eq!( - 123, + RawDelegationData::new(123u128.into(), mock_env().block.height), mix_delegations_read(&deps.storage, &identity1) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() - .u128() + ); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .load(identity1.as_bytes()) + .is_ok() ); assert_eq!( - 42, + RawDelegationData::new(42u128.into(), mock_env().block.height), mix_delegations_read(&deps.storage, &identity2) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() - .u128() + ); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .load(identity2.as_bytes()) + .is_ok() ); } @@ -2230,6 +2710,7 @@ pub mod tests { assert!(try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info("sender1", &vec![delegation1.clone()]), identity.clone() ) @@ -2237,6 +2718,7 @@ pub mod tests { assert!(try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info("sender2", &vec![delegation2.clone()]), identity.clone() ) @@ -2259,10 +2741,12 @@ pub mod tests { let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2270,11 +2754,15 @@ pub mod tests { try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); assert_eq!( - 100, + RawDelegationData::new(100u128.into(), mock_env().block.height), mix_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() - .u128() + ); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() ); } } @@ -2291,15 +2779,16 @@ pub mod tests { let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); assert_eq!( Err(ContractError::NoMixnodeDelegationFound { identity: identity.clone(), - address: Addr::unchecked("sender"), + address: delegation_owner.clone(), }), try_remove_delegation_from_mixnode( deps.as_mut(), - mock_info("sender", &[]), + mock_info(delegation_owner.as_str(), &[]), identity, ) ); @@ -2311,10 +2800,12 @@ pub mod tests { let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2323,7 +2814,7 @@ pub mod tests { Ok(Response { submessages: vec![], messages: vec![BankMsg::Send { - to_address: "sender".into(), + to_address: delegation_owner.clone().into(), amount: coins(100, DENOM), } .into()], @@ -2332,15 +2823,21 @@ pub mod tests { }), try_remove_delegation_from_mixnode( deps.as_mut(), - mock_info("sender", &[]), + mock_info(delegation_owner.as_str(), &[]), identity.clone(), ) ); assert!(mix_delegations_read(&deps.storage, &identity) - .may_load(b"sender") + .may_load(delegation_owner.as_bytes()) .unwrap() .is_none()); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .may_load(identity.as_bytes()) + .unwrap() + .is_none() + ); // and total delegation is cleared assert_eq!( @@ -2359,10 +2856,12 @@ pub mod tests { let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2373,7 +2872,7 @@ pub mod tests { Ok(Response { submessages: vec![], messages: vec![BankMsg::Send { - to_address: "sender".into(), + to_address: delegation_owner.clone().into(), amount: coins(100, DENOM), } .into()], @@ -2382,15 +2881,21 @@ pub mod tests { }), try_remove_delegation_from_mixnode( deps.as_mut(), - mock_info("sender", &[]), + mock_info(delegation_owner.as_str(), &[]), identity.clone(), ) ); assert!(mix_delegations_read(&deps.storage, &identity) - .may_load(b"sender") + .may_load(delegation_owner.as_bytes()) .unwrap() .is_none()); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .may_load(identity.as_bytes()) + .unwrap() + .is_none() + ); } #[test] @@ -2398,20 +2903,24 @@ pub mod tests { let mut deps = helpers::init_contract(); let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner1 = Addr::unchecked("sender1"); + let delegation_owner2 = Addr::unchecked("sender2"); let delegation1 = coin(123, DENOM); let delegation2 = coin(234, DENOM); assert!(try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender1", &vec![delegation1.clone()]), + mock_env(), + mock_info(delegation_owner1.as_str(), &vec![delegation1.clone()]), identity.clone() ) .is_ok()); assert!(try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender2", &vec![delegation2.clone()]), + mock_env(), + mock_info(delegation_owner2.as_str(), &vec![delegation2.clone()]), identity.clone() ) .is_ok()); @@ -2419,7 +2928,7 @@ pub mod tests { // sender1 undelegates try_remove_delegation_from_mixnode( deps.as_mut(), - mock_info("sender1", &[]), + mock_info(delegation_owner1.as_str(), &[]), identity.clone(), ) .unwrap(); @@ -2439,6 +2948,7 @@ pub mod tests { #[test] fn delegators_on_mix_node_reward_rate() { let mut deps = helpers::init_contract(); + let mut env = mock_env(); let current_state = config(deps.as_mut().storage).load().unwrap(); let network_monitor_address = current_state.network_monitor_address; @@ -2451,15 +2961,26 @@ pub mod tests { let identity = add_mixnode(node_owner, good_mixnode_bond(), &mut deps); mix_delegations(&mut deps.storage, &identity) - .save(b"delegator1", &Uint128(initial_delegation1)) + .save( + b"delegator1", + &RawDelegationData::new(initial_delegation1.into(), env.block.height), + ) .unwrap(); mix_delegations(&mut deps.storage, &identity) - .save(b"delegator2", &Uint128(initial_delegation2)) + .save( + b"delegator2", + &RawDelegationData::new(initial_delegation2.into(), env.block.height), + ) .unwrap(); mix_delegations(&mut deps.storage, &identity) - .save(b"delegator3", &Uint128(initial_delegation3)) + .save( + b"delegator3", + &RawDelegationData::new(initial_delegation3.into(), env.block.height), + ) .unwrap(); + env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING; + let bond_reward = read_mixnode_epoch_bond_reward_rate(deps.as_ref().storage); let delegation_reward = read_mixnode_epoch_delegation_reward_rate(deps.as_ref().storage); @@ -2476,7 +2997,8 @@ pub mod tests { let expected_delegation3 = expected_delegation3_reward + Uint128(initial_delegation3); let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_mixnode(deps.as_mut(), info, identity.clone(), 100).unwrap(); + let res = + try_reward_mixnode(deps.as_mut(), env.clone(), info, identity.clone(), 100).unwrap(); assert_eq!( expected_bond, @@ -2488,6 +3010,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator1".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2495,6 +3018,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator2".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2502,6 +3026,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator3".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2532,7 +3057,8 @@ pub mod tests { let expected_delegation3 = expected_delegation3_reward + expected_delegation3; let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_mixnode(deps.as_mut(), info, identity.clone(), 20).unwrap(); + let res = + try_reward_mixnode(deps.as_mut(), env.clone(), info, identity.clone(), 20).unwrap(); assert_eq!( expected_bond, @@ -2544,6 +3070,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator1".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2551,6 +3078,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator2".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2558,6 +3086,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator3".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2575,7 +3104,8 @@ pub mod tests { // if the node was 0% up, nobody will get any rewards let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_mixnode(deps.as_mut(), info, identity.clone(), 0).unwrap(); + let res = + try_reward_mixnode(deps.as_mut(), env.clone(), info, identity.clone(), 0).unwrap(); assert_eq!( expected_bond, @@ -2587,6 +3117,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator1".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2594,6 +3125,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator2".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2601,6 +3133,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator3".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2627,6 +3160,7 @@ pub mod tests { }), try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info("sender", &coins(123, DENOM)), "non-existent-gateway-identity".into() ) @@ -2638,21 +3172,28 @@ pub mod tests { let mut deps = helpers::init_contract(); let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); let delegation = coin(123, DENOM); assert!(try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &vec![delegation.clone()]), + mock_env(), + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), identity.clone() ) .is_ok()); assert_eq!( - delegation.amount, + RawDelegationData::new(delegation.amount, mock_env().block.height), gateway_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() ); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); // node's "total_delegation" is increased assert_eq!( @@ -2670,6 +3211,8 @@ pub mod tests { let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); + try_remove_gateway(deps.as_mut(), mock_info(gateway_owner, &[])).unwrap(); assert_eq!( @@ -2678,7 +3221,8 @@ pub mod tests { }), try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(123, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(123, DENOM)), identity ) ); @@ -2693,20 +3237,27 @@ pub mod tests { try_remove_gateway(deps.as_mut(), mock_info(gateway_owner, &[])).unwrap(); let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); let delegation = coin(123, DENOM); + let delegation_owner = Addr::unchecked("sender"); assert!(try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &vec![delegation.clone()]), + mock_env(), + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), identity.clone() ) .is_ok()); assert_eq!( - delegation.amount, + RawDelegationData::new(delegation.amount, mock_env().block.height), gateway_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() ); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); // node's "total_delegation" is increased assert_eq!( @@ -2723,30 +3274,41 @@ pub mod tests { let mut deps = helpers::init_contract(); let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); let delegation1 = coin(100, DENOM); let delegation2 = coin(50, DENOM); try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &vec![delegation1.clone()]), + mock_env(), + mock_info(delegation_owner.as_str(), &vec![delegation1.clone()]), identity.clone(), ) .unwrap(); try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &vec![delegation2.clone()]), + mock_env(), + mock_info(delegation_owner.as_str(), &vec![delegation2.clone()]), identity.clone(), ) .unwrap(); assert_eq!( - delegation1.amount + delegation2.amount, + RawDelegationData::new( + delegation1.amount + delegation2.amount, + mock_env().block.height + ), gateway_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() ); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); // node's "total_delegation" is sum of both assert_eq!( @@ -2759,16 +3321,118 @@ pub mod tests { ) } + #[test] + fn block_height_is_updated_on_new_delegation() { + let mut deps = helpers::init_contract(); + let gateway_owner = "bob"; + let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); + let delegation = coin(100, DENOM); + + let env1 = mock_env(); + let mut env2 = mock_env(); + let initial_height = env1.block.height; + let updated_height = initial_height + 42; + // second env has grown in block height + env2.block.height = updated_height; + + try_delegate_to_gateway( + deps.as_mut(), + env1, + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation.amount, initial_height), + gateway_delegations_read(&deps.storage, &identity) + .load(delegation_owner.as_bytes()) + .unwrap() + ); + + try_delegate_to_gateway( + deps.as_mut(), + env2, + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation.amount + delegation.amount, updated_height), + gateway_delegations_read(&deps.storage, &identity) + .load(delegation_owner.as_bytes()) + .unwrap() + ); + } + + #[test] + fn block_height_is_not_updated_on_different_delegator() { + let mut deps = helpers::init_contract(); + let gateway_owner = "bob"; + let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner1 = Addr::unchecked("sender1"); + let delegation_owner2 = Addr::unchecked("sender2"); + let delegation1 = coin(100, DENOM); + let delegation2 = coin(120, DENOM); + + let env1 = mock_env(); + let mut env2 = mock_env(); + let initial_height = env1.block.height; + let second_height = initial_height + 42; + // second env has grown in block height + env2.block.height = second_height; + + try_delegate_to_gateway( + deps.as_mut(), + env1, + mock_info(delegation_owner1.as_str(), &vec![delegation1.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation1.amount, initial_height), + gateway_delegations_read(&deps.storage, &identity) + .load(delegation_owner1.as_bytes()) + .unwrap() + ); + + try_delegate_to_gateway( + deps.as_mut(), + env2, + mock_info(delegation_owner2.as_str(), &vec![delegation2.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation1.amount, initial_height), + gateway_delegations_read(&deps.storage, &identity) + .load(delegation_owner1.as_bytes()) + .unwrap() + ); + assert_eq!( + RawDelegationData::new(delegation2.amount, second_height), + gateway_delegations_read(&deps.storage, &identity) + .load(delegation_owner2.as_bytes()) + .unwrap() + ); + } + #[test] fn is_disallowed_for_already_delegated_node_if_it_unbonded() { let mut deps = helpers::init_contract(); let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2781,7 +3445,8 @@ pub mod tests { }), try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(50, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(50, DENOM)), identity.clone() ) ); @@ -2794,35 +3459,46 @@ pub mod tests { let gateway_owner2 = "fred"; let identity1 = add_gateway(gateway_owner1, good_gateway_bond(), &mut deps); let identity2 = add_gateway(gateway_owner2, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); assert!(try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(123, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(123, DENOM)), identity1.clone() ) .is_ok()); assert!(try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(42, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(42, DENOM)), identity2.clone() ) .is_ok()); assert_eq!( - 123, + RawDelegationData::new(123u128.into(), mock_env().block.height), gateway_delegations_read(&deps.storage, &identity1) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() - .u128() + ); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .load(identity1.as_bytes()) + .is_ok() ); assert_eq!( - 42, + RawDelegationData::new(42u128.into(), mock_env().block.height), gateway_delegations_read(&deps.storage, &identity2) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() - .u128() + ); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .load(identity2.as_bytes()) + .is_ok() ); } @@ -2837,6 +3513,7 @@ pub mod tests { assert!(try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info("sender1", &vec![delegation1.clone()]), identity.clone() ) @@ -2844,6 +3521,7 @@ pub mod tests { assert!(try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info("sender2", &vec![delegation2.clone()]), identity.clone() ) @@ -2866,10 +3544,12 @@ pub mod tests { let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2877,11 +3557,15 @@ pub mod tests { try_remove_gateway(deps.as_mut(), mock_info(gateway_owner, &[])).unwrap(); assert_eq!( - 100, + RawDelegationData::new(100u128.into(), mock_env().block.height), gateway_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() - .u128() + ); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() ); } } @@ -2898,14 +3582,16 @@ pub mod tests { let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); + assert_eq!( Err(ContractError::NoGatewayDelegationFound { identity: identity.clone(), - address: Addr::unchecked("sender"), + address: delegation_owner.clone(), }), try_remove_delegation_from_gateway( deps.as_mut(), - mock_info("sender", &[]), + mock_info(delegation_owner.as_str(), &[]), identity, ) ); @@ -2917,10 +3603,12 @@ pub mod tests { let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2929,7 +3617,7 @@ pub mod tests { Ok(Response { submessages: vec![], messages: vec![BankMsg::Send { - to_address: "sender".into(), + to_address: delegation_owner.clone().into(), amount: coins(100, DENOM), } .into()], @@ -2938,15 +3626,21 @@ pub mod tests { }), try_remove_delegation_from_gateway( deps.as_mut(), - mock_info("sender", &[]), + mock_info(delegation_owner.as_str(), &[]), identity.clone(), ) ); assert!(gateway_delegations_read(&deps.storage, &identity) - .may_load(b"sender") + .may_load(delegation_owner.as_bytes()) .unwrap() .is_none()); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .may_load(identity.as_bytes()) + .unwrap() + .is_none() + ); // and total delegation is cleared assert_eq!( @@ -2965,10 +3659,12 @@ pub mod tests { let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_env(), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2979,7 +3675,7 @@ pub mod tests { Ok(Response { submessages: vec![], messages: vec![BankMsg::Send { - to_address: "sender".into(), + to_address: delegation_owner.clone().into(), amount: coins(100, DENOM), } .into()], @@ -2988,15 +3684,21 @@ pub mod tests { }), try_remove_delegation_from_gateway( deps.as_mut(), - mock_info("sender", &[]), + mock_info(delegation_owner.as_str(), &[]), identity.clone(), ) ); assert!(gateway_delegations_read(&deps.storage, &identity) - .may_load(b"sender") + .may_load(delegation_owner.as_bytes()) .unwrap() .is_none()); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .may_load(identity.as_bytes()) + .unwrap() + .is_none() + ); } #[test] @@ -3004,20 +3706,24 @@ pub mod tests { let mut deps = helpers::init_contract(); let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner1 = Addr::unchecked("sender1"); + let delegation_owner2 = Addr::unchecked("sender2"); let delegation1 = coin(123, DENOM); let delegation2 = coin(234, DENOM); assert!(try_delegate_to_gateway( deps.as_mut(), - mock_info("sender1", &vec![delegation1.clone()]), + mock_env(), + mock_info(delegation_owner1.as_str(), &vec![delegation1.clone()]), identity.clone() ) .is_ok()); assert!(try_delegate_to_gateway( deps.as_mut(), - mock_info("sender2", &vec![delegation2.clone()]), + mock_env(), + mock_info(delegation_owner2.as_str(), &vec![delegation2.clone()]), identity.clone() ) .is_ok()); @@ -3025,7 +3731,7 @@ pub mod tests { // sender1 undelegates try_remove_delegation_from_gateway( deps.as_mut(), - mock_info("sender1", &[]), + mock_info(delegation_owner1.as_str(), &[]), identity.clone(), ) .unwrap(); @@ -3045,6 +3751,7 @@ pub mod tests { #[test] fn delegators_on_gateway_reward_rate() { let mut deps = helpers::init_contract(); + let mut env = mock_env(); let current_state = config(deps.as_mut().storage).load().unwrap(); let network_monitor_address = current_state.network_monitor_address; @@ -3057,15 +3764,17 @@ pub mod tests { let identity = add_gateway(node_owner, good_gateway_bond(), &mut deps); gateway_delegations(&mut deps.storage, &identity) - .save(b"delegator1", &Uint128(initial_delegation1)) + .save(b"delegator1", &raw_delegation_fixture(initial_delegation1)) .unwrap(); gateway_delegations(&mut deps.storage, &identity) - .save(b"delegator2", &Uint128(initial_delegation2)) + .save(b"delegator2", &raw_delegation_fixture(initial_delegation2)) .unwrap(); gateway_delegations(&mut deps.storage, &identity) - .save(b"delegator3", &Uint128(initial_delegation3)) + .save(b"delegator3", &raw_delegation_fixture(initial_delegation3)) .unwrap(); + env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING; + let bond_reward = read_gateway_epoch_bond_reward_rate(deps.as_ref().storage); let delegation_reward = read_gateway_epoch_delegation_reward_rate(deps.as_ref().storage); @@ -3082,7 +3791,8 @@ pub mod tests { let expected_delegation3 = expected_delegation3_reward + Uint128(initial_delegation3); let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_gateway(deps.as_mut(), info, identity.clone(), 100).unwrap(); + let res = + try_reward_gateway(deps.as_mut(), env.clone(), info, identity.clone(), 100).unwrap(); assert_eq!( expected_bond, @@ -3094,6 +3804,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator1".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3101,6 +3812,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator2".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3108,6 +3820,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator3".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3138,7 +3851,8 @@ pub mod tests { let expected_delegation3 = expected_delegation3_reward + expected_delegation3; let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_gateway(deps.as_mut(), info, identity.clone(), 20).unwrap(); + let res = + try_reward_gateway(deps.as_mut(), env.clone(), info, identity.clone(), 20).unwrap(); assert_eq!( expected_bond, @@ -3150,6 +3864,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator1".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3157,6 +3872,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator2".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3164,6 +3880,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator3".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3181,7 +3898,8 @@ pub mod tests { // if the node was 0% up, nobody will get any rewards let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_gateway(deps.as_mut(), info, identity.clone(), 0).unwrap(); + let res = + try_reward_gateway(deps.as_mut(), env.clone(), info, identity.clone(), 0).unwrap(); assert_eq!( expected_bond, @@ -3193,6 +3911,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator1".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3200,6 +3919,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator2".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3207,6 +3927,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator3".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3229,7 +3950,7 @@ pub mod tests { let node_identity: IdentityKey = "nodeidentity".into(); let read_bucket = mix_delegations_read(&deps.storage, &node_identity); - let old_delegations = find_old_delegations(read_bucket).unwrap(); + let old_delegations = total_delegations(read_bucket).unwrap(); assert_eq!(Coin::new(0, DENOM), old_delegations); } @@ -3246,25 +3967,25 @@ pub mod tests { OLD_DELEGATIONS_CHUNK_SIZE * 3 + 1, ]; - for total_delegations in num_delegations { + for delegations in num_delegations { let mut deps = helpers::init_contract(); let node_identity: IdentityKey = "nodeidentity".into(); // delegate some stake let mut write_bucket = mix_delegations(&mut deps.storage, &node_identity); - for i in 1..=total_delegations { + for i in 1..=delegations { let delegator = Addr::unchecked(format!("delegator{}", i)); - let delegation = Uint128(i as u128); + let delegation = raw_delegation_fixture(i as u128); write_bucket .save(delegator.as_bytes(), &delegation) .unwrap(); } let read_bucket = mix_delegations_read(&deps.storage, &node_identity); - let old_delegations = find_old_delegations(read_bucket).unwrap(); + let old_delegations = total_delegations(read_bucket).unwrap(); - let total_delegation = (1..=total_delegations as u128).into_iter().sum(); + let total_delegation = (1..=delegations as u128).into_iter().sum(); assert_eq!(Coin::new(total_delegation, DENOM), old_delegations); } } @@ -3276,6 +3997,7 @@ pub mod tests { for owner in ["alice", "bob"] { try_add_mixnode( deps.as_mut(), + mock_env(), mock_info(owner, &good_mixnode_bond()), MixNode { identity_key: owner.to_string(), diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index c5906c376a..cf97221de4 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -23,4 +23,4 @@ pretty_env_logger = "0.4.0" mixnet-contract = { path = "../common/mixnet-contract" } network-defaults = { path = "../common/network-defaults" } -validator-client = { path = "../common/client-libs/validator-client" } +validator-client = { path = "../common/client-libs/validator-client", features=["nymd-client"] } diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index 20b5c2434c..56cb5ba848 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -6,11 +6,11 @@ use serde::Serialize; use mixnet_contract::{Addr, Coin, Layer, MixNode}; use crate::mix_node::models::{NodeDescription, NodeStats}; -use crate::mix_nodes::Location; +use crate::mix_nodes::{get_mixnode_delegations, Location}; use crate::state::ExplorerApiStateContext; pub fn mix_node_make_default_routes() -> Vec { - routes_with_openapi![get_description, get_stats, list] + routes_with_openapi![get_delegations, get_description, get_stats, list] } #[derive(Clone, Debug, Serialize, JsonSchema)] @@ -51,6 +51,12 @@ pub(crate) async fn list( ) } +#[openapi(tag = "mix_node")] +#[get("//delegations")] +pub(crate) async fn get_delegations(pubkey: &str) -> Json> { + Json(get_mixnode_delegations(pubkey).await) +} + #[openapi(tag = "mix_node")] #[get("//description")] pub(crate) async fn get_description( diff --git a/explorer-api/src/mix_nodes/mod.rs b/explorer-api/src/mix_nodes/mod.rs index 9ed89fc4d0..2c82ef22d9 100644 --- a/explorer-api/src/mix_nodes/mod.rs +++ b/explorer-api/src/mix_nodes/mod.rs @@ -8,8 +8,11 @@ use rocket::tokio::sync::RwLock; use serde::{Deserialize, Serialize}; use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code; -use mixnet_contract::MixNodeBond; -use network_defaults::default_api_endpoints; +use mixnet_contract::{Delegation, MixNodeBond}; +use network_defaults::{ + default_api_endpoints, default_nymd_endpoints, DEFAULT_MIXNET_CONTRACT_ADDRESS, +}; +use validator_client::nymd::QueryNymdClient; pub(crate) type LocationCache = HashMap; @@ -163,6 +166,32 @@ pub(crate) async fn retrieve_mixnodes() -> Vec { bonds } +pub(crate) async fn get_mixnode_delegations(pubkey: &str) -> Vec { + let client = new_nymd_client(); + let delegates = match client + .get_all_nymd_mixnode_delegations(pubkey.to_string()) + .await + { + Ok(result) => result, + Err(e) => { + error!("Could not get delegations for mix node {}: {:?}", pubkey, e); + vec![] + } + }; + delegates +} + +fn new_nymd_client() -> validator_client::Client { + let mixnet_contract = DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(); + let nymd_url = default_nymd_endpoints()[0].clone(); + let api_url = default_api_endpoints()[0].clone(); + + let client_config = + validator_client::Config::new(nymd_url, api_url, Some(mixnet_contract.parse().unwrap())); + + validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!") +} + // TODO: inject constants fn new_validator_client() -> validator_client::ApiClient { validator_client::ApiClient::new(default_api_endpoints()[0].clone()) diff --git a/explorer-api/src/ping/http.rs b/explorer-api/src/ping/http.rs index 248db7874d..8853fbac61 100644 --- a/explorer-api/src/ping/http.rs +++ b/explorer-api/src/ping/http.rs @@ -1,13 +1,14 @@ use std::collections::HashMap; -use std::net::ToSocketAddrs; +use std::net::{SocketAddr, ToSocketAddrs}; use std::time::Duration; use rocket::serde::json::Json; use rocket::{Route, State}; +use mixnet_contract::MixNodeBond; + use crate::ping::models::PingResponse; use crate::state::ExplorerApiStateContext; -use mixnet_contract::MixNodeBond; const CONNECTION_TIMEOUT_SECONDS: Duration = Duration::from_secs(10); @@ -80,32 +81,95 @@ async fn port_check(bond: &MixNodeBond) -> HashMap { ports } -async fn do_port_check(host: &str, port: u16) -> bool { - let addr = format!("{}:{}", host, port) - .to_socket_addrs() - .unwrap() - .next() - .unwrap(); - match tokio::time::timeout( - CONNECTION_TIMEOUT_SECONDS, - tokio::net::TcpStream::connect(addr), - ) - .await - { - Ok(Ok(_stream)) => { - // didn't timeout and tcp stream is open - trace!("Successfully pinged {}", addr); - true - } - Ok(Err(_stream_err)) => { - error!("{} ping failed {:}", addr, _stream_err); - // didn't timeout but couldn't open tcp stream - false - } - Err(_timeout) => { - // timed out - error!("{} timed out {:}", addr, _timeout); - false +fn sanitize_and_resolve_host(host: &str, port: u16) -> Option { + // trim the host + let trimmed_host = host.trim(); + + // host must be at least one non-whitespace character + if trimmed_host.is_empty() { + return None; + } + + // the host string should hopefully parse and resolve into a valid socket address + let parsed_host = format!("{}:{}", trimmed_host, port); + match parsed_host.to_socket_addrs() { + Ok(mut addrs) => addrs.next(), + Err(e) => { + warn!( + "Failed to resolve {}:{} -> {}. Error: {}", + host, port, parsed_host, e + ); + None } } } + +async fn do_port_check(host: &str, port: u16) -> bool { + match sanitize_and_resolve_host(host, port) { + Some(addr) => match tokio::time::timeout( + CONNECTION_TIMEOUT_SECONDS, + tokio::net::TcpStream::connect(addr), + ) + .await + { + Ok(Ok(_stream)) => { + // didn't timeout and tcp stream is open + trace!("Successfully pinged {}", addr); + true + } + Ok(Err(_stream_err)) => { + warn!("{} ping failed {:}", addr, _stream_err); + // didn't timeout but couldn't open tcp stream + false + } + Err(_timeout) => { + // timed out + warn!("{} timed out {:}", addr, _timeout); + false + } + }, + None => false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn resolve_host_with_valid_ip_address_returns_some() { + assert!(sanitize_and_resolve_host("8.8.8.8", 1234).is_some()); + assert!(sanitize_and_resolve_host("2001:4860:4860::8888", 1234).is_some()); + } + + #[test] + fn resolve_host_with_valid_hostname_returns_some() { + assert!(sanitize_and_resolve_host("nymtech.net", 1234).is_some()); + } + + #[test] + fn resolve_host_with_malformed_ip_address_returns_none() { + // these are invalid ip addresses + assert!(sanitize_and_resolve_host("192.168.1.999", 1234).is_none()); + assert!(sanitize_and_resolve_host("10.999.999.999", 1234).is_none()); + } + + #[test] + fn resolve_host_with_unknown_hostname_returns_none() { + assert!(sanitize_and_resolve_host( + "some-unknown-hostname-that-will-never-resolve.nymtech.net", + 1234 + ) + .is_none()); + } + + #[test] + fn resolve_host_with_bad_strings_return_none() { + assert!(sanitize_and_resolve_host("", 1234).is_none()); + assert!(sanitize_and_resolve_host(" ", 1234).is_none()); + assert!(sanitize_and_resolve_host(" 🤘 ", 1234).is_none()); + assert!(sanitize_and_resolve_host("🤘", 1234).is_none()); + assert!(sanitize_and_resolve_host("@", 1234).is_none()); + assert!(sanitize_and_resolve_host("*", 1234).is_none()); + } +} diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml index e9d28094ae..442d9837f0 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/gateway/gateway-requests/Cargo.toml @@ -24,6 +24,7 @@ crypto = { path = "../../common/crypto" } pemstore = { path = "../../common/pemstore" } coconut-interface = { path = "../../common/coconut-interface" } +credentials = { path = "../../common/credentials"} [dependencies.tungstenite] version = "0.13.0" diff --git a/gateway/gateway-requests/src/authentication/encrypted_address.rs b/gateway/gateway-requests/src/authentication/encrypted_address.rs index 73ae885dfd..36c5f969ed 100644 --- a/gateway/gateway-requests/src/authentication/encrypted_address.rs +++ b/gateway/gateway-requests/src/authentication/encrypted_address.rs @@ -1,7 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::authentication::iv::AuthenticationIV; +use crate::iv::IV; use crate::registration::handshake::shared_key::SharedKeys; use crypto::symmetric::stream_cipher; use nymsphinx::params::GatewayEncryptionAlgorithm; @@ -23,7 +23,7 @@ pub enum EncryptedAddressConversionError { } impl EncryptedAddressBytes { - pub fn new(address: &DestinationAddressBytes, key: &SharedKeys, iv: &AuthenticationIV) -> Self { + pub fn new(address: &DestinationAddressBytes, key: &SharedKeys, iv: &IV) -> Self { let ciphertext = stream_cipher::encrypt::( key.encryption_key(), iv.inner(), @@ -35,12 +35,7 @@ impl EncryptedAddressBytes { EncryptedAddressBytes(enc_address) } - pub fn verify( - &self, - address: &DestinationAddressBytes, - key: &SharedKeys, - iv: &AuthenticationIV, - ) -> bool { + pub fn verify(&self, address: &DestinationAddressBytes, key: &SharedKeys, iv: &IV) -> bool { self == &Self::new(address, key, iv) } diff --git a/gateway/gateway-requests/src/authentication/mod.rs b/gateway/gateway-requests/src/authentication/mod.rs index cf1ba5aab3..3e2a4c4d5a 100644 --- a/gateway/gateway-requests/src/authentication/mod.rs +++ b/gateway/gateway-requests/src/authentication/mod.rs @@ -2,4 +2,3 @@ // SPDX-License-Identifier: Apache-2.0 pub mod encrypted_address; -pub mod iv; diff --git a/gateway/gateway-requests/src/authentication/iv.rs b/gateway/gateway-requests/src/iv.rs similarity index 81% rename from gateway/gateway-requests/src/authentication/iv.rs rename to gateway/gateway-requests/src/iv.rs index a0964c96bf..95fae40095 100644 --- a/gateway/gateway-requests/src/authentication/iv.rs +++ b/gateway/gateway-requests/src/iv.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crypto::generic_array::{typenum::Unsigned, GenericArray}; -use crypto::symmetric::stream_cipher::{random_iv, NewStreamCipher, IV}; +use crypto::symmetric::stream_cipher::{random_iv, NewStreamCipher, IV as CryptoIV}; use nymsphinx::params::GatewayEncryptionAlgorithm; use rand::{CryptoRng, RngCore}; @@ -10,7 +10,7 @@ type NonceSize = ::NonceSize; // I think 'IV' looks better than 'Iv', feel free to change that. #[allow(clippy::upper_case_acronyms)] -pub struct AuthenticationIV(IV); +pub struct IV(CryptoIV); #[derive(Debug)] // I think 'IV' looks better than 'Iv', feel free to change that. @@ -21,9 +21,9 @@ pub enum IVConversionError { StringOfInvalidLengthError, } -impl AuthenticationIV { +impl IV { pub fn new_random(rng: &mut R) -> Self { - AuthenticationIV(random_iv::(rng)) + IV(random_iv::(rng)) } pub fn try_from_bytes(bytes: &[u8]) -> Result { @@ -31,7 +31,7 @@ impl AuthenticationIV { return Err(IVConversionError::BytesOfInvalidLengthError); } - Ok(AuthenticationIV(GenericArray::clone_from_slice(bytes))) + Ok(IV(GenericArray::clone_from_slice(bytes))) } pub fn to_bytes(&self) -> Vec { @@ -42,7 +42,7 @@ impl AuthenticationIV { self.0.as_ref() } - pub fn inner(&self) -> &IV { + pub fn inner(&self) -> &CryptoIV { &self.0 } @@ -56,8 +56,8 @@ impl AuthenticationIV { return Err(IVConversionError::StringOfInvalidLengthError); } - Ok(AuthenticationIV( - GenericArray::from_exact_iter(decoded).expect("Invalid vector length!"), + Ok(IV( + GenericArray::from_exact_iter(decoded).expect("Invalid vector length!") )) } @@ -66,8 +66,8 @@ impl AuthenticationIV { } } -impl From for String { - fn from(iv: AuthenticationIV) -> Self { +impl From for String { + fn from(iv: IV) -> Self { iv.to_base58_string() } } diff --git a/gateway/gateway-requests/src/lib.rs b/gateway/gateway-requests/src/lib.rs index bbfd8abd5f..24018ba219 100644 --- a/gateway/gateway-requests/src/lib.rs +++ b/gateway/gateway-requests/src/lib.rs @@ -1,19 +1,19 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +pub use crypto::generic_array; use crypto::hmac::{hmac::Mac, HmacOutput}; use nymsphinx::params::GatewayIntegrityHmacAlgorithm; +pub use types::*; pub mod authentication; +pub mod iv; pub mod registration; pub mod types; pub const DUMMY_MESSAGE_CONTENT: &[u8] = b"[DUMMY MESSAGE] Wanting something does not give you the right to have it."; -pub use crypto::generic_array; -pub use types::*; - pub type GatewayMac = HmacOutput; // TODO: could using `Mac` trait here for OutputSize backfire? diff --git a/gateway/gateway-requests/src/registration/handshake/client.rs b/gateway/gateway-requests/src/registration/handshake/client.rs index 9848c621c3..784364ebe2 100644 --- a/gateway/gateway-requests/src/registration/handshake/client.rs +++ b/gateway/gateway-requests/src/registration/handshake/client.rs @@ -24,18 +24,11 @@ impl<'a> ClientHandshake<'a> { ws_stream: &'a mut S, identity: &'a crypto::asymmetric::identity::KeyPair, gateway_pubkey: identity::PublicKey, - coconut_credential: coconut_interface::Credential, ) -> Self where S: Stream + Sink + Unpin + Send + 'a, { - let mut state = State::new( - rng, - ws_stream, - identity, - Some(gateway_pubkey), - Some(coconut_credential), - ); + let mut state = State::new(rng, ws_stream, identity, Some(gateway_pubkey)); ClientHandshake { handshake_future: Box::pin(async move { diff --git a/gateway/gateway-requests/src/registration/handshake/error.rs b/gateway/gateway-requests/src/registration/handshake/error.rs index dbb083d0b9..0bf672d2b5 100644 --- a/gateway/gateway-requests/src/registration/handshake/error.rs +++ b/gateway/gateway-requests/src/registration/handshake/error.rs @@ -25,8 +25,6 @@ pub enum HandshakeError { MalformedRequest, #[error("sent request was malformed")] HandshakeFailure, - #[error("could not verify Coconut Credential")] - InvalidCoconutCredential, #[error("could not deserialize from slice: {source}")] DeserializationError { #[from] diff --git a/gateway/gateway-requests/src/registration/handshake/gateway.rs b/gateway/gateway-requests/src/registration/handshake/gateway.rs index bf786f687e..8663f9af5f 100644 --- a/gateway/gateway-requests/src/registration/handshake/gateway.rs +++ b/gateway/gateway-requests/src/registration/handshake/gateway.rs @@ -4,7 +4,6 @@ use crate::registration::handshake::shared_key::SharedKeys; use crate::registration::handshake::state::State; use crate::registration::handshake::{error::HandshakeError, WsItem}; -use coconut_interface::VerificationKey; use crypto::asymmetric::encryption; use futures::future::BoxFuture; use futures::task::{Context, Poll}; @@ -23,12 +22,11 @@ impl<'a> GatewayHandshake<'a> { ws_stream: &'a mut S, identity: &'a crypto::asymmetric::identity::KeyPair, received_init_payload: Vec, - verification_key: &'a VerificationKey, ) -> Self where S: Stream + Sink + Unpin + Send + 'a, { - let mut state = State::new(rng, ws_stream, identity, None, None); + let mut state = State::new(rng, ws_stream, identity, None); GatewayHandshake { handshake_future: Box::pin(async move { // If any step along the way failed (that are non-network related), @@ -50,27 +48,13 @@ impl<'a> GatewayHandshake<'a> { } } - // init: <- pub_key || g^x || credential + // init: <- pub_key || g^x let init_message = check_processing_error( State::::parse_init_message(received_init_payload), &mut state, ) .await?; - let credential = init_message.credential(); - - check_processing_error( - { - if !credential.verify(verification_key).await { - Err(HandshakeError::InvalidCoconutCredential) - } else { - Ok(()) - } - }, - &mut state, - ) - .await?; - let remote_identity = init_message.local_id_pubkey(); let remote_ephemeral_key = init_message.ephemeral_key(); state.update_remote_identity(remote_identity); diff --git a/gateway/gateway-requests/src/registration/handshake/mod.rs b/gateway/gateway-requests/src/registration/handshake/mod.rs index 715b0dedba..0b3b95bf7f 100644 --- a/gateway/gateway-requests/src/registration/handshake/mod.rs +++ b/gateway/gateway-requests/src/registration/handshake/mod.rs @@ -6,8 +6,6 @@ use self::error::HandshakeError; #[cfg(not(target_arch = "wasm32"))] use self::gateway::GatewayHandshake; pub use self::shared_key::{SharedKeySize, SharedKeys}; -#[cfg(not(target_arch = "wasm32"))] -use coconut_interface::VerificationKey; use crypto::asymmetric::identity; use futures::{Sink, Stream}; use rand::{CryptoRng, RngCore}; @@ -32,12 +30,11 @@ pub async fn client_handshake<'a, S>( ws_stream: &'a mut S, identity: &'a identity::KeyPair, gateway_pubkey: identity::PublicKey, - coconut_credential: coconut_interface::Credential, ) -> Result where S: Stream + Sink + Unpin + Send + 'a, { - ClientHandshake::new(rng, ws_stream, identity, gateway_pubkey, coconut_credential).await + ClientHandshake::new(rng, ws_stream, identity, gateway_pubkey).await } #[cfg(not(target_arch = "wasm32"))] @@ -46,19 +43,11 @@ pub async fn gateway_handshake<'a, S>( ws_stream: &'a mut S, identity: &'a identity::KeyPair, received_init_payload: Vec, - verification_key: &VerificationKey, ) -> Result where S: Stream + Sink + Unpin + Send + 'a, { - GatewayHandshake::new( - rng, - ws_stream, - identity, - received_init_payload, - verification_key, - ) - .await + GatewayHandshake::new(rng, ws_stream, identity, received_init_payload).await } /* diff --git a/gateway/gateway-requests/src/registration/handshake/shared_key.rs b/gateway/gateway-requests/src/registration/handshake/shared_key.rs index 7fc655ee19..b07c6827d7 100644 --- a/gateway/gateway-requests/src/registration/handshake/shared_key.rs +++ b/gateway/gateway-requests/src/registration/handshake/shared_key.rs @@ -1,11 +1,12 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::{GatewayMacSize, GatewayRequestsError}; use crypto::generic_array::{ typenum::{Sum, Unsigned, U16}, GenericArray, }; -use crypto::hmac::compute_keyed_hmac; +use crypto::hmac::{compute_keyed_hmac, recompute_keyed_hmac_and_verify_tag}; use crypto::symmetric::stream_cipher::{self, Key, NewStreamCipher, IV}; use nymsphinx::params::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorithm}; use pemstore::traits::PemStorableKey; @@ -102,6 +103,41 @@ impl SharedKeys { .collect() } + pub fn decrypt_tagged( + &self, + enc_data: &[u8], + iv: Option<&IV>, + ) -> Result, GatewayRequestsError> { + let mac_size = GatewayMacSize::to_usize(); + if enc_data.len() < mac_size { + return Err(GatewayRequestsError::TooShortRequest); + } + + let mac_tag = &enc_data[..mac_size]; + let message_bytes = &enc_data[mac_size..]; + + if !recompute_keyed_hmac_and_verify_tag::( + self.mac_key(), + message_bytes, + mac_tag, + ) { + return Err(GatewayRequestsError::InvalidMac); + } + + // couldn't have made the first borrow mutable as you can't have an immutable borrow + // together with a mutable one + let mut message_bytes_mut = &mut enc_data.to_vec()[mac_size..]; + + let zero_iv = stream_cipher::zero_iv::(); + let iv = iv.unwrap_or(&zero_iv); + stream_cipher::decrypt_in_place::( + self.encryption_key(), + iv, + &mut message_bytes_mut, + ); + Ok(message_bytes_mut.to_vec()) + } + pub fn encryption_key(&self) -> &Key { &self.encryption_key } diff --git a/gateway/gateway-requests/src/registration/handshake/state.rs b/gateway/gateway-requests/src/registration/handshake/state.rs index 97e2ae56e6..9b45b89292 100644 --- a/gateway/gateway-requests/src/registration/handshake/state.rs +++ b/gateway/gateway-requests/src/registration/handshake/state.rs @@ -5,7 +5,6 @@ use crate::registration::handshake::error::HandshakeError; use crate::registration::handshake::shared_key::{SharedKeySize, SharedKeys}; use crate::registration::handshake::WsItem; use crate::types; -use coconut_interface::Credential; use crypto::{ asymmetric::{encryption, identity}, generic_array::typenum::Unsigned, @@ -24,19 +23,13 @@ use tungstenite::Message as WsMessage; pub struct InitMessage { local_id_pubkey: [u8; identity::PUBLIC_KEY_LENGTH], ephemeral_key: [u8; identity::PUBLIC_KEY_LENGTH], - credential: Option, } impl InitMessage { - fn new( - local_id_pubkey: &identity::PublicKey, - ephemeral_key: &encryption::PublicKey, - credential: Credential, - ) -> Self { + fn new(local_id_pubkey: &identity::PublicKey, ephemeral_key: &encryption::PublicKey) -> Self { InitMessage { local_id_pubkey: local_id_pubkey.to_bytes(), ephemeral_key: ephemeral_key.to_bytes(), - credential: Some(credential), } } @@ -50,11 +43,6 @@ impl InitMessage { encryption::PublicKey::from_bytes(&self.ephemeral_key).unwrap() } - #[cfg(not(target_arch = "wasm32"))] - pub fn credential(&self) -> Credential { - self.credential.clone().unwrap() - } - fn to_bytes(&self) -> Vec { bincode::serialize(self).unwrap() } @@ -85,7 +73,6 @@ pub(crate) struct State<'a, S> { /// The known or received public identity key of the remote. /// Ideally it would always be known before the handshake was initiated. remote_pubkey: Option, - coconut_credential: Option, } impl<'a, S> State<'a, S> { @@ -94,7 +81,6 @@ impl<'a, S> State<'a, S> { ws_stream: &'a mut S, identity: &'a identity::KeyPair, remote_pubkey: Option, - credential: Option, ) -> Self { let ephemeral_keypair = encryption::KeyPair::new(rng); State { @@ -103,7 +89,6 @@ impl<'a, S> State<'a, S> { identity, remote_pubkey, derived_shared_keys: None, - coconut_credential: credential, } } @@ -119,7 +104,6 @@ impl<'a, S> State<'a, S> { InitMessage::new( self.identity.public_key(), self.ephemeral_keypair.public_key(), - self.coconut_credential.clone().unwrap(), ) .to_bytes() } diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 1c751783e9..3ada132538 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::authentication::encrypted_address::EncryptedAddressBytes; -use crate::authentication::iv::AuthenticationIV; +use crate::iv::IV; use crate::registration::handshake::SharedKeys; use crate::GatewayMacSize; +use coconut_interface::Credential; use crypto::generic_array::typenum::Unsigned; use crypto::hmac::recompute_keyed_hmac_and_verify_tag; use crypto::symmetric::stream_cipher; @@ -112,13 +113,17 @@ pub enum ClientControlRequest { }, #[serde(alias = "handshakePayload")] RegisterHandshakeInitRequest { data: Vec }, + BandwidthCredential { + enc_credential: Vec, + iv: Vec, + }, } impl ClientControlRequest { pub fn new_authenticate( address: DestinationAddressBytes, enc_address: EncryptedAddressBytes, - iv: AuthenticationIV, + iv: IV, ) -> Self { ClientControlRequest::Authenticate { address: address.as_base58_string(), @@ -126,6 +131,34 @@ impl ClientControlRequest { iv: iv.to_base58_string(), } } + + pub fn new_enc_bandwidth_credential( + credential: &Credential, + shared_key: &SharedKeys, + iv: IV, + ) -> Option { + match bincode::serialize(credential) { + Ok(serialized_credential) => { + let enc_credential = + shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner())); + + Some(ClientControlRequest::BandwidthCredential { + enc_credential, + iv: iv.to_bytes(), + }) + } + _ => None, + } + } + + pub fn try_from_enc_bandwidth_credential( + enc_credential: Vec, + shared_key: &SharedKeys, + iv: IV, + ) -> Result { + let credential = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?; + bincode::deserialize(&credential).map_err(|_| GatewayRequestsError::MalformedEncryption) + } } impl From for Message { @@ -158,6 +191,8 @@ impl TryInto for ClientControlRequest { pub enum ServerResponse { Authenticate { status: bool }, Register { status: bool }, + // Maybe we could return the remaining bandwidth? + Bandwidth { status: bool }, Send { status: bool }, Error { message: String }, } @@ -210,39 +245,14 @@ pub enum BinaryRequest { // would work there. impl BinaryRequest { pub fn try_from_encrypted_tagged_bytes( - mut raw_req: Vec, + raw_req: Vec, shared_keys: &SharedKeys, ) -> Result { - let mac_size = GatewayMacSize::to_usize(); - if raw_req.len() < mac_size { - return Err(GatewayRequestsError::TooShortRequest); - } - - let mac_tag = &raw_req[..mac_size]; - let message_bytes = &raw_req[mac_size..]; - - if !recompute_keyed_hmac_and_verify_tag::( - shared_keys.mac_key(), - message_bytes, - mac_tag, - ) { - return Err(GatewayRequestsError::InvalidMac); - } - - // couldn't have made the first borrow mutable as you can't have an immutable borrow - // together with a mutable one - let mut message_bytes_mut = &mut raw_req[mac_size..]; - - let zero_iv = stream_cipher::zero_iv::(); - stream_cipher::decrypt_in_place::( - shared_keys.encryption_key(), - &zero_iv, - &mut message_bytes_mut, - ); + let message_bytes = &shared_keys.decrypt_tagged(&raw_req, None)?; // right now there's only a single option possible which significantly simplifies the logic // if we decided to allow for more 'binary' messages, the API wouldn't need to change. - let mix_packet = MixPacket::try_from_bytes(message_bytes_mut)?; + let mix_packet = MixPacket::try_from_bytes(message_bytes)?; Ok(BinaryRequest::ForwardSphinx(mix_packet)) } diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs new file mode 100644 index 0000000000..203b75fd0a --- /dev/null +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -0,0 +1,85 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; +use std::convert::TryFrom; +use std::sync::Arc; +use tokio::sync::RwLock; + +use coconut_interface::Credential; +use credentials::error::Error; +use nymsphinx::DestinationAddressBytes; + +const BANDWIDTH_INDEX: usize = 0; + +pub type BandwidthDatabase = Arc>>; + +pub fn empty_bandwidth_database() -> BandwidthDatabase { + Arc::new(RwLock::new(HashMap::new())) +} + +pub struct Bandwidth { + value: u64, +} + +impl Bandwidth { + pub fn value(&self) -> u64 { + self.value + } + + pub async fn consume_bandwidth( + bandwidths: &BandwidthDatabase, + remote_address: &DestinationAddressBytes, + consumed: u64, + ) -> Result<(), Error> { + if let Some(bandwidth) = bandwidths.write().await.get_mut(remote_address) { + if let Some(res) = bandwidth.checked_sub(consumed) { + *bandwidth = res; + Ok(()) + } else { + Err(Error::BandwidthOverflow(String::from( + "Allocate more bandwidth for consumption", + ))) + } + } else { + Err(Error::MissingBandwidth) + } + } + + pub async fn increase_bandwidth( + bandwidths: &BandwidthDatabase, + remote_address: &DestinationAddressBytes, + increase: u64, + ) -> Result<(), Error> { + let mut db = bandwidths.write().await; + if let Some(bandwidth) = db.get_mut(remote_address) { + if let Some(new_bandwidth) = bandwidth.checked_add(increase) { + *bandwidth = new_bandwidth; + } else { + return Err(Error::BandwidthOverflow(String::from( + "Use some of the already allocated bandwidth", + ))); + } + } else { + db.insert(*remote_address, increase); + } + Ok(()) + } +} + +impl TryFrom for Bandwidth { + type Error = Error; + + fn try_from(credential: Credential) -> Result { + match credential.public_attributes().get(BANDWIDTH_INDEX) { + None => Err(Error::NotEnoughPublicAttributes), + Some(attr) => match <[u8; 8]>::try_from(attr.as_slice()) { + Ok(bandwidth_bytes) => { + let value = u64::from_be_bytes(bandwidth_bytes); + Ok(Self { value }) + } + Err(_) => Err(Error::InvalidBandwidthSize), + }, + } + } +} diff --git a/gateway/src/node/client_handling/clients_handler.rs b/gateway/src/node/client_handling/clients_handler.rs index 110f056f30..2051d9b501 100644 --- a/gateway/src/node/client_handling/clients_handler.rs +++ b/gateway/src/node/client_handling/clients_handler.rs @@ -10,7 +10,7 @@ use futures::{ StreamExt, }; use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; -use gateway_requests::authentication::iv::AuthenticationIV; +use gateway_requests::iv::IV; use gateway_requests::registration::handshake::SharedKeys; use log::*; use nymsphinx::DestinationAddressBytes; @@ -34,7 +34,7 @@ pub(crate) enum ClientsHandlerRequest { Authenticate( DestinationAddressBytes, EncryptedAddressBytes, - AuthenticationIV, + IV, MixMessageSender, ClientsHandlerResponseSender, ), @@ -193,7 +193,7 @@ impl ClientsHandler { &mut self, address: DestinationAddressBytes, encrypted_address: EncryptedAddressBytes, - iv: AuthenticationIV, + iv: IV, comm_channel: MixMessageSender, res_channel: ClientsHandlerResponseSender, ) { diff --git a/gateway/src/node/client_handling/mod.rs b/gateway/src/node/client_handling/mod.rs index 9ad982e49b..8e8013fb93 100644 --- a/gateway/src/node/client_handling/mod.rs +++ b/gateway/src/node/client_handling/mod.rs @@ -1,5 +1,6 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +mod bandwidth; pub(crate) mod clients_handler; pub(crate) mod websocket; diff --git a/gateway/src/node/client_handling/websocket/connection_handler.rs b/gateway/src/node/client_handling/websocket/connection_handler.rs index 82c459fe4a..dc81c5b1dd 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler.rs @@ -1,6 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::node::client_handling::bandwidth::{Bandwidth, BandwidthDatabase}; use crate::node::client_handling::clients_handler::{ ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, }; @@ -14,7 +15,7 @@ use futures::{ SinkExt, StreamExt, }; use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; -use gateway_requests::authentication::iv::AuthenticationIV; +use gateway_requests::iv::IV; use gateway_requests::registration::handshake::error::HandshakeError; use gateway_requests::registration::handshake::{gateway_handshake, SharedKeys}; use gateway_requests::types::{BinaryRequest, ClientControlRequest, ServerResponse}; @@ -24,6 +25,7 @@ use mixnet_client::forwarder::MixForwardingSender; use nymsphinx::DestinationAddressBytes; use rand::{CryptoRng, Rng}; use std::convert::TryFrom; +use std::mem; use std::sync::Arc; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tungstenite::{ @@ -59,6 +61,7 @@ pub(crate) struct Handle { local_identity: Arc, aggregated_verification_key: VerificationKey, + bandwidths: BandwidthDatabase, } impl Handle @@ -74,6 +77,7 @@ where outbound_mix_sender: MixForwardingSender, local_identity: Arc, aggregated_verification_key: VerificationKey, + bandwidths: BandwidthDatabase, ) -> Self { Handle { rng, @@ -84,6 +88,7 @@ where socket_connection: SocketStream::RawTcp(conn), local_identity, aggregated_verification_key, + bandwidths, } } @@ -119,7 +124,6 @@ where ws_stream, self.local_identity.as_ref(), init_msg, - &self.aggregated_verification_key, ) .await } @@ -205,8 +209,19 @@ where Ok(request) => match request { // currently only a single type exists BinaryRequest::ForwardSphinx(mix_packet) => { - self.outbound_mix_sender.unbounded_send(mix_packet).unwrap(); - ServerResponse::Send { status: true } + let consumed_bandwidth = mem::size_of_val(&mix_packet) as u64; + if let Err(e) = Bandwidth::consume_bandwidth( + &self.bandwidths, + &self.remote_address.unwrap(), + consumed_bandwidth, + ) + .await + { + ServerResponse::new_error(format!("{:?}", e)) + } else { + self.outbound_mix_sender.unbounded_send(mix_packet).unwrap(); + ServerResponse::Send { status: true } + } } }, } @@ -236,7 +251,7 @@ where } }; - let iv = match AuthenticationIV::try_from_base58_string(iv) { + let iv = match IV::try_from_base58_string(iv) { Ok(iv) => iv, Err(e) => { trace!("failed to parse received IV {:?}", e); @@ -342,12 +357,68 @@ where } } - // currently there are no valid control messages you can send after authentication - async fn handle_text(&mut self, _: String) -> Message { - trace!("Handling text message (presumably control message)"); + async fn handle_bandwidth(&mut self, enc_credential: Vec, iv: Vec) -> ServerResponse { + if self.shared_key.is_none() { + return ServerResponse::new_error("No shared key has been exchanged with the gateway"); + } + if self.remote_address.is_none() { + return ServerResponse::new_error("No remote address has been set"); + } + let iv = match IV::try_from_bytes(&iv) { + Ok(iv) => iv, + Err(e) => { + trace!("failed to parse received IV {:?}", e); + return ServerResponse::new_error("malformed iv"); + } + }; + let credential = match ClientControlRequest::try_from_enc_bandwidth_credential( + enc_credential, + self.shared_key.as_ref().unwrap(), + iv, + ) { + Ok(c) => c, + Err(e) => { + return ServerResponse::new_error(e.to_string()); + } + }; + if credential.verify(&self.aggregated_verification_key) { + match Bandwidth::try_from(credential) { + Ok(bandwidth) => { + if let Err(e) = Bandwidth::increase_bandwidth( + &self.bandwidths, + &self.remote_address.unwrap(), + bandwidth.value(), + ) + .await + { + return ServerResponse::Error { + message: format!("{:?}", e), + }; + } + ServerResponse::Bandwidth { status: true } + } + Err(e) => ServerResponse::Error { + message: format!("{:?}", e), + }, + } + } else { + ServerResponse::Bandwidth { status: false } + } + } - error!("Currently there are no text messages besides 'Authenticate' and 'Register' and they were already dealt with!"); - ServerResponse::new_error("invalid request").into() + // currently the bandwidth credential request is the only one we can receive after + // authentication + async fn handle_text(&mut self, raw_request: String) -> Message { + if let Ok(request) = ClientControlRequest::try_from(raw_request) { + match request { + ClientControlRequest::BandwidthCredential { enc_credential, iv } => { + self.handle_bandwidth(enc_credential, iv).await.into() + } + _ => ServerResponse::new_error("invalid request").into(), + } + } else { + ServerResponse::new_error("malformed request").into() + } } async fn handle_request(&mut self, raw_request: Message) -> Option { @@ -384,6 +455,7 @@ where ClientControlRequest::RegisterHandshakeInitRequest { data } => { self.handle_register(data, mix_sender).await } + _ => ServerResponse::new_error("invalid request"), } } else { // TODO: is this a malformed request or rather a network error and diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index 7978e77a93..fa1c26aaa6 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -1,6 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::node::client_handling::bandwidth::empty_bandwidth_database; use crate::node::client_handling::clients_handler::ClientsHandlerRequestSender; use crate::node::client_handling::websocket::connection_handler::Handle; use coconut_interface::VerificationKey; @@ -46,6 +47,8 @@ impl Listener { } }; + let bandwidths = empty_bandwidth_database(); + loop { match tcp_listener.accept().await { Ok((socket, remote_addr)) => { @@ -59,6 +62,7 @@ impl Listener { outbound_mix_sender.clone(), Arc::clone(&self.local_identity), self.aggregated_verification_key.clone(), + Arc::clone(&bandwidths), ); tokio::spawn(async move { handle.start_handling().await }); } diff --git a/gateway/src/node/client_handling/websocket/mod.rs b/gateway/src/node/client_handling/websocket/mod.rs index b9920c505f..df45a32a69 100644 --- a/gateway/src/node/client_handling/websocket/mod.rs +++ b/gateway/src/node/client_handling/websocket/mod.rs @@ -1,8 +1,8 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +pub(crate) use listener::Listener; + pub(crate) mod connection_handler; pub(crate) mod listener; pub(crate) mod message_receiver; - -pub(crate) use listener::Listener; diff --git a/gateway/src/node/storage/ledger.rs b/gateway/src/node/storage/ledger.rs index e5d6c5aec2..ba69835886 100644 --- a/gateway/src/node/storage/ledger.rs +++ b/gateway/src/node/storage/ledger.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; -use gateway_requests::authentication::iv::AuthenticationIV; use gateway_requests::generic_array::typenum::Unsigned; +use gateway_requests::iv::IV; use gateway_requests::registration::handshake::{SharedKeySize, SharedKeys}; use log::*; use nymsphinx::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; @@ -79,7 +79,7 @@ impl ClientLedger { &self, client_address: &DestinationAddressBytes, encrypted_address: &EncryptedAddressBytes, - iv: &AuthenticationIV, + iv: &IV, ) -> Result { match self.db.get(client_address.as_bytes_ref()) { Err(e) => Err(ClientLedgerError::Read(e)), diff --git a/tauri-wallet/Cargo.lock b/tauri-wallet/Cargo.lock index 03cb42d2af..3a1460e857 100644 --- a/tauri-wallet/Cargo.lock +++ b/tauri-wallet/Cargo.lock @@ -283,6 +283,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397" dependencies = [ + "digest 0.9.0", "ff", "group", "pairing", @@ -484,13 +485,12 @@ dependencies = [ "coconut-rs", "getset", "serde", - "sha2", ] [[package]] name = "coconut-rs" -version = "0.4.0" -source = "git+https://github.com/nymtech/coconut.git?branch=0.4.0#183cff805aa73a908b681ac3fcdff7d084c4f42b" +version = "0.5.0" +source = "git+https://github.com/nymtech/coconut.git?branch=0.5.0#a1b72d51aa2a67b73b9f58d707030ae6dc70af7f" dependencies = [ "bls12_381", "bs58", @@ -503,8 +503,6 @@ dependencies = [ "serde", "serde_derive", "sha2", - "sha3", - "subtle", "thiserror", ] @@ -653,8 +651,9 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" -version = "0.6.0" -source = "git+https://github.com/cosmos/cosmos-rust/?rev=ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30#ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7115eae4b8518967b8e737a3ef76025f2d007de7906d88c18f00b8bbfc70f51e" dependencies = [ "prost", "prost-types", @@ -662,9 +661,10 @@ dependencies = [ ] [[package]] -name = "cosmos_sdk" -version = "0.2.0" -source = "git+https://github.com/cosmos/cosmos-rust/?rev=ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30#ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30" +name = "cosmrs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "609eba7eee6d9929927cbdf15f9fe96d250c316c5e1b983c4a47a10f60da5ea7" dependencies = [ "bip32", "cosmos-sdk-proto", @@ -2563,7 +2563,7 @@ dependencies = [ "bip39", "coconut-interface", "config", - "cosmos_sdk", + "cosmrs", "cosmwasm-std", "credentials", "dirs", @@ -4793,7 +4793,7 @@ dependencies = [ "bip39", "coconut-interface", "config", - "cosmos_sdk", + "cosmrs", "cosmwasm-std", "flate2", "itertools 0.10.1", diff --git a/tauri-wallet/src-tauri/Cargo.toml b/tauri-wallet/src-tauri/Cargo.toml index 2de6949374..9737017ee4 100644 --- a/tauri-wallet/src-tauri/Cargo.toml +++ b/tauri-wallet/src-tauri/Cargo.toml @@ -28,11 +28,7 @@ ts-rs = "3.0" url = "2.0" rand = "0.6.5" -cosmos_sdk = { git = "https://github.com/cosmos/cosmos-rust/", rev = "ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30", features = [ - "rpc", - "bip32", - "cosmwasm", -] } +cosmrs = { version = "0.1", features = ["rpc", "bip32", "cosmwasm"] } cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch = "0.14.1-updatedk256" } validator-client = { path = "../../common/client-libs/validator-client", features = [ diff --git a/tauri-wallet/src-tauri/src/coconut.rs b/tauri-wallet/src-tauri/src/coconut.rs deleted file mode 100644 index 554186f980..0000000000 --- a/tauri-wallet/src-tauri/src/coconut.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::state::State; -use coconut_interface::{self, Credential, Signature, Theta, VerificationKey}; -use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key}; -use std::sync::Arc; -use tokio::sync::RwLock; -use url::Url; - -#[tauri::command] -pub async fn randomise_credential( - idx: usize, - state: tauri::State<'_, Arc>>, -) -> Result, String> { - let mut state = state.write().await; - let signature = state.signatures.remove(idx); - let new = signature.randomise(state.params()?); - state.signatures.insert(idx, new); - Ok(state.signatures.clone()) -} - -#[tauri::command] -pub async fn delete_credential( - idx: usize, - state: tauri::State<'_, Arc>>, -) -> Result, String> { - let mut state = state.write().await; - state.signatures.remove(idx); - Ok(state.signatures.clone()) -} - -#[tauri::command] -pub async fn list_credentials( - state: tauri::State<'_, Arc>>, -) -> Result, String> { - let state = state.read().await; - Ok(state.signatures.clone()) -} - -#[tauri::command] -pub async fn verify_credential( - idx: usize, - validator_urls: Vec, - state: tauri::State<'_, Arc>>, -) -> Result { - // the API needs to be improved but at least it should compile (in theory) - let verification_key = - get_aggregated_verification_key(validator_urls.clone(), state.clone()).await?; - let theta = prove_credential(idx, validator_urls, state.clone()).await?; - - let state = state.read().await; - - let credential = Credential::new( - state.n_attributes(), - theta, - &state.public_attributes(), - state - .signatures - .get(idx) - .ok_or("Got invalid signature idx")?, - ); - - Ok(credential.verify(&verification_key).await) -} - -async fn prove_credential( - idx: usize, - validator_urls: Vec, - state: tauri::State<'_, Arc>>, -) -> Result { - let verification_key = get_aggregated_verification_key(validator_urls, state.clone()).await?; - let state = state.read().await; - - if let Some(signature) = state.signatures.get(idx) { - match coconut_interface::prove_credential( - state.params()?, - &verification_key, - signature, - &state.private_attributes(), - ) { - Ok(theta) => Ok(theta), - Err(e) => Err(format!("{}", e)), - } - } else { - Err("Got invalid Signature idx".to_string()) - } -} - -async fn get_aggregated_verification_key( - validator_urls: Vec, - state: tauri::State<'_, Arc>>, -) -> Result { - if let Some(verification_key) = &state.read().await.aggregated_verification_key { - return Ok(verification_key.clone()); - } - - let parsed_urls = parse_url_validators(&validator_urls)?; - let key = obtain_aggregate_verification_key(&parsed_urls) - .await - .map_err(|err| format!("failed to obtain aggregate verification key - {:?}", err))?; - - state - .write() - .await - .aggregated_verification_key - .replace(key.clone()); - - Ok(key) -} - -fn parse_url_validators(raw: &[String]) -> Result, String> { - let mut parsed_urls = Vec::with_capacity(raw.len()); - for url in raw { - let parsed_url: Url = url - .parse() - .map_err(|err| format!("one of validator urls is malformed - {}", err))?; - parsed_urls.push(parsed_url) - } - Ok(parsed_urls) -} - -#[tauri::command] -pub async fn get_credential( - validator_urls: Vec, - state: tauri::State<'_, Arc>>, -) -> Result, String> { - let guard = state.read().await; - let parsed_urls = parse_url_validators(&validator_urls)?; - - let signature = obtain_aggregate_signature( - guard.params()?, - &guard.public_attributes(), - &guard.private_attributes(), - &parsed_urls, - ) - .await - .map_err(|err| format!("failed to obtain aggregate signature - {:?}", err))?; - - let mut state = state.write().await; - state.signatures.push(signature); - Ok(state.signatures.clone()) -} diff --git a/tauri-wallet/src-tauri/src/coin.rs b/tauri-wallet/src-tauri/src/coin.rs index e77ae07da4..11a365f6b8 100644 --- a/tauri-wallet/src-tauri/src/coin.rs +++ b/tauri-wallet/src-tauri/src/coin.rs @@ -1,9 +1,8 @@ // This should be moved out of the wallet, and used as a primary coin type throughout the codebase use ::config::defaults::DENOM; -use cosmos_sdk::Coin as CosmosCoin; -use cosmos_sdk::Decimal; -use cosmos_sdk::Denom as CosmosDenom; +use cosmrs::Decimal; +use cosmrs::Denom as CosmosDenom; use cosmwasm_std::Coin as CosmWasmCoin; use cosmwasm_std::Uint128; use serde::{Deserialize, Serialize}; @@ -12,7 +11,7 @@ use std::fmt; use std::ops::{Add, Sub}; use std::str::FromStr; use ts_rs::TS; -use validator_client::nymd::GasPrice; +use validator_client::nymd::{GasPrice, CosmosCoin}; use crate::format_err; @@ -214,9 +213,9 @@ impl From for Coin { #[cfg(test)] mod test { use crate::{Coin, Denom}; - use cosmos_sdk::Coin as CosmosCoin; - use cosmos_sdk::Decimal; - use cosmos_sdk::Denom as CosmosDenom; + use cosmrs::Coin as CosmosCoin; + use cosmrs::Decimal; + use cosmrs::Denom as CosmosDenom; use cosmwasm_std::Coin as CosmWasmCoin; use serde_json::json; use std::convert::{TryFrom, TryInto}; diff --git a/tauri-wallet/src-tauri/src/main.rs b/tauri-wallet/src-tauri/src/main.rs index 5e85ff5c16..d1b201d33c 100644 --- a/tauri-wallet/src-tauri/src/main.rs +++ b/tauri-wallet/src-tauri/src/main.rs @@ -4,8 +4,6 @@ )] use bip39::{Language, Mnemonic}; -use cosmos_sdk::AccountId; -use cosmos_sdk::Coin as CosmosCoin; use cosmwasm_std::Coin as CosmWasmCoin; use error::BackendError; use mixnet_contract::{Gateway, MixNode}; @@ -18,17 +16,13 @@ use tendermint_rpc::endpoint::broadcast::tx_commit::Response; use tokio::sync::RwLock; use ts_rs::{export, TS}; use validator_client::nymd::fee_helpers::Operation; -use validator_client::nymd::{NymdClient, SigningNymdClient}; +use validator_client::nymd::{NymdClient, SigningNymdClient, AccountId, CosmosCoin}; -mod coconut; mod coin; mod config; mod error; mod state; -use crate::coconut::{ - delete_credential, get_credential, list_credentials, randomise_credential, verify_credential, -}; use crate::state::State; use crate::coin::{Coin, Denom}; @@ -400,11 +394,6 @@ fn main() { delegate_to_gateway, undelegate_from_gateway, send, - get_credential, - randomise_credential, - delete_credential, - list_credentials, - verify_credential, create_new_account, get_fee ]) diff --git a/tauri-wallet/src-tauri/src/state.rs b/tauri-wallet/src-tauri/src/state.rs index 501e469cac..40b21e9106 100644 --- a/tauri-wallet/src-tauri/src/state.rs +++ b/tauri-wallet/src-tauri/src/state.rs @@ -1,19 +1,10 @@ use crate::config::Config; -use crate::format_err; -use coconut_interface::{self, Attribute, Parameters, Signature, VerificationKey}; use validator_client::nymd::{NymdClient, SigningNymdClient}; #[derive(Default)] pub struct State { config: Config, signing_client: Option>, - // Coconut stuff - pub signatures: Vec, - n_attributes: u32, - params: Option, - public_attributes: Vec, - private_attributes: Vec, - pub aggregated_verification_key: Option, } impl State { @@ -30,23 +21,4 @@ impl State { pub fn set_client(&mut self, signing_client: NymdClient) { self.signing_client = Some(signing_client) } - - pub fn params(&self) -> Result<&Parameters, String> { - self - .params - .as_ref() - .ok_or_else(|| format_err!("Parameters are not set!")) - } - - pub fn private_attributes(&self) -> Vec { - self.private_attributes.clone() - } - - pub fn public_attributes(&self) -> Vec { - self.public_attributes.clone() - } - - pub fn n_attributes(&self) -> u32 { - self.n_attributes - } } diff --git a/validator-api/migrations/20210819120000_monitor_runs.sql b/validator-api/migrations/20210819120000_monitor_runs.sql new file mode 100644 index 0000000000..0b6561862a --- /dev/null +++ b/validator-api/migrations/20210819120000_monitor_runs.sql @@ -0,0 +1,7 @@ +-- keeping track of all monitor runs that have happened will help to +-- solve an issue of mixnode being online only for a single check and yet being assigned 100% uptime +CREATE TABLE monitor_run +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + timestamp INTEGER NOT NULL +) \ No newline at end of file diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index b3601de13f..ef765dc209 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -7,7 +7,7 @@ extern crate rocket; use crate::cache::ValidatorCacheRefresher; use crate::config::Config; use crate::network_monitor::tested_network::good_topology::parse_topology_file; -use crate::network_monitor::{new_monitor_runnables, NetworkMonitorRunnables}; +use crate::network_monitor::NetworkMonitorBuilder; use crate::nymd_client::Client; use crate::storage::NodeStatusStorage; use ::config::{defaults::DEFAULT_VALIDATOR_API_PORT, NymConfig}; @@ -16,10 +16,13 @@ use cache::ValidatorCache; use clap::{App, Arg, ArgMatches}; use coconut::InternalSignRequest; use log::info; +use rocket::fairing::AdHoc; use rocket::http::Method; use rocket::{Ignite, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; use std::process; +use std::sync::Arc; +use tokio::sync::Notify; use url::Url; pub(crate) mod cache; @@ -79,6 +82,12 @@ fn parse_args<'a>() -> ArgMatches<'a> { .long(NYMD_VALIDATOR_ARG) .takes_value(true) ) + .arg( + Arg::with_name(API_VALIDATORS_ARG) + .help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered") + .long(API_VALIDATORS_ARG) + .takes_value(true) + ) .arg(Arg::with_name(MIXNET_CONTRACT_ARG) .long(MIXNET_CONTRACT_ARG) .help("Address of the validator contract managing the network") @@ -146,7 +155,7 @@ fn override_config(mut config: Config, matches: &ArgMatches) -> Config { config = config.with_v4_good_topology(v4_topology_path) } - if let Some(v6_topology_path) = matches.value_of(V4_TOPOLOGY_ARG) { + if let Some(v6_topology_path) = matches.value_of(V6_TOPOLOGY_ARG) { config = config.with_v6_good_topology(v6_topology_path) } @@ -214,10 +223,16 @@ fn setup_cors() -> Result { Ok(cors) } -async fn setup_network_monitor( - config: &Config, +fn setup_liftoff_notify(notify: Arc) -> AdHoc { + AdHoc::on_liftoff("Liftoff notifier", |_| { + Box::pin(async move { notify.notify_one() }) + }) +} + +fn setup_network_monitor<'a>( + config: &'a Config, rocket: &Rocket, -) -> Option { +) -> Option> { if !config.get_network_monitor_enabled() { return None; } @@ -230,19 +245,16 @@ async fn setup_network_monitor( let v6_topology = parse_topology_file(config.get_v6_good_topology_file()); network_monitor::check_if_up_to_date(&v4_topology, &v6_topology); - Some( - new_monitor_runnables( - config, - v4_topology, - v6_topology, - node_status_storage, - validator_cache, - ) - .await, - ) + Some(NetworkMonitorBuilder::new( + config, + v4_topology, + v6_topology, + node_status_storage, + validator_cache, + )) } -async fn setup_rocket(config: &Config) -> Result> { +async fn setup_rocket(config: &Config, liftoff_notify: Arc) -> Result> { // let's build our rocket! let rocket_config = rocket::config::Config { // TODO: probably the port should be configurable? @@ -251,6 +263,7 @@ async fn setup_rocket(config: &Config) -> Result> { }; let rocket = rocket::custom(rocket_config) .attach(setup_cors()?) + .attach(setup_liftoff_notify(liftoff_notify)) .attach(ValidatorCache::stage()) .attach(InternalSignRequest::stage(config.keypair())); @@ -290,10 +303,11 @@ async fn main() -> Result<()> { let matches = parse_args(); let config = override_config(config, &matches); + let liftoff_notify = Arc::new(Notify::new()); // let's build our rocket! - let rocket = setup_rocket(&config).await?; - let monitor_runnables = setup_network_monitor(&config, &rocket).await; + let rocket = setup_rocket(&config, Arc::clone(&liftoff_notify)).await?; + let monitor_builder = setup_network_monitor(&config, &rocket); let validator_cache = rocket.state::().unwrap().clone(); @@ -321,19 +335,24 @@ async fn main() -> Result<()> { tokio::spawn(async move { validator_cache_refresher.run().await }); } - if let Some(runnables) = monitor_runnables { + // launch the rocket! + let shutdown_handle = rocket.shutdown(); + tokio::spawn(rocket.launch()); + + // to finish building our monitor, we need to have rocket up and running so that we could + // obtain our bandwidth credential + if let Some(monitor_builder) = monitor_builder { info!("Starting network monitor..."); - // spawn network monitor! + // wait for rocket's liftoff stage + liftoff_notify.notified().await; + + // we're ready to go! spawn the network monitor! + let runnables = monitor_builder.build().await; runnables.spawn_tasks(); } else { info!("Network monitoring is disabled."); } - // and launch the rocket - let shutdown_handle = rocket.shutdown(); - - tokio::spawn(rocket.launch()); - wait_for_interrupt().await; shutdown_handle.notify(); diff --git a/validator-api/src/network_monitor/mod.rs b/validator-api/src/network_monitor/mod.rs index f59bb5367c..24715c32dd 100644 --- a/validator-api/src/network_monitor/mod.rs +++ b/validator-api/src/network_monitor/mod.rs @@ -31,6 +31,103 @@ pub(crate) mod monitor; pub(crate) mod test_packet; pub(crate) mod tested_network; +pub(crate) struct NetworkMonitorBuilder<'a> { + config: &'a Config, + tested_network: TestedNetwork, + node_status_storage: NodeStatusStorage, + validator_cache: ValidatorCache, +} + +impl<'a> NetworkMonitorBuilder<'a> { + pub(crate) fn new( + config: &'a Config, + v4_topology: NymTopology, + v6_topology: NymTopology, + node_status_storage: NodeStatusStorage, + validator_cache: ValidatorCache, + ) -> Self { + let tested_network = TestedNetwork::new_good(v4_topology, v6_topology); + + NetworkMonitorBuilder { + config, + tested_network, + node_status_storage, + validator_cache, + } + } + + pub(crate) async fn build(self) -> NetworkMonitorRunnables { + // TODO: in the future I guess this should somehow change to distribute the load + let tested_mix_gateway = self.tested_network.main_v4_gateway().clone(); + info!( + "* gateway for testing mixnodes: {}", + tested_mix_gateway.identity_key.to_base58_string() + ); + + // TODO: those keys change constant throughout the whole execution of the monitor. + // and on top of that, they are used with ALL the gateways -> presumably this should change + // in the future + let mut rng = rand::rngs::OsRng; + + let identity_keypair = Arc::new(identity::KeyPair::new(&mut rng)); + let encryption_keypair = Arc::new(encryption::KeyPair::new(&mut rng)); + + let test_mixnode_sender = Recipient::new( + *identity_keypair.public_key(), + *encryption_keypair.public_key(), + tested_mix_gateway.identity_key, + ); + + let (gateway_status_update_sender, gateway_status_update_receiver) = mpsc::unbounded(); + let (received_processor_sender_channel, received_processor_receiver_channel) = + mpsc::unbounded(); + + let packet_preparer = new_packet_preparer( + self.validator_cache, + self.tested_network.clone(), + test_mixnode_sender, + *identity_keypair.public_key(), + *encryption_keypair.public_key(), + ); + + let bandwidth_credential = + TEMPORARY_obtain_bandwidth_credential(self.config, identity_keypair.public_key()).await; + + let packet_sender = new_packet_sender( + self.config, + gateway_status_update_sender, + Arc::clone(&identity_keypair), + bandwidth_credential, + self.config.get_gateway_sending_rate(), + ); + + let received_processor = new_received_processor( + received_processor_receiver_channel, + Arc::clone(&encryption_keypair), + ); + let summary_producer = new_summary_producer(self.config.get_detailed_report()); + let packet_receiver = new_packet_receiver( + gateway_status_update_receiver, + received_processor_sender_channel, + ); + + let monitor = monitor::Monitor::new( + self.config, + packet_preparer, + packet_sender, + received_processor, + summary_producer, + self.node_status_storage, + self.tested_network, + ); + + NetworkMonitorRunnables { + monitor, + packet_receiver, + } + } +} + pub(crate) struct NetworkMonitorRunnables { monitor: Monitor, packet_receiver: PacketReceiver, @@ -48,85 +145,6 @@ impl NetworkMonitorRunnables { } } -pub(crate) async fn new_monitor_runnables( - config: &Config, - v4_topology: NymTopology, - v6_topology: NymTopology, - node_status_storage: NodeStatusStorage, - validator_cache: ValidatorCache, -) -> NetworkMonitorRunnables { - // TODO: in the future I guess this should somehow change to distribute the load - let tested_mix_gateway = v4_topology.gateways()[0].clone(); - info!( - "* gateway for testing mixnodes: {}", - tested_mix_gateway.identity_key.to_base58_string() - ); - - let tested_network = TestedNetwork::new_good(v4_topology, v6_topology); - - // TODO: those keys change constant throughout the whole execution of the monitor. - // and on top of that, they are used with ALL the gateways -> presumably this should change - // in the future - let mut rng = rand::rngs::OsRng; - - let identity_keypair = Arc::new(identity::KeyPair::new(&mut rng)); - let encryption_keypair = Arc::new(encryption::KeyPair::new(&mut rng)); - - let test_mixnode_sender = Recipient::new( - *identity_keypair.public_key(), - *encryption_keypair.public_key(), - tested_mix_gateway.identity_key, - ); - - let (gateway_status_update_sender, gateway_status_update_receiver) = mpsc::unbounded(); - let (received_processor_sender_channel, received_processor_receiver_channel) = - mpsc::unbounded(); - - let packet_preparer = new_packet_preparer( - validator_cache, - tested_network.clone(), - test_mixnode_sender, - *identity_keypair.public_key(), - *encryption_keypair.public_key(), - ); - - let bandwidth_credential = - TEMPORARY_obtain_bandwidth_credential(config, identity_keypair.public_key()).await; - - let packet_sender = new_packet_sender( - config, - gateway_status_update_sender, - Arc::clone(&identity_keypair), - bandwidth_credential, - config.get_gateway_sending_rate(), - ); - - let received_processor = new_received_processor( - received_processor_receiver_channel, - Arc::clone(&encryption_keypair), - ); - let summary_producer = new_summary_producer(config.get_detailed_report()); - let packet_receiver = new_packet_receiver( - gateway_status_update_receiver, - received_processor_sender_channel, - ); - - let monitor = monitor::Monitor::new( - config, - packet_preparer, - packet_sender, - received_processor, - summary_producer, - node_status_storage, - tested_network, - ); - - NetworkMonitorRunnables { - monitor, - packet_receiver, - } -} - fn new_packet_preparer( validator_cache: ValidatorCache, tested_network: TestedNetwork, diff --git a/validator-api/src/network_monitor/monitor/mod.rs b/validator-api/src/network_monitor/monitor/mod.rs index ccfff233a0..39bbe0dda9 100644 --- a/validator-api/src/network_monitor/monitor/mod.rs +++ b/validator-api/src/network_monitor/monitor/mod.rs @@ -77,6 +77,18 @@ impl Monitor { // TODO: slightly more graceful shutdown here process::exit(1); } + + // indicate our run has completed successfully and should be used in any future + // uptime calculations + if let Err(err) = self.node_status_storage.insert_monitor_run().await { + error!( + "Failed to submit monitor run information to the database - {}", + err + ); + + // TODO: slightly more graceful shutdown here + process::exit(1); + } } // checking it this way with a TestReport is rather suboptimal but given the fact we're only diff --git a/validator-api/src/node_status_api/models.rs b/validator-api/src/node_status_api/models.rs index a2a174bd97..5f57e17b43 100644 --- a/validator-api/src/node_status_api/models.rs +++ b/validator-api/src/node_status_api/models.rs @@ -7,6 +7,7 @@ use rocket::http::{ContentType, Status}; use rocket::response::{self, Responder, Response}; use rocket::Request; use serde::{Deserialize, Serialize}; +use sqlx::types::time::OffsetDateTime; use std::convert::TryFrom; use std::fmt::{self, Display, Formatter}; use std::io::Cursor; @@ -90,13 +91,21 @@ pub struct MixnodeStatusReport { impl MixnodeStatusReport { pub(crate) fn construct_from_last_day_reports( + report_time: OffsetDateTime, identity: String, owner: String, last_day_ipv4: Vec, last_day_ipv6: Vec, + last_hour_test_runs: usize, + last_day_test_runs: usize, ) -> Self { - let node_uptimes = - NodeUptimes::calculate_from_last_day_reports(last_day_ipv4, last_day_ipv6); + let node_uptimes = NodeUptimes::calculate_from_last_day_reports( + report_time, + last_day_ipv4, + last_day_ipv6, + last_hour_test_runs, + last_day_test_runs, + ); MixnodeStatusReport { identity, @@ -128,13 +137,21 @@ pub struct GatewayStatusReport { impl GatewayStatusReport { pub(crate) fn construct_from_last_day_reports( + report_time: OffsetDateTime, identity: String, owner: String, last_day_ipv4: Vec, last_day_ipv6: Vec, + last_hour_test_runs: usize, + last_day_test_runs: usize, ) -> Self { - let node_uptimes = - NodeUptimes::calculate_from_last_day_reports(last_day_ipv4, last_day_ipv6); + let node_uptimes = NodeUptimes::calculate_from_last_day_reports( + report_time, + last_day_ipv4, + last_day_ipv6, + last_hour_test_runs, + last_day_test_runs, + ); GatewayStatusReport { identity, diff --git a/validator-api/src/node_status_api/utils.rs b/validator-api/src/node_status_api/utils.rs index a7d4f2d52d..08cff22f95 100644 --- a/validator-api/src/node_status_api/utils.rs +++ b/validator-api/src/node_status_api/utils.rs @@ -4,7 +4,9 @@ use crate::node_status_api::models::Uptime; use crate::node_status_api::{FIFTEEN_MINUTES, ONE_HOUR}; use crate::storage::models::NodeStatus; +use log::warn; use sqlx::types::time::OffsetDateTime; +use std::cmp::min; // A temporary helper struct used to produce reports for active nodes. pub(crate) struct ActiveNodeDayStatuses { @@ -30,33 +32,23 @@ pub(crate) struct NodeUptimes { impl NodeUptimes { pub(crate) fn calculate_from_last_day_reports( + report_time: OffsetDateTime, last_day_ipv4: Vec, last_day_ipv6: Vec, + last_hour_test_runs: usize, + last_day_test_runs: usize, ) -> Self { - let now = OffsetDateTime::now_utc(); - let hour_ago = (now - ONE_HOUR).unix_timestamp(); - let fifteen_minutes_ago = (now - FIFTEEN_MINUTES).unix_timestamp(); + let hour_ago = (report_time - ONE_HOUR).unix_timestamp(); + let fifteen_minutes_ago = (report_time - FIFTEEN_MINUTES).unix_timestamp(); - let ipv4_day_total = last_day_ipv4.len(); - let ipv6_day_total = last_day_ipv6.len(); + let mut ipv4_day_up = last_day_ipv4.iter().filter(|report| report.up).count(); + let mut ipv6_day_up = last_day_ipv6.iter().filter(|report| report.up).count(); - let ipv4_day_up = last_day_ipv4.iter().filter(|report| report.up).count(); - let ipv6_day_up = last_day_ipv6.iter().filter(|report| report.up).count(); - - let ipv4_hour_total = last_day_ipv4 - .iter() - .filter(|report| report.timestamp >= hour_ago) - .count(); - let ipv6_hour_total = last_day_ipv6 - .iter() - .filter(|report| report.timestamp >= hour_ago) - .count(); - - let ipv4_hour_up = last_day_ipv4 + let mut ipv4_hour_up = last_day_ipv4 .iter() .filter(|report| report.up && report.timestamp >= hour_ago) .count(); - let ipv6_hour_up = last_day_ipv6 + let mut ipv6_hour_up = last_day_ipv6 .iter() .filter(|report| report.up && report.timestamp >= hour_ago) .count(); @@ -73,15 +65,42 @@ impl NodeUptimes { .map(|status| status.timestamp >= fifteen_minutes_ago && status.up) // make sure its within last 15min .unwrap_or_default(); - // the unwraps in Uptime::from_ratio are fine because it's impossible for us to have more "up" results than all results in total - // because both of those values originate from the same vector + // If somehow we have more "up" reports than the actual test runs it means something weird is going on + // (or we just started running this code on old data, so if it appears for first 24h, it's fine and actually expected + // as we would not have any run information from the past) + // Either way, bound the the number of "up" reports by number of test runs and log warnings + // if that happens + if ipv4_hour_up > last_hour_test_runs || ipv6_hour_up > last_hour_test_runs { + warn!( + "We have more 'up' reports than the actual number of test runs in last hour! ({} ipv4 'ups', {} ipv6 'ups' for {} test runs)", + ipv4_hour_up, + ipv6_hour_up, + last_hour_test_runs, + ); + ipv4_hour_up = min(ipv4_hour_up, last_hour_test_runs); + ipv6_hour_up = min(ipv6_hour_up, last_hour_test_runs); + } + + if ipv4_day_up > last_day_test_runs || ipv6_day_up > last_day_test_runs { + warn!( + "We have more 'up' reports than the actual number of test runs in last day! ({} ipv4 'ups', {} ipv6 'ups' for {} test runs)", + ipv4_day_up, + ipv6_day_up, + last_day_test_runs, + ); + ipv4_day_up = min(ipv4_day_up, last_day_test_runs); + ipv6_day_up = min(ipv6_day_up, last_day_test_runs); + } + + // the unwraps in Uptime::from_ratio are fine because it's impossible for us to have more "up" results + // than total test runs as we just bounded them NodeUptimes { most_recent_ipv4, most_recent_ipv6, - last_hour_ipv4: Uptime::from_ratio(ipv4_hour_up, ipv4_hour_total).unwrap(), - last_hour_ipv6: Uptime::from_ratio(ipv6_hour_up, ipv6_hour_total).unwrap(), - last_day_ipv4: Uptime::from_ratio(ipv4_day_up, ipv4_day_total).unwrap(), - last_day_ipv6: Uptime::from_ratio(ipv6_day_up, ipv6_day_total).unwrap(), + last_hour_ipv4: Uptime::from_ratio(ipv4_hour_up, last_hour_test_runs).unwrap(), + last_hour_ipv6: Uptime::from_ratio(ipv6_hour_up, last_hour_test_runs).unwrap(), + last_day_ipv4: Uptime::from_ratio(ipv4_day_up, last_day_test_runs).unwrap(), + last_day_ipv6: Uptime::from_ratio(ipv6_day_up, last_day_test_runs).unwrap(), } } } diff --git a/validator-api/src/storage/manager.rs b/validator-api/src/storage/manager.rs index c95dbfbb0c..3e18786a2a 100644 --- a/validator-api/src/storage/manager.rs +++ b/validator-api/src/storage/manager.rs @@ -4,10 +4,8 @@ use crate::network_monitor::monitor::summary_producer::NodeResult; use crate::node_status_api::models::{HistoricalUptime, Uptime}; use crate::node_status_api::utils::ActiveNodeDayStatuses; -use crate::node_status_api::ONE_DAY; use crate::storage::models::{ActiveNode, NodeStatus}; use crate::storage::UnixTimestamp; -use sqlx::types::time::OffsetDateTime; use std::convert::TryFrom; #[derive(Clone)] @@ -463,6 +461,43 @@ impl StorageManager { Ok(()) } + /// Creates a database entry for a finished network monitor test run. + /// + /// # Arguments + /// + /// * `timestamp`: unix timestamp at which the monitor test run has occurred + pub(crate) async fn insert_monitor_run( + &self, + timestamp: UnixTimestamp, + ) -> Result<(), sqlx::Error> { + sqlx::query!("INSERT INTO monitor_run(timestamp) VALUES (?)", timestamp) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + /// Obtains number of network monitor test runs that have occurred within the specified interval. + /// + /// # Arguments + /// + /// * `since`: unix timestamp indicating the lower bound interval of the selection. + /// * `until`: unix timestamp indicating the upper bound interval of the selection. + pub(crate) async fn get_monitor_runs_count( + &self, + since: UnixTimestamp, + until: UnixTimestamp, + ) -> Result { + let count = sqlx::query!( + "SELECT COUNT(*) as count FROM monitor_run WHERE timestamp > ? AND timestamp < ?", + since, + until, + ) + .fetch_one(&self.connection_pool) + .await? + .count; + Ok(count) + } + pub(crate) async fn purge_old_mixnode_ipv4_statuses( &self, timestamp: UnixTimestamp, @@ -579,19 +614,17 @@ impl StorageManager { // since technically it doesn't touch any SQL directly pub(crate) async fn get_all_active_mixnodes_statuses( &self, + since: UnixTimestamp, ) -> Result, sqlx::Error> { - let now = OffsetDateTime::now_utc(); - let day_ago = (now - ONE_DAY).unix_timestamp(); - - let active_nodes = self.get_all_active_mixnodes(day_ago).await?; + let active_nodes = self.get_all_active_mixnodes(since).await?; let mut active_day_statuses = Vec::with_capacity(active_nodes.len()); for active_node in active_nodes.into_iter() { let ipv4_statuses = self - .get_mixnode_ipv4_statuses_since_by_id(active_node.id, day_ago) + .get_mixnode_ipv4_statuses_since_by_id(active_node.id, since) .await?; let ipv6_statuses = self - .get_mixnode_ipv6_statuses_since_by_id(active_node.id, day_ago) + .get_mixnode_ipv6_statuses_since_by_id(active_node.id, since) .await?; let statuses = ActiveNodeDayStatuses { @@ -614,19 +647,17 @@ impl StorageManager { // since technically it doesn't touch any SQL directly pub(crate) async fn get_all_active_gateways_statuses( &self, + since: UnixTimestamp, ) -> Result, sqlx::Error> { - let now = OffsetDateTime::now_utc(); - let day_ago = (now - ONE_DAY).unix_timestamp(); - - let active_nodes = self.get_all_active_gateways(day_ago).await?; + let active_nodes = self.get_all_active_gateways(since).await?; let mut active_day_statuses = Vec::with_capacity(active_nodes.len()); for active_node in active_nodes.into_iter() { let ipv4_statuses = self - .get_gateway_ipv4_statuses_since_by_id(active_node.id, day_ago) + .get_gateway_ipv4_statuses_since_by_id(active_node.id, since) .await?; let ipv6_statuses = self - .get_gateway_ipv6_statuses_since_by_id(active_node.id, day_ago) + .get_gateway_ipv6_statuses_since_by_id(active_node.id, since) .await?; let statuses = ActiveNodeDayStatuses { diff --git a/validator-api/src/storage/mod.rs b/validator-api/src/storage/mod.rs index 253566ac8a..2256e94999 100644 --- a/validator-api/src/storage/mod.rs +++ b/validator-api/src/storage/mod.rs @@ -6,7 +6,7 @@ use crate::node_status_api::models::{ GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport, MixnodeUptimeHistory, NodeStatusApiError, Uptime, }; -use crate::node_status_api::ONE_DAY; +use crate::node_status_api::{ONE_DAY, ONE_HOUR}; use crate::storage::manager::StorageManager; use crate::storage::models::NodeStatus; use rocket::fairing::{self, AdHoc}; @@ -67,46 +67,58 @@ impl NodeStatusStorage { }) } - /// Gets all statuses for particular mixnode (ipv4 and ipv6) that were inserted in last 24h. - async fn get_mixnode_daily_statuses( + /// Gets all statuses for particular mixnode (ipv4 and ipv6) that were inserted + /// since the provided timestamp. + /// + /// Returns tuple containing vectors of ipv4 statuses and ipv6 statuses. + /// + /// # Arguments + /// + /// * `identity`: identity key of the mixnode to query. + /// * `since`: unix timestamp indicating the lower bound interval of the selection. + async fn get_mixnode_statuses( &self, identity: &str, + since: UnixTimestamp, ) -> Result<(Vec, Vec), NodeStatusApiError> { - let now = OffsetDateTime::now_utc(); - let day_ago = now - ONE_DAY; - let ipv4_statuses = self .manager - .get_mixnode_ipv4_statuses_since(identity, day_ago.unix_timestamp()) + .get_mixnode_ipv4_statuses_since(identity, since) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; let ipv6_statuses = self .manager - .get_mixnode_ipv6_statuses_since(identity, day_ago.unix_timestamp()) + .get_mixnode_ipv6_statuses_since(identity, since) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; Ok((ipv4_statuses, ipv6_statuses)) } - /// Gets all statuses for particular gateway (ipv4 and ipv6) that were inserted in last 24h. - async fn get_gateway_daily_statuses( + /// Gets all statuses for particular gateway (ipv4 and ipv6) that were inserted + /// since the provided timestamp. + /// + /// Returns tuple containing vectors of ipv4 statuses and ipv6 statuses. + /// + /// # Arguments + /// + /// * `identity`: identity key of the gateway to query. + /// * `since`: unix timestamp indicating the lower bound interval of the selection. + async fn get_gateway_statuses( &self, identity: &str, + since: UnixTimestamp, ) -> Result<(Vec, Vec), NodeStatusApiError> { - let now = OffsetDateTime::now_utc(); - let day_ago = now - ONE_DAY; - let ipv4_statuses = self .manager - .get_gateway_ipv4_statuses_since(identity, day_ago.unix_timestamp()) + .get_gateway_ipv4_statuses_since(identity, since) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; let ipv6_statuses = self .manager - .get_gateway_ipv6_statuses_since(identity, day_ago.unix_timestamp()) + .get_gateway_ipv6_statuses_since(identity, since) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; @@ -118,7 +130,11 @@ impl NodeStatusStorage { &self, identity: &str, ) -> Result { - let (ipv4_statuses, ipv6_statuses) = self.get_mixnode_daily_statuses(identity).await?; + let now = OffsetDateTime::now_utc(); + let day_ago = (now - ONE_DAY).unix_timestamp(); + let hour_ago = (now - ONE_HOUR).unix_timestamp(); + + let (ipv4_statuses, ipv6_statuses) = self.get_mixnode_statuses(identity, day_ago).await?; // if we have no statuses, the node doesn't exist (or monitor is down), but either way, we can't make a report if ipv4_statuses.is_empty() { @@ -127,6 +143,14 @@ impl NodeStatusStorage { )); } + // determine the number of runs the mixnode should have been online for + let last_hour_runs_count = self + .get_monitor_runs_count(hour_ago, now.unix_timestamp()) + .await?; + let last_day_runs_count = self + .get_monitor_runs_count(day_ago, now.unix_timestamp()) + .await?; + // now, technically this is not a critical error, but this should have NEVER happened in the first place // so something super weird is going on if ipv4_statuses.len() != ipv6_statuses.len() { @@ -145,10 +169,13 @@ impl NodeStatusStorage { .expect("The node doesn't have an owner even though we have status information on it!"); Ok(MixnodeStatusReport::construct_from_last_day_reports( + now, identity.to_owned(), mixnode_owner, ipv4_statuses, ipv6_statuses, + last_hour_runs_count, + last_day_runs_count, )) } @@ -156,7 +183,11 @@ impl NodeStatusStorage { &self, identity: &str, ) -> Result { - let (ipv4_statuses, ipv6_statuses) = self.get_gateway_daily_statuses(identity).await?; + let now = OffsetDateTime::now_utc(); + let day_ago = (now - ONE_DAY).unix_timestamp(); + let hour_ago = (now - ONE_HOUR).unix_timestamp(); + + let (ipv4_statuses, ipv6_statuses) = self.get_gateway_statuses(identity, day_ago).await?; // if we have no statuses, the node doesn't exist (or monitor is down), but either way, we can't make a report if ipv4_statuses.is_empty() { @@ -165,6 +196,14 @@ impl NodeStatusStorage { )); } + // determine the number of runs the gateway should have been online for + let last_hour_runs_count = self + .get_monitor_runs_count(hour_ago, now.unix_timestamp()) + .await?; + let last_day_runs_count = self + .get_monitor_runs_count(day_ago, now.unix_timestamp()) + .await?; + // now, technically this is not a critical error, but this should have NEVER happened in the first place // so something super weird is going on if ipv4_statuses.len() != ipv6_statuses.len() { @@ -185,10 +224,13 @@ impl NodeStatusStorage { ); Ok(GatewayStatusReport::construct_from_last_day_reports( + now, identity.to_owned(), gateway_owner, ipv4_statuses, ipv6_statuses, + last_hour_runs_count, + last_day_runs_count, )) } @@ -257,18 +299,33 @@ impl NodeStatusStorage { pub(crate) async fn get_all_mixnode_reports( &self, ) -> Result, NodeStatusApiError> { + let now = OffsetDateTime::now_utc(); + let day_ago = (now - ONE_DAY).unix_timestamp(); + let hour_ago = (now - ONE_HOUR).unix_timestamp(); + + // determine the number of runs the mixnodes should have been online for + let last_hour_runs_count = self + .get_monitor_runs_count(hour_ago, now.unix_timestamp()) + .await?; + let last_day_runs_count = self + .get_monitor_runs_count(day_ago, now.unix_timestamp()) + .await?; + let reports = self .manager - .get_all_active_mixnodes_statuses() + .get_all_active_mixnodes_statuses(day_ago) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)? .into_iter() .map(|statuses| { MixnodeStatusReport::construct_from_last_day_reports( + now, statuses.identity, statuses.owner, statuses.ipv4_statuses, statuses.ipv6_statuses, + last_hour_runs_count, + last_day_runs_count, ) }) .collect(); @@ -281,18 +338,33 @@ impl NodeStatusStorage { pub(crate) async fn get_all_gateway_reports( &self, ) -> Result, NodeStatusApiError> { + let now = OffsetDateTime::now_utc(); + let day_ago = (now - ONE_DAY).unix_timestamp(); + let hour_ago = (now - ONE_HOUR).unix_timestamp(); + + // determine the number of runs the gateways should have been online for + let last_hour_runs_count = self + .get_monitor_runs_count(hour_ago, now.unix_timestamp()) + .await?; + let last_day_runs_count = self + .get_monitor_runs_count(day_ago, now.unix_timestamp()) + .await?; + let reports = self .manager - .get_all_active_gateways_statuses() + .get_all_active_gateways_statuses(day_ago) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)? .into_iter() .map(|statuses| { GatewayStatusReport::construct_from_last_day_reports( + now, statuses.identity, statuses.owner, statuses.ipv4_statuses, statuses.ipv6_statuses, + last_hour_runs_count, + last_day_runs_count, ) }) .collect(); @@ -321,15 +393,53 @@ impl NodeStatusStorage { .map_err(|_| NodeStatusApiError::InternalDatabaseError) } + /// Inserts an entry to the database with the network monitor test run information + /// that has occurred at this instant. + pub(crate) async fn insert_monitor_run(&self) -> Result<(), NodeStatusApiError> { + let now = OffsetDateTime::now_utc().unix_timestamp(); + + self.manager + .insert_monitor_run(now) + .await + .map_err(|_| NodeStatusApiError::InternalDatabaseError) + } + + /// Obtains number of network monitor test runs that have occurred within the specified interval. + /// + /// # Arguments + /// + /// * `since`: unix timestamp indicating the lower bound interval of the selection. + /// * `until`: unix timestamp indicating the upper bound interval of the selection. + pub(crate) async fn get_monitor_runs_count( + &self, + since: UnixTimestamp, + until: UnixTimestamp, + ) -> Result { + let run_count = self + .manager + .get_monitor_runs_count(since, until) + .await + .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; + + if run_count < 0 { + // I don't think it's ever possible for SQL to return a negative value from COUNT? + return Err(NodeStatusApiError::InternalDatabaseError); + } + Ok(run_count as usize) + } + // Called on timer/reward script async fn update_historical_uptimes( &self, today_iso_8601: &str, ) -> Result<(), NodeStatusApiError> { + let now = OffsetDateTime::now_utc(); + let day_ago = (now - ONE_DAY).unix_timestamp(); + // get statuses for all active mixnodes... let active_mixnodes_statuses = self .manager - .get_all_active_mixnodes_statuses() + .get_all_active_mixnodes_statuses(day_ago) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; @@ -368,7 +478,7 @@ impl NodeStatusStorage { // get statuses for all active gateways... let active_gateways_statuses = self .manager - .get_all_active_gateways_statuses() + .get_all_active_gateways_statuses(day_ago) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; diff --git a/wallet-web/components/AppAlert.tsx b/wallet-web/components/AppAlert.tsx new file mode 100644 index 0000000000..6e7bff3583 --- /dev/null +++ b/wallet-web/components/AppAlert.tsx @@ -0,0 +1,30 @@ +import { IconButton } from '@material-ui/core' +import { Close } from '@material-ui/icons' +import { Alert, AlertProps, AlertTitle } from '@material-ui/lab' +import React, { useState } from 'react' + +export const AppAlert = ({ + message, + severity = 'info', + title, +}: { + message: string + severity?: AlertProps['severity'] + title?: string +}) => { + const [showAlert, setShowAlert] = useState(true) + + return showAlert ? ( + setShowAlert(false)}> + + + } + > + {title} + {message} + + ) : null +} diff --git a/wallet-web/package-lock.json b/wallet-web/package-lock.json index 6b029202cd..a1a9e22e12 100644 --- a/wallet-web/package-lock.json +++ b/wallet-web/package-lock.json @@ -11,10 +11,10 @@ "@material-ui/core": "^4.11.3", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.57", - "@nymproject/nym-validator-client": "0.16.0", + "@nymproject/nym-validator-client": "0.17.0", "@types/react-dom": "^17.0.3", "bs58": "^4.0.1", - "next": "11.1.0", + "next": "11.1.1", "react": "17.0.2", "react-dom": "17.0.2", "semver": "^7.3.5" @@ -1785,11 +1785,14 @@ } }, "node_modules/@babel/runtime": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", - "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz", + "integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==", "dependencies": { "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/template": { @@ -1906,14 +1909,6 @@ "@cosmjs/utils": "^0.25.5" } }, - "node_modules/@cosmjs/cosmwasm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm/-/cosmwasm-0.25.5.tgz", - "integrity": "sha512-PsK28ARcow5hEd+hKAdGsjtjz/g5UMtPpSWD7l1WxWhuASLmJVXFebBUPJAu3CDeFG3FrlCRP5J8tLvJOisuJQ==", - "dependencies": { - "@cosmjs/cosmwasm-launchpad": "^0.25.5" - } - }, "node_modules/@cosmjs/cosmwasm-launchpad": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-launchpad/-/cosmwasm-launchpad-0.25.5.tgz", @@ -1996,33 +1991,6 @@ "fast-deep-equal": "^3.1.3" } }, - "node_modules/@cosmjs/launchpad/node_modules/axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dependencies": { - "follow-redirects": "^1.10.0" - } - }, - "node_modules/@cosmjs/launchpad/node_modules/follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/@cosmjs/math": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.25.5.tgz", @@ -2093,33 +2061,6 @@ "xstream": "^11.14.0" } }, - "node_modules/@cosmjs/tendermint-rpc/node_modules/axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dependencies": { - "follow-redirects": "^1.10.0" - } - }, - "node_modules/@cosmjs/tendermint-rpc/node_modules/follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/@cosmjs/utils": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.25.5.tgz", @@ -3011,19 +2952,19 @@ "integrity": "sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA==" }, "node_modules/@next/env": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.0.tgz", - "integrity": "sha512-zPJkMFRenSf7BLlVee8987G0qQXAhxy7k+Lb/5hLAGkPVHAHm+oFFeL+2ipbI2KTEFlazdmGY0M+AlLQn7pWaw==" + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.1.tgz", + "integrity": "sha512-UEAzlfKofotLmj9LIgNixAfXpRck9rt/1CU9Q4ZtNDueGBJQP3HUzPHlrLChltWY2TA5MOzDQGL82H0a3+i5Ag==" }, "node_modules/@next/polyfill-module": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.0.tgz", - "integrity": "sha512-64EgW8SzJRQls2yJ5DkuljRxgE24o2kYtX/ghTkPUJYsfidHMWzQGwg26IgRbb/uHqTd1G0W5UkKag+Nt8TWaQ==" + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.1.tgz", + "integrity": "sha512-9FyVSnz00WGdlLsgc2w1xL1Lm/Q25y6FYIyA+1WlJvT6LA2lbR78GKiHgedzUvrAatVGAcg/Og+d0d7B4tsJOg==" }, "node_modules/@next/react-dev-overlay": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.0.tgz", - "integrity": "sha512-h+ry0sTk1W3mJw+TwEf91aqLbBJ5oqAsxfx+QryqEItNtfW6zLSSjxkyTYTqX8DkgSssQQutQfATkzBVgOR+qQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.1.tgz", + "integrity": "sha512-CXc/A0DbSk5VXYu4+zr0fHm52Zh/LhPlLyVPEctJOZL64ccxkls5xGoXvgolJCku9L0pLjJzvdfAmhNLOp5dyw==", "dependencies": { "@babel/code-frame": "7.12.11", "anser": "1.4.9", @@ -3107,9 +3048,9 @@ } }, "node_modules/@next/react-refresh-utils": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.0.tgz", - "integrity": "sha512-g5DtFTpLTGa36iy9DuZawtJeitI11gysFGKPQQqy+mNbSFazguArcJ10gAYFlbqpIi4boUamWNI5mAoSPx3kog==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.1.tgz", + "integrity": "sha512-j186y+lWc8BHAuysAWvlOqO9Bp7E3BLK/d/Ju3W2sP5BCH5ZLyLG/p308zSy/O0MGTag0B038ZA1dCy/msouRQ==", "peerDependencies": { "react-refresh": "0.8.3", "webpack": "^4 || ^5" @@ -3120,6 +3061,66 @@ } } }, + "node_modules/@next/swc-darwin-arm64": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.1.tgz", + "integrity": "sha512-KyB0aLpfQ+B2dsyGYpkM0ZwK3PV0t4C4b9yjgQc1VoTVnIjzXdDPnNOuVvmD849ZNOHfj3x8e2rlbxkj0lPm3A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.1.tgz", + "integrity": "sha512-B3ZXgrGx0bQplbrk2oggPjKPPsmyg8Fl0PJLMTVQ+erQ8g1m5QzyS9P6tB3SiIZa180JgENuguTHlVK5qEj4UA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.1.tgz", + "integrity": "sha512-qvZL7gSKF+E+GZ3L1XiTnE3cOh9rk0wkqimT/q+wwcZA4E720Lu4lrT79I3HPuj6i/JPgGvmNskcnYrDeaoFaw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.1.tgz", + "integrity": "sha512-jhnCiA1De1L+kA0gmHG1AJijHoxOcrETWziDWy8fcqSrM1NlC4aJ5Mnu6k0QMcM9MnmXTA4TQZOEv3kF7vhJUQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@node-rs/helper": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.2.1.tgz", @@ -3129,17 +3130,15 @@ } }, "node_modules/@nymproject/nym-validator-client": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.16.0.tgz", - "integrity": "sha512-tczc9qx68D3CJsw+BZ3N6ReCq/smgpgL58iQQ0yo5MD2ZoGIu6J8Zhg2vau/IR9uRyGtg4ipNSe0Wtxi++pnJQ==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.17.0.tgz", + "integrity": "sha512-MtzKECm3ZSV/P8ro6ZbKqHiSqg3IU3WXUpiGdFamfWnvVDa7Q3SSyxc26kr59CH8DnrkMWa2/N96i1dIVj3YYA==", "dependencies": { - "@cosmjs/cosmwasm": "^0.25.5", "@cosmjs/cosmwasm-stargate": "^0.25.5", - "@cosmjs/crypto": "^0.25.5", - "@cosmjs/launchpad": "^0.25.5", "@cosmjs/math": "^0.25.5", "@cosmjs/proto-signing": "^0.25.5", - "axios": "^0.19.2" + "@cosmjs/stargate": "^0.25.5", + "axios": "^0.21.1" } }, "node_modules/@protobufjs/aspromise": { @@ -3746,12 +3745,11 @@ } }, "node_modules/axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", - "deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "dependencies": { - "follow-redirects": "1.5.10" + "follow-redirects": "^1.10.0" } }, "node_modules/babel-jest": { @@ -4696,14 +4694,6 @@ "node": ">=10" } }, - "node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dependencies": { - "ms": "2.0.0" - } - }, "node_modules/decimal.js": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", @@ -5176,14 +5166,22 @@ } }, "node_modules/follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "dependencies": { - "debug": "=3.1.0" - }, + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz", + "integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "engines": { "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, "node_modules/foreach": { @@ -8319,16 +8317,16 @@ "dev": true }, "node_modules/next": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/next/-/next-11.1.0.tgz", - "integrity": "sha512-GHBk/c7Wyr6YbFRFZF37I0X7HKzkHHI8pur/loyXo5AIE8wdkbGPGO0ds3vNAO6f8AxZAKGCRYtAzoGlVLoifA==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/next/-/next-11.1.1.tgz", + "integrity": "sha512-vfLJDkwAHsZUho5R1K4w49nfYhftUMWNmeNSjCtulOvnRBuEFb7ROyRZOQk7f29rMz02eLQrPZ9yiAmPsexL2g==", "dependencies": { - "@babel/runtime": "7.12.5", + "@babel/runtime": "7.15.3", "@hapi/accept": "5.0.2", - "@next/env": "11.1.0", - "@next/polyfill-module": "11.1.0", - "@next/react-dev-overlay": "11.1.0", - "@next/react-refresh-utils": "11.1.0", + "@next/env": "11.1.1", + "@next/polyfill-module": "11.1.1", + "@next/react-dev-overlay": "11.1.1", + "@next/react-refresh-utils": "11.1.1", "@node-rs/helper": "1.2.1", "assert": "2.0.0", "ast-types": "0.13.2", @@ -8370,7 +8368,7 @@ "timers-browserify": "2.0.12", "tty-browserify": "0.0.1", "use-subscription": "1.5.1", - "util": "0.12.3", + "util": "0.12.4", "vm-browserify": "1.1.2", "watchpack": "2.1.1" }, @@ -8380,6 +8378,12 @@ "engines": { "node": ">=12.0.0" }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "11.1.1", + "@next/swc-darwin-x64": "11.1.1", + "@next/swc-linux-x64-gnu": "11.1.1", + "@next/swc-win32-x64-msvc": "11.1.1" + }, "peerDependencies": { "fibers": ">= 3.1.0", "node-sass": "^4.0.0 || ^5.0.0", @@ -10083,9 +10087,9 @@ } }, "node_modules/util": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz", - "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==", + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", + "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -11650,9 +11654,9 @@ } }, "@babel/runtime": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", - "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz", + "integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -11752,14 +11756,6 @@ "@cosmjs/utils": "^0.25.5" } }, - "@cosmjs/cosmwasm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm/-/cosmwasm-0.25.5.tgz", - "integrity": "sha512-PsK28ARcow5hEd+hKAdGsjtjz/g5UMtPpSWD7l1WxWhuASLmJVXFebBUPJAu3CDeFG3FrlCRP5J8tLvJOisuJQ==", - "requires": { - "@cosmjs/cosmwasm-launchpad": "^0.25.5" - } - }, "@cosmjs/cosmwasm-launchpad": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-launchpad/-/cosmwasm-launchpad-0.25.5.tgz", @@ -11840,21 +11836,6 @@ "@cosmjs/utils": "^0.25.5", "axios": "^0.21.1", "fast-deep-equal": "^3.1.3" - }, - "dependencies": { - "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==" - } } }, "@cosmjs/math": { @@ -11925,21 +11906,6 @@ "axios": "^0.21.1", "readonly-date": "^1.0.0", "xstream": "^11.14.0" - }, - "dependencies": { - "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==" - } } }, "@cosmjs/utils": { @@ -12576,19 +12542,19 @@ "integrity": "sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA==" }, "@next/env": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.0.tgz", - "integrity": "sha512-zPJkMFRenSf7BLlVee8987G0qQXAhxy7k+Lb/5hLAGkPVHAHm+oFFeL+2ipbI2KTEFlazdmGY0M+AlLQn7pWaw==" + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.1.tgz", + "integrity": "sha512-UEAzlfKofotLmj9LIgNixAfXpRck9rt/1CU9Q4ZtNDueGBJQP3HUzPHlrLChltWY2TA5MOzDQGL82H0a3+i5Ag==" }, "@next/polyfill-module": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.0.tgz", - "integrity": "sha512-64EgW8SzJRQls2yJ5DkuljRxgE24o2kYtX/ghTkPUJYsfidHMWzQGwg26IgRbb/uHqTd1G0W5UkKag+Nt8TWaQ==" + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.1.tgz", + "integrity": "sha512-9FyVSnz00WGdlLsgc2w1xL1Lm/Q25y6FYIyA+1WlJvT6LA2lbR78GKiHgedzUvrAatVGAcg/Og+d0d7B4tsJOg==" }, "@next/react-dev-overlay": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.0.tgz", - "integrity": "sha512-h+ry0sTk1W3mJw+TwEf91aqLbBJ5oqAsxfx+QryqEItNtfW6zLSSjxkyTYTqX8DkgSssQQutQfATkzBVgOR+qQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.1.tgz", + "integrity": "sha512-CXc/A0DbSk5VXYu4+zr0fHm52Zh/LhPlLyVPEctJOZL64ccxkls5xGoXvgolJCku9L0pLjJzvdfAmhNLOp5dyw==", "requires": { "@babel/code-frame": "7.12.11", "anser": "1.4.9", @@ -12649,11 +12615,35 @@ } }, "@next/react-refresh-utils": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.0.tgz", - "integrity": "sha512-g5DtFTpLTGa36iy9DuZawtJeitI11gysFGKPQQqy+mNbSFazguArcJ10gAYFlbqpIi4boUamWNI5mAoSPx3kog==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.1.tgz", + "integrity": "sha512-j186y+lWc8BHAuysAWvlOqO9Bp7E3BLK/d/Ju3W2sP5BCH5ZLyLG/p308zSy/O0MGTag0B038ZA1dCy/msouRQ==", "requires": {} }, + "@next/swc-darwin-arm64": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.1.tgz", + "integrity": "sha512-KyB0aLpfQ+B2dsyGYpkM0ZwK3PV0t4C4b9yjgQc1VoTVnIjzXdDPnNOuVvmD849ZNOHfj3x8e2rlbxkj0lPm3A==", + "optional": true + }, + "@next/swc-darwin-x64": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.1.tgz", + "integrity": "sha512-B3ZXgrGx0bQplbrk2oggPjKPPsmyg8Fl0PJLMTVQ+erQ8g1m5QzyS9P6tB3SiIZa180JgENuguTHlVK5qEj4UA==", + "optional": true + }, + "@next/swc-linux-x64-gnu": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.1.tgz", + "integrity": "sha512-qvZL7gSKF+E+GZ3L1XiTnE3cOh9rk0wkqimT/q+wwcZA4E720Lu4lrT79I3HPuj6i/JPgGvmNskcnYrDeaoFaw==", + "optional": true + }, + "@next/swc-win32-x64-msvc": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.1.tgz", + "integrity": "sha512-jhnCiA1De1L+kA0gmHG1AJijHoxOcrETWziDWy8fcqSrM1NlC4aJ5Mnu6k0QMcM9MnmXTA4TQZOEv3kF7vhJUQ==", + "optional": true + }, "@node-rs/helper": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.2.1.tgz", @@ -12663,17 +12653,15 @@ } }, "@nymproject/nym-validator-client": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.16.0.tgz", - "integrity": "sha512-tczc9qx68D3CJsw+BZ3N6ReCq/smgpgL58iQQ0yo5MD2ZoGIu6J8Zhg2vau/IR9uRyGtg4ipNSe0Wtxi++pnJQ==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.17.0.tgz", + "integrity": "sha512-MtzKECm3ZSV/P8ro6ZbKqHiSqg3IU3WXUpiGdFamfWnvVDa7Q3SSyxc26kr59CH8DnrkMWa2/N96i1dIVj3YYA==", "requires": { - "@cosmjs/cosmwasm": "^0.25.5", "@cosmjs/cosmwasm-stargate": "^0.25.5", - "@cosmjs/crypto": "^0.25.5", - "@cosmjs/launchpad": "^0.25.5", "@cosmjs/math": "^0.25.5", "@cosmjs/proto-signing": "^0.25.5", - "axios": "^0.19.2" + "@cosmjs/stargate": "^0.25.5", + "axios": "^0.21.1" } }, "@protobufjs/aspromise": { @@ -13195,11 +13183,11 @@ } }, "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "requires": { - "follow-redirects": "1.5.10" + "follow-redirects": "^1.10.0" } }, "babel-jest": { @@ -13985,14 +13973,6 @@ } } }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, "decimal.js": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", @@ -14355,12 +14335,9 @@ } }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - } + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz", + "integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==" }, "foreach": { "version": "2.0.5", @@ -16702,16 +16679,20 @@ "dev": true }, "next": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/next/-/next-11.1.0.tgz", - "integrity": "sha512-GHBk/c7Wyr6YbFRFZF37I0X7HKzkHHI8pur/loyXo5AIE8wdkbGPGO0ds3vNAO6f8AxZAKGCRYtAzoGlVLoifA==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/next/-/next-11.1.1.tgz", + "integrity": "sha512-vfLJDkwAHsZUho5R1K4w49nfYhftUMWNmeNSjCtulOvnRBuEFb7ROyRZOQk7f29rMz02eLQrPZ9yiAmPsexL2g==", "requires": { - "@babel/runtime": "7.12.5", + "@babel/runtime": "7.15.3", "@hapi/accept": "5.0.2", - "@next/env": "11.1.0", - "@next/polyfill-module": "11.1.0", - "@next/react-dev-overlay": "11.1.0", - "@next/react-refresh-utils": "11.1.0", + "@next/env": "11.1.1", + "@next/polyfill-module": "11.1.1", + "@next/react-dev-overlay": "11.1.1", + "@next/react-refresh-utils": "11.1.1", + "@next/swc-darwin-arm64": "11.1.1", + "@next/swc-darwin-x64": "11.1.1", + "@next/swc-linux-x64-gnu": "11.1.1", + "@next/swc-win32-x64-msvc": "11.1.1", "@node-rs/helper": "1.2.1", "assert": "2.0.0", "ast-types": "0.13.2", @@ -16753,7 +16734,7 @@ "timers-browserify": "2.0.12", "tty-browserify": "0.0.1", "use-subscription": "1.5.1", - "util": "0.12.3", + "util": "0.12.4", "vm-browserify": "1.1.2", "watchpack": "2.1.1" }, @@ -18105,9 +18086,9 @@ } }, "util": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz", - "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==", + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", + "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", "requires": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", diff --git a/wallet-web/package.json b/wallet-web/package.json index 9b845a2893..509ff97076 100644 --- a/wallet-web/package.json +++ b/wallet-web/package.json @@ -18,7 +18,7 @@ "@nymproject/nym-validator-client": "0.17.0", "@types/react-dom": "^17.0.3", "bs58": "^4.0.1", - "next": "11.1.0", + "next": "11.1.1", "react": "17.0.2", "react-dom": "17.0.2", "semver": "^7.3.5" diff --git a/wallet-web/pages/_app.tsx b/wallet-web/pages/_app.tsx index c7064abf06..9d95f386d9 100644 --- a/wallet-web/pages/_app.tsx +++ b/wallet-web/pages/_app.tsx @@ -1,49 +1,52 @@ -import React, { useState } from 'react'; -import Head from 'next/head'; -import { ThemeProvider } from '@material-ui/core/styles'; -import CssBaseline from '@material-ui/core/CssBaseline'; -import { theme } from '../lib/theme'; -import type { AppProps } from 'next/app'; -import { ValidatorClientContext } from "../contexts/ValidatorClient"; - +import React, { useState } from 'react' +import Head from 'next/head' +import { ThemeProvider } from '@material-ui/core/styles' +import CssBaseline from '@material-ui/core/CssBaseline' +import { theme } from '../lib/theme' +import type { AppProps } from 'next/app' +import { ValidatorClientContext } from '../contexts/ValidatorClient' // TODO: should it perhaps be pulled from some config or also user provided? -export const BONDING_CONTRACT_ADDRESS: string = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen"; +export const BONDING_CONTRACT_ADDRESS: string = + 'punk10pyejy66429refv3g35g2t7am0was7yalwrzen' export const VALIDATOR_URLS: string[] = [ - "https://testnet-milhon-validator1.nymtech.net", - "https://testnet-milhon-validator2.nymtech.net", -]; -export const ADDRESS_LENGTH: number = 43; -export const ADMIN_ADDRESS: string = "punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0" -export const DENOM: string = "punk"; // used everywhere else -export const KEY_LENGTH: number = 32; -export const UDENOM: string = "upunk"; // required for client and coin construction - + 'https://testnet-milhon-validator1.nymtech.net', + 'https://testnet-milhon-validator2.nymtech.net', +] +export const ADDRESS_LENGTH: number = 43 +export const ADMIN_ADDRESS: string = + 'punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0' +export const DENOM: string = 'punk' // used everywhere else +export const KEY_LENGTH: number = 32 +export const UDENOM: string = 'upunk' // required for client and coin construction export default function Application(props: AppProps) { - const { Component, pageProps } = props; + const { Component, pageProps } = props - const [client, setClient] = useState(null) + const [client, setClient] = useState(null) - React.useEffect(() => { - const jssStyles = document.querySelector('#jss-server-side'); - if (jssStyles) { - jssStyles.parentElement.removeChild(jssStyles); - } - }, []); + React.useEffect(() => { + const jssStyles = document.querySelector('#jss-server-side') + if (jssStyles) { + jssStyles.parentElement.removeChild(jssStyles) + } + }, []) - return ( - - - - - Nym - - - - - - - - - ); + return ( + + + + + Nym + + + + + + + + + ) } diff --git a/wallet-web/yarn.lock b/wallet-web/yarn.lock index c025b1eef2..46b2251bfa 100644 --- a/wallet-web/yarn.lock +++ b/wallet-web/yarn.lock @@ -821,9 +821,9 @@ "@babel/helper-validator-option" "^7.14.5" "@babel/plugin-transform-typescript" "^7.14.5" -"@babel/runtime@7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": - version "7.12.5" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz" +"@babel/runtime@7.15.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b" dependencies: regenerator-runtime "^0.13.4" @@ -920,12 +920,6 @@ pako "^2.0.2" protobufjs "~6.10.2" -"@cosmjs/cosmwasm@^0.25.5": - version "0.25.5" - resolved "https://registry.npmjs.org/@cosmjs/cosmwasm/-/cosmwasm-0.25.5.tgz" - dependencies: - "@cosmjs/cosmwasm-launchpad" "^0.25.5" - "@cosmjs/crypto@^0.25.5": version "0.25.5" resolved "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.25.5.tgz" @@ -1312,17 +1306,17 @@ version "1.0.3" resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c" -"@next/env@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@next/env/-/env-11.1.0.tgz#cae83d8e0a65aa9f2af3368f8269ffd9d911746a" +"@next/env@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/env/-/env-11.1.1.tgz#d403282accbe8795aa2341f0e02c2e8bfc92bfb0" -"@next/polyfill-module@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-11.1.0.tgz#ee6b9117a1f9bb137479dfa51d5a9e38e066a62f" +"@next/polyfill-module@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-11.1.1.tgz#89d5a70685a52a0fad79f05a1f97a6b15cc727aa" -"@next/react-dev-overlay@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-11.1.0.tgz#8d4e8020a4cbdacbca431a0bf40c4d28187083af" +"@next/react-dev-overlay@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-11.1.1.tgz#3cd99202a85412bada8ba9c8e3f4cf7c19294b24" dependencies: "@babel/code-frame" "7.12.11" anser "1.4.9" @@ -1336,9 +1330,25 @@ stacktrace-parser "0.1.10" strip-ansi "6.0.0" -"@next/react-refresh-utils@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-11.1.0.tgz#60c3c7b127a5dab8b0a2889a7dcf8a90d2c4e592" +"@next/react-refresh-utils@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-11.1.1.tgz#8d1a5432a53c9f987503d5ab07d3241230afb33f" + +"@next/swc-darwin-arm64@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.1.tgz#ea9a76bcff00945df29a81bc43b3b22dd0a6cb53" + +"@next/swc-darwin-x64@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.1.tgz#95838e9116897ae734d02fdbbfa601b6f52adaf3" + +"@next/swc-linux-x64-gnu@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.1.tgz#42c4973213a880977ebdfad01474217d7d71e8c2" + +"@next/swc-win32-x64-msvc@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.1.tgz#1ffcbd01a0155fa8558f7aefffea1066e9bebe74" "@node-rs/helper@1.2.1": version "1.2.1" @@ -1346,17 +1356,15 @@ dependencies: "@napi-rs/triples" "^1.0.3" -"@nymproject/nym-validator-client@0.16.0": - version "0.16.0" - resolved "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.16.0.tgz" +"@nymproject/nym-validator-client@0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@nymproject/nym-validator-client/-/nym-validator-client-0.17.0.tgz#b03af24295cbd2f1b94ffd0c38c5a493e994d111" dependencies: - "@cosmjs/cosmwasm" "^0.25.5" "@cosmjs/cosmwasm-stargate" "^0.25.5" - "@cosmjs/crypto" "^0.25.5" - "@cosmjs/launchpad" "^0.25.5" "@cosmjs/math" "^0.25.5" "@cosmjs/proto-signing" "^0.25.5" - axios "^0.19.2" + "@cosmjs/stargate" "^0.25.5" + axios "^0.21.1" "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" @@ -1658,12 +1666,6 @@ available-typed-arrays@^1.0.2: dependencies: array-filter "^1.0.0" -axios@^0.19.2: - version "0.19.2" - resolved "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz" - dependencies: - follow-redirects "1.5.10" - axios@^0.21.1: version "0.21.1" resolved "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz" @@ -2221,12 +2223,6 @@ debug@4, debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" -debug@=3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz" - dependencies: - ms "2.0.0" - decimal.js@^10.2.1: version "10.3.1" resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz" @@ -2494,12 +2490,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz" - dependencies: - debug "=3.1.0" - follow-redirects@^1.10.0: version "1.13.3" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz" @@ -3617,16 +3607,16 @@ natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" -next@11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/next/-/next-11.1.0.tgz#767d4c4fa0b9b0c768cdbd6c9f03dd86b5d701c0" +next@11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/next/-/next-11.1.1.tgz#ca15c6d6b4b4bf8c3e859f7fc4f9657ce59bcb63" dependencies: - "@babel/runtime" "7.12.5" + "@babel/runtime" "7.15.3" "@hapi/accept" "5.0.2" - "@next/env" "11.1.0" - "@next/polyfill-module" "11.1.0" - "@next/react-dev-overlay" "11.1.0" - "@next/react-refresh-utils" "11.1.0" + "@next/env" "11.1.1" + "@next/polyfill-module" "11.1.1" + "@next/react-dev-overlay" "11.1.1" + "@next/react-refresh-utils" "11.1.1" "@node-rs/helper" "1.2.1" assert "2.0.0" ast-types "0.13.2" @@ -3668,9 +3658,14 @@ next@11.1.0: timers-browserify "2.0.12" tty-browserify "0.0.1" use-subscription "1.5.1" - util "0.12.3" + util "0.12.4" vm-browserify "1.1.2" watchpack "2.1.1" + optionalDependencies: + "@next/swc-darwin-arm64" "11.1.1" + "@next/swc-darwin-x64" "11.1.1" + "@next/swc-linux-x64-gnu" "11.1.1" + "@next/swc-win32-x64-msvc" "11.1.1" node-fetch@2.6.1: version "2.6.1" @@ -4637,9 +4632,9 @@ util@0.10.3: dependencies: inherits "2.0.1" -util@0.12.3, util@^0.12.0: - version "0.12.3" - resolved "https://registry.npmjs.org/util/-/util-0.12.3.tgz" +util@0.12.4, util@^0.12.0: + version "0.12.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253" dependencies: inherits "^2.0.3" is-arguments "^1.0.4" From f33defc645f6f4e576029263a2aaadc25152fad0 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Thu, 16 Sep 2021 17:36:24 +0200 Subject: [PATCH 08/16] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 976dd7aae2e7ee6c8a0fdbe368f393b1c26ad704 Author: Drazen Urch Date: Wed Sep 15 17:28:49 2021 +0200 Add block_height method to Delegation (#778) Co-authored-by: Drazen Urch commit 0d21f4e937b1222820260a426c02b10d420963aa Merge: e84af4f6 1403449a Author: Fouad Date: Wed Sep 15 12:41:29 2021 +0100 Merge pull request #776 from nymtech/update/re-enable-bonding re-enable bonding commit 1403449ad51ec2cba19997af88e60f1435512171 Author: fmtabbara Date: Tue Sep 14 16:00:21 2021 +0100 enable bonding commit e84af4f6018772feb4896b31702f2d33ee4c1fb1 Author: Drazen Urch Date: Tue Sep 14 15:15:26 2021 +0200 Migrate legacy delegation data (#771) * Skip ReadOnlyBucket deserialization errors * empty migration * clippy * cargo schema * Drop invalid delegation data * Dont drop old data * Add todo * Unify on type param * gateways are different * cargo fmt Co-authored-by: Drazen Urch commit 26b032c15c7c8ec8f1442f23d2416bafce65323a Merge: e1ddaff0 cba36253 Author: Mark Sinclair Date: Tue Sep 14 10:09:14 2021 +0100 Merge pull request #774 from nymtech/feature/explorer-api-delegations Explorer-api: add API resource to show the delegations for each mix node commit cba3625394dae3f736dab23018df5fcc3c9a4f5d Author: Mark Sinclair Date: Mon Sep 13 10:33:41 2021 +0100 explorer-api: add API resource to show the delegations for each mix node commit e1ddaff04d5077969dd0dbb1e645653035aa8e23 Merge: 0b9c03ca 66ab5de4 Author: Fouad Date: Fri Sep 10 17:17:14 2021 +0100 Merge pull request #772 from nymtech/update/disable-bonding add app alert commit 66ab5de442de91a50556117b62ef028b6b249f6f Author: fmtabbara Date: Fri Sep 10 16:16:58 2021 +0100 add app alert commit 0b9c03ca900a834856efa37706dafac7685b5f79 Author: Dave Hrycyszyn Date: Fri Sep 10 11:23:21 2021 +0300 Adding deps for building the Tauri wallet under Ubuntu (#770) commit c9dce0c1da29a99b1c483009a0658b2236c3a4e8 Author: Bogdan-Ștefan Neacşu Date: Thu Sep 9 11:21:45 2021 +0200 Feature/consumable bandwidth (#766) * Set actual value for bandwidth Also put it as a public attribute, such that it can be actively used by the credential consumer * Switch from sending Attribute structs to sending the actual attribute bytes over the wire * Add atomic bandwidth value to gateway * Consume bandwidth based on the mix packet size * Use Bandwidth struct for specific functionality * Move bandwidth code outside the dependency path of wasm client * Use u64 instead of AtomicU64, as the handling is not parallel commit e00e77db15c2c18699c58bf771940c16f63a610f Author: Bogdan-Ștefan Neacşu Date: Wed Sep 8 15:07:24 2021 +0200 Feature/bond blockstamp (#760) * Add block_height to MixNode/GatewayBond * Reward based on blockstamp of bonded node or of delegation * Add specific tests * Add migration code * Apply doc nit commit 1074449f91a1978e2d03f190c5854ae162bae907 Merge: 08276e6e 9a3d824a Author: Fouad Date: Tue Sep 7 23:45:10 2021 +0100 Merge pull request #767 from nymtech/update/remove-app-alert remove alert commit 08276e6e427679f1b437e370a66dd1140631b429 Author: Bogdan-Ștefan Neacşu Date: Tue Sep 7 16:33:30 2021 +0200 Remove migration code (#759) commit 9a3d824a4a451d4906219607f4a7bcd27dbbeba4 Author: fmtabbara Date: Mon Sep 6 20:39:24 2021 +0100 remove alert commit 2789ee8f18768bf7fd9b26a63ee928b286996049 Author: Bogdan-Ștefan Neacşu Date: Fri Sep 3 15:30:45 2021 +0300 Update coconut-rs and use hash_to_scalar from there (#765) Failed tests are due to some nightly issue, not related to this PR commit a7ba643c354da7ec40ecfb1b0fcae46b9f57f0e2 Merge: 28be53ee c42f3c68 Author: Fouad Date: Fri Sep 3 09:14:50 2021 +0100 Merge pull request #762 from nymtech/feature/app-alert add app alert banner commit 28be53eefb9a1a6950b0b84e8117c6829df93c37 Author: Bogdan-Ștefan Neacşu Date: Thu Sep 2 18:26:40 2021 +0300 Add block_height in the Delegation structure as well (#757) commit 219c45a352584ab7f9efb957107a671a4e19d13e Author: Jędrzej Stuczyński Date: Thu Sep 2 15:48:29 2021 +0100 Updated cosmos-sdk (#761) * Updated cosmos-sdk * Re-exposing more things commit 1a3b83752e0f5e4eed5515ba0b90f3551c1f0a81 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Sep 2 15:45:56 2021 +0100 Bump next from 11.1.0 to 11.1.1 in /wallet-web (#758) Bumps [next](https://github.com/vercel/next.js) from 11.1.0 to 11.1.1. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v11.1.0...v11.1.1) --- updated-dependencies: - dependency-name: next dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit c42f3c684425d0e718ca55adacbe5afa99502603 Author: fmtabbara Date: Thu Sep 2 12:29:47 2021 +0100 add app alert banner commit a5d3ba390029818fcaa7c640a0c6eb6ec7916a96 Merge: 92e13a5d cdf0d443 Author: Mark Sinclair Date: Tue Aug 31 15:59:11 2021 +0100 Merge pull request #755 from nymtech/bugfix/explorer-api-ping Explorer API: port test now split out address resolution and add units tests commit cdf0d443411618e0952c35bfb5972fc82023cdfa Author: Mark Sinclair Date: Tue Aug 31 14:37:28 2021 +0100 explorer-api: turned down logging from `error` to `warn` commit 92f976a45d2d05c1250d6351392a3acc4eb1c01f Author: Mark Sinclair Date: Fri Aug 27 11:34:21 2021 +0100 explorer-api: sanitize hostname before running checks to avoid leading or trailing spaces that are known to exist in the current test net commit 2bc858cde351cb299c8986fd1045da6d832acbdb Author: Mark Sinclair Date: Fri Aug 27 09:32:26 2021 +0100 explorer-api: port test: split out address resolution and add units tests commit 92e13a5d005d3093717c77332cb5837cec4cfd34 Author: Bogdan-Ștefan Neacşu Date: Tue Aug 31 14:51:15 2021 +0300 Feature/add blockstamp (#756) * Add RawDelegationData * Fix current tests for the new stored data * Added migration commit. Will be reverted after doing the migration * New tests for block height * Use current blockstamp instead of 24h old one * Put _alot_ of migration stuff in the migrate function scope commit 122f5d9f2e5c1ced96e3b9ba0c74ef8b7dbde2c7 Author: Bogdan-Ștefan Neacşu Date: Mon Aug 30 10:27:20 2021 +0300 Feature/cred after handshake (#745) * Call perform_initial_authentication instead of register in clients * Refactor the register/authenticate functions a bit * Introduce Bandwidth request type * Add encryption layer to cred * Remove cred pass and check from handshake * Replaced unreachable! with error * Changed decrypt_tagged signature to not take mutable ownership of data * Put handle_bandwidth work inside a function * Add check before unwrap * Remove unnecessary async * Decouple bandwidth credential from authentication * Use new_error for ServerResponse:Error * Send a fresh IV each time the BandwidthCredential request is sent * Remove unwrap of bincode::serialize * Add comment regarding Bandwidth response * Remove _mut from naming * Leave Debug trait alone, as the initial error doesn't reproduce anymore * Pass iv as Vec instead of base58 string * Renamed AuthenticationIV to IV, as it is now used for more the just authentication * Did some IV refactorization commit 982ee0266c92628909ea0b5691b2abd31950f084 Author: Bogdan-Ștefan Neacşu Date: Fri Aug 27 16:02:34 2021 +0300 Feature/get own delegations (#748) * Introduce reverse delegation bucket * Add client command * Fix clippy error * Added tests in queries * Add tests in transactions * Migration code. Will be reverted after it's called on testnet * Replace unwrap with expect * Move some test code in the right file... ... to remove unnecessary auxiliary function. * Reduce the scope to migration auxiliary functions * Rename everything from [node]reverse to reverse[node] * Fix fmt commit 5f42a9bd052aebc496d639be4cb2aeadc4a575cb Author: Jędrzej Stuczyński Date: Fri Aug 27 13:52:18 2021 +0100 NetworkMonitorBuilder - starting the monitor after rocket has launched (#754) * NetworkMonitorBuilder - starting the monitor after rocket has launched * Removed unused import commit 1811df9ddb696279e2fc4cb58eeb9df55f2b1843 Author: Jędrzej Stuczyński Date: Fri Aug 27 13:52:10 2021 +0100 Enabled validators api argument (#753) commit 6bdfe7f8954177240894945dee86926eb3df08ab Author: Jędrzej Stuczyński Date: Thu Aug 26 11:21:01 2021 +0100 Correctly bounding nominator of uptime calculation (#752) commit c6b286a1dba0dd1a1a1df021cc4e8e45873eed15 Author: Jędrzej Stuczyński Date: Wed Aug 25 14:50:57 2021 +0100 Fixed argument parsing for ipv6 'good' topology (#751) commit b3568a26f57faaffe7b8d20ab90cb4cb456eae5e Author: Bogdan-Ștefan Neacşu Date: Tue Aug 24 11:25:05 2021 +0300 Revert "Migration commit, will be reverted after the testnet contract is updated" (#749) This reverts commit 38d868bcce3738c00d0f60bbc40373efd3cf7c6e. commit 15ae0f521e46336c0e8518e9b749dde9d702077a Author: Jędrzej Stuczyński Date: Mon Aug 23 10:26:51 2021 +0100 Feature/more reliable uptime calculation (#747) * New database table holding monitor run info * SQL interface for new table * Updated uptime calculation to instead rely on number of monitor test runs commit 2923d4b8725f04a0097461852862cf348b0998da Author: Bogdan-Ștefan Neacşu Date: Thu Aug 19 22:03:07 2021 +0300 Update template toml key (#746) --- tauri-wallet/src-tauri/src/coin.rs | 48 +++++++++++++++--------------- tauri-wallet/src-tauri/src/main.rs | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tauri-wallet/src-tauri/src/coin.rs b/tauri-wallet/src-tauri/src/coin.rs index 11a365f6b8..3b035768e7 100644 --- a/tauri-wallet/src-tauri/src/coin.rs +++ b/tauri-wallet/src-tauri/src/coin.rs @@ -11,7 +11,7 @@ use std::fmt; use std::ops::{Add, Sub}; use std::str::FromStr; use ts_rs::TS; -use validator_client::nymd::{GasPrice, CosmosCoin}; +use validator_client::nymd::{CosmosCoin, GasPrice}; use crate::format_err; @@ -96,25 +96,25 @@ impl Add for Coin { // Allows adding minor and major denominations, output will have the LHS denom. impl Sub for Coin { - type Output = Self; - - fn sub(self, rhs: Self) -> Self { - let denom = self.denom.clone(); - let lhs = self.to_minor(); - let rhs = rhs.to_minor(); - let lhs_amount = lhs.amount.parse::().unwrap(); - let rhs_amount = rhs.amount.parse::().unwrap(); - let amount = lhs_amount - rhs_amount; - let coin = Coin { - amount: amount.to_string(), - denom: Denom::Minor, - }; - match denom { - Denom::Major => coin.to_major(), - Denom::Minor => coin, - } + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + let denom = self.denom.clone(); + let lhs = self.to_minor(); + let rhs = rhs.to_minor(); + let lhs_amount = lhs.amount.parse::().unwrap(); + let rhs_amount = rhs.amount.parse::().unwrap(); + let amount = lhs_amount - rhs_amount; + let coin = Coin { + amount: amount.to_string(), + denom: Denom::Minor, + }; + match denom { + Denom::Major => coin.to_major(), + Denom::Minor => coin, } } +} impl Coin { pub fn major(amount: T) -> Coin { @@ -273,7 +273,7 @@ mod test { "1000000000000000", "10000000000000000", "100000000000000000", - "1000000000000000000" + "1000000000000000000", ] } @@ -333,10 +333,10 @@ mod test { #[test] fn test_add() { - assert_eq!(Coin::minor("1") + Coin::minor("1"), Coin::minor("2")); - assert_eq!(Coin::major("1") + Coin::major("1"), Coin::major("2")); - assert_eq!(Coin::minor("1") + Coin::major("1"), Coin::minor("1000001")); - assert_eq!(Coin::major("1") + Coin::minor("1"), Coin::major("1.000001")); + assert_eq!(Coin::minor("1") + Coin::minor("1"), Coin::minor("2")); + assert_eq!(Coin::major("1") + Coin::major("1"), Coin::major("2")); + assert_eq!(Coin::minor("1") + Coin::major("1"), Coin::minor("1000001")); + assert_eq!(Coin::major("1") + Coin::minor("1"), Coin::major("1.000001")); } #[test] @@ -345,5 +345,5 @@ mod test { assert_eq!(Coin::major("1") - Coin::major("1"), Coin::major("0")); assert_eq!(Coin::minor("1") - Coin::major("1"), Coin::minor("-999999")); assert_eq!(Coin::major("1") - Coin::minor("1"), Coin::major("0.999999")); -} + } } diff --git a/tauri-wallet/src-tauri/src/main.rs b/tauri-wallet/src-tauri/src/main.rs index d1b201d33c..ee4e20f5f2 100644 --- a/tauri-wallet/src-tauri/src/main.rs +++ b/tauri-wallet/src-tauri/src/main.rs @@ -16,7 +16,7 @@ use tendermint_rpc::endpoint::broadcast::tx_commit::Response; use tokio::sync::RwLock; use ts_rs::{export, TS}; use validator_client::nymd::fee_helpers::Operation; -use validator_client::nymd::{NymdClient, SigningNymdClient, AccountId, CosmosCoin}; +use validator_client::nymd::{AccountId, CosmosCoin, NymdClient, SigningNymdClient}; mod coin; mod config; From f6c316eea9f980d6c6bb0ca62fd2e12aa9b57441 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Thu, 16 Sep 2021 18:02:15 +0200 Subject: [PATCH 09/16] fix typo --- tauri-wallet/src/routes/sign-in.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tauri-wallet/src/routes/sign-in.tsx b/tauri-wallet/src/routes/sign-in.tsx index cc1a60f262..3b7f397f62 100644 --- a/tauri-wallet/src/routes/sign-in.tsx +++ b/tauri-wallet/src/routes/sign-in.tsx @@ -74,7 +74,7 @@ const SignInContent = ({ showCreateAccount: () => void }) => { const [mnemonic, setMnemonic] = useState( - 'alley mutual arrange escape army vacuum cherry ozone frame steel current smile dad subject primary foster lazy want perfect fury general eye cannon motor' + 'menmonic phrase' ) const [inputError, setInputError] = useState() const [isLoading, setIsLoading] = useState(false) From 085538582bbe930226708403dffb3004d5897e04 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Fri, 17 Sep 2021 10:43:26 +0200 Subject: [PATCH 10/16] Admin functions, reorganize code --- tauri-wallet/src-tauri/src/coin.rs | 2 +- tauri-wallet/src-tauri/src/main.rs | 370 +----------------- tauri-wallet/src-tauri/src/nymd_client.rs | 80 ---- .../src-tauri/src/operations/account.rs | 113 ++++++ .../src-tauri/src/operations/admin.rs | 84 ++++ tauri-wallet/src-tauri/src/operations/bond.rs | 64 +++ .../src-tauri/src/operations/delegate.rs | 94 +++++ tauri-wallet/src-tauri/src/operations/mod.rs | 5 + tauri-wallet/src-tauri/src/operations/send.rs | 69 ++++ tauri-wallet/src-tauri/src/utils.rs | 54 +++ tauri-wallet/src/types/rust/account.ts | 8 + tauri-wallet/src/types/rust/stateparams.ts | 10 + 12 files changed, 519 insertions(+), 434 deletions(-) delete mode 100644 tauri-wallet/src-tauri/src/nymd_client.rs create mode 100644 tauri-wallet/src-tauri/src/operations/account.rs create mode 100644 tauri-wallet/src-tauri/src/operations/admin.rs create mode 100644 tauri-wallet/src-tauri/src/operations/bond.rs create mode 100644 tauri-wallet/src-tauri/src/operations/delegate.rs create mode 100644 tauri-wallet/src-tauri/src/operations/mod.rs create mode 100644 tauri-wallet/src-tauri/src/operations/send.rs create mode 100644 tauri-wallet/src-tauri/src/utils.rs create mode 100644 tauri-wallet/src/types/rust/account.ts create mode 100644 tauri-wallet/src/types/rust/stateparams.ts diff --git a/tauri-wallet/src-tauri/src/coin.rs b/tauri-wallet/src-tauri/src/coin.rs index 3b035768e7..8a7862b80f 100644 --- a/tauri-wallet/src-tauri/src/coin.rs +++ b/tauri-wallet/src-tauri/src/coin.rs @@ -212,7 +212,7 @@ impl From for Coin { #[cfg(test)] mod test { - use crate::{Coin, Denom}; + use crate::coin::{Coin, Denom}; use cosmrs::Coin as CosmosCoin; use cosmrs::Decimal; use cosmrs::Denom as CosmosDenom; diff --git a/tauri-wallet/src-tauri/src/main.rs b/tauri-wallet/src-tauri/src/main.rs index ee4e20f5f2..1b6a7c3ebd 100644 --- a/tauri-wallet/src-tauri/src/main.rs +++ b/tauri-wallet/src-tauri/src/main.rs @@ -3,30 +3,30 @@ windows_subsystem = "windows" )] -use bip39::{Language, Mnemonic}; -use cosmwasm_std::Coin as CosmWasmCoin; -use error::BackendError; use mixnet_contract::{Gateway, MixNode}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::convert::TryInto; -use std::str::FromStr; use std::sync::Arc; -use tendermint_rpc::endpoint::broadcast::tx_commit::Response; use tokio::sync::RwLock; -use ts_rs::{export, TS}; +use ts_rs::export; use validator_client::nymd::fee_helpers::Operation; -use validator_client::nymd::{AccountId, CosmosCoin, NymdClient, SigningNymdClient}; mod coin; mod config; mod error; +mod operations; mod state; +mod utils; + +use crate::operations::account::*; +use crate::operations::admin::*; +use crate::operations::bond::*; +use crate::operations::delegate::*; +use crate::operations::send::*; +use crate::utils::*; use crate::state::State; +#[cfg(test)] use crate::coin::{Coin, Denom}; -use crate::config::Config; #[macro_export] macro_rules! format_err { @@ -35,346 +35,6 @@ macro_rules! format_err { }; } -#[derive(TS, Serialize, Deserialize)] -struct DelegationResult { - source_address: String, - target_address: String, - amount: Option, -} - -#[derive(TS, Serialize, Deserialize)] -struct Balance { - coin: Coin, - printable_balance: String, -} - -#[derive(Deserialize, Serialize, TS)] -struct TauriTxResult { - code: u32, - gas_wanted: u64, - gas_used: u64, - block_height: u64, - details: TransactionDetails, -} - -#[derive(Deserialize, Serialize, TS)] -struct TransactionDetails { - from_address: String, - to_address: String, - amount: Coin, -} - -impl TauriTxResult { - fn new(t: Response, details: TransactionDetails) -> TauriTxResult { - TauriTxResult { - code: t.check_tx.code.value(), - gas_wanted: t.check_tx.gas_wanted.value(), - gas_used: t.check_tx.gas_used.value(), - block_height: t.height.value(), - details, - } - } -} - -// TODO these should be more explicit -#[tauri::command] -fn major_to_minor(amount: &str) -> Result { - let coin = Coin::new(amount, &Denom::Major); - Ok(coin.to_minor()) -} - -#[tauri::command] -fn minor_to_major(amount: &str) -> Result { - let coin = Coin::new(amount, &Denom::Minor); - Ok(coin.to_major()) -} - -#[tauri::command] -async fn connect_with_mnemonic( - mnemonic: String, - state: tauri::State<'_, Arc>>, -) -> Result, String> { - let mnemonic = match Mnemonic::from_str(&mnemonic) { - Ok(mnemonic) => mnemonic, - Err(e) => return Err(BackendError::from(e).to_string()), - }; - let client; - { - let r_state = state.read().await; - client = _connect_with_mnemonic(mnemonic, &r_state.config()); - } - - let mut ret = HashMap::new(); - ret.insert( - "contract_address", - match client.contract_address() { - Ok(address) => address.to_string(), - Err(e) => format_err!(e), - }, - ); - ret.insert("client_address", client.address().to_string()); - ret.insert( - "denom", - match client.denom() { - Ok(denom) => denom.to_string(), - Err(e) => format_err!(e), - }, - ); - let mut w_state = state.write().await; - w_state.set_client(client); - - Ok(ret) -} - -#[tauri::command] -async fn get_balance(state: tauri::State<'_, Arc>>) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - match client.get_balance(client.address()).await { - Ok(Some(coin)) => { - let coin = Coin::new( - &coin.amount.to_string(), - &Denom::from_str(&coin.denom.to_string())?, - ); - Ok(Balance { - coin: coin.clone(), - printable_balance: coin.to_major().to_string(), - }) - } - Ok(None) => Err(format!( - "No balance available for address {}", - client.address() - )), - Err(e) => Err(BackendError::from(e).to_string()), - } -} - -#[tauri::command] -async fn owns_mixnode(state: tauri::State<'_, Arc>>) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - match client.owns_mixnode(client.address()).await { - Ok(o) => Ok(o), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -async fn owns_gateway(state: tauri::State<'_, Arc>>) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - match client.owns_gateway(client.address()).await { - Ok(o) => Ok(o), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -async fn unbond_mixnode(state: tauri::State<'_, Arc>>) -> Result<(), String> { - let r_state = state.read().await; - let client = r_state.client()?; - match client.unbond_mixnode().await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -async fn bond_mixnode( - mixnode: MixNode, - bond: Coin, - state: tauri::State<'_, Arc>>, -) -> Result<(), String> { - let r_state = state.read().await; - let bond: CosmWasmCoin = match bond.try_into() { - Ok(b) => b, - Err(e) => return Err(format_err!(e)), - }; - let client = r_state.client()?; - match client.bond_mixnode(mixnode, bond).await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -async fn delegate_to_mixnode( - identity: &str, - amount: Coin, - state: tauri::State<'_, Arc>>, -) -> Result { - let r_state = state.read().await; - let bond: CosmWasmCoin = match amount.try_into() { - Ok(b) => b, - Err(e) => return Err(format_err!(e)), - }; - let client = r_state.client()?; - match client.delegate_to_mixnode(identity, &bond).await { - Ok(_result) => Ok(DelegationResult { - source_address: client.address().to_string(), - target_address: identity.to_string(), - amount: Some(bond.into()), - }), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -async fn undelegate_from_mixnode( - identity: &str, - state: tauri::State<'_, Arc>>, -) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - match client.remove_mixnode_delegation(identity).await { - Ok(_result) => Ok(DelegationResult { - source_address: client.address().to_string(), - target_address: identity.to_string(), - amount: None, - }), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -async fn delegate_to_gateway( - identity: &str, - amount: Coin, - state: tauri::State<'_, Arc>>, -) -> Result { - let r_state = state.read().await; - let bond: CosmWasmCoin = match amount.try_into() { - Ok(b) => b, - Err(e) => return Err(format_err!(e)), - }; - let client = r_state.client()?; - match client.delegate_to_gateway(identity, &bond).await { - Ok(_result) => Ok(DelegationResult { - source_address: client.address().to_string(), - target_address: identity.to_string(), - amount: Some(bond.into()), - }), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -async fn undelegate_from_gateway( - identity: &str, - state: tauri::State<'_, Arc>>, -) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - match client.remove_gateway_delegation(identity).await { - Ok(_result) => Ok(DelegationResult { - source_address: client.address().to_string(), - target_address: identity.to_string(), - amount: None, - }), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -async fn bond_gateway( - gateway: Gateway, - bond: Coin, - state: tauri::State<'_, Arc>>, -) -> Result<(), String> { - let r_state = state.read().await; - let bond: CosmWasmCoin = match bond.try_into() { - Ok(b) => b, - Err(e) => return Err(format_err!(e)), - }; - let client = r_state.client()?; - match client.bond_gateway(gateway, bond).await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -async fn unbond_gateway(state: tauri::State<'_, Arc>>) -> Result<(), String> { - let r_state = state.read().await; - let client = r_state.client()?; - match client.unbond_gateway().await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -async fn send( - address: &str, - amount: Coin, - memo: String, - state: tauri::State<'_, Arc>>, -) -> Result { - let address = match AccountId::from_str(address) { - Ok(addy) => addy, - Err(e) => return Err(format_err!(e)), - }; - let cosmos_amount: CosmosCoin = match amount.clone().try_into() { - Ok(b) => b, - Err(e) => return Err(format_err!(e)), - }; - let r_state = state.read().await; - let client = r_state.client()?; - match client.send(&address, vec![cosmos_amount], memo).await { - Ok(result) => Ok(TauriTxResult::new( - result, - TransactionDetails { - from_address: client.address().to_string(), - to_address: address.to_string(), - amount, - }, - )), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -async fn get_fee( - operation: Operation, - state: tauri::State<'_, Arc>>, -) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - let fee = client.get_fee(operation); - let mut coin = Coin::new("0", &Denom::Major); - for f in fee.amount { - coin = coin + f.into(); - } - - Ok(coin) -} - -#[tauri::command] -async fn create_new_account( - state: tauri::State<'_, Arc>>, -) -> Result, String> { - let mnemonic = random_mnemonic(); - let mut client = connect_with_mnemonic(mnemonic.to_string(), state).await?; - client.insert("mnemonic", mnemonic.to_string()); - Ok(client) -} - -fn random_mnemonic() -> Mnemonic { - let mut rng = rand::thread_rng(); - Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap() -} - -fn _connect_with_mnemonic(mnemonic: Mnemonic, config: &Config) -> NymdClient { - match NymdClient::connect_with_mnemonic( - config.get_nymd_validator_url().unwrap(), - Some(AccountId::from_str(&config.get_mixnet_contract_address()).unwrap()), - mnemonic, - ) { - Ok(client) => client, - Err(e) => panic!("{}", e), - } -} - fn main() { tauri::Builder::default() .manage(Arc::new(RwLock::new(State::default()))) @@ -395,7 +55,9 @@ fn main() { undelegate_from_gateway, send, create_new_account, - get_fee + get_fee, + get_state_params, + update_state_params ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); @@ -410,5 +72,7 @@ export! { TransactionDetails => "../src/types/rust/transactiondetails.ts", Operation => "../src/types/rust/operation.ts", Denom => "../src/types/rust/denom.ts", - DelegationResult => "../src/types/rust/delegationresult.ts" + DelegationResult => "../src/types/rust/delegationresult.ts", + Account => "../src/types/rust/account.ts", + TauriStateParams => "../src/types/rust/stateparams.ts" } diff --git a/tauri-wallet/src-tauri/src/nymd_client.rs b/tauri-wallet/src-tauri/src/nymd_client.rs deleted file mode 100644 index 08e0ac5929..0000000000 --- a/tauri-wallet/src-tauri/src/nymd_client.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::config::Config; -use config::defaults::DEFAULT_VALIDATOR_API_PORT; -use mixnet_contract::{GatewayBond, MixNodeBond}; -use std::sync::Arc; -use tokio::sync::RwLock; -use validator_client::nymd::{CosmWasmClient, QueryNymdClient, SigningNymdClient, NymdClient}; -use validator_client::ValidatorClientError; - -#[derive(Clone)] -pub(crate) struct Client(Arc>>); - -impl Client { - pub(crate) fn new_query(config: &Config) -> Self { - // the api address is irrelevant here as **WE ARE THE API** - let api_url = format!("http://localhost:{}", DEFAULT_VALIDATOR_API_PORT) - .parse() - .unwrap(); - let nymd_url = config.get_nymd_validator_url(); - - let mixnet_contract = config - .get_mixnet_contract_address() - .parse() - .expect("the mixnet contract address is invalid!"); - - let client_config = validator_client::Config::new(nymd_url, api_url, Some(mixnet_contract)); - let inner = - validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"); - - Client(Arc::new(RwLock::new(inner))) - } -} - -impl Client { - pub(crate) fn new_signing(config: &Config) -> Self { - // the api address is irrelevant here as **WE ARE THE API** - let api_url = format!("http://localhost:{}", DEFAULT_VALIDATOR_API_PORT) - .parse() - .unwrap(); - let nymd_url = config.get_nymd_validator_url(); - - let mixnet_contract = config - .get_mixnet_contract_address() - .parse() - .expect("the mixnet contract address is invalid!"); - let mnemonic = config - .get_mnemonic() - .parse() - .expect("the mnemonic is invalid!"); - - let client_config = validator_client::Config::new(nymd_url, api_url, Some(mixnet_contract)); - let inner = validator_client::Client::new_signing(client_config, mnemonic) - .expect("Failed to connect to nymd!"); - - Client(Arc::new(RwLock::new(inner))) - } -} - -impl Client { - pub(crate) async fn get_mixnodes(&self) -> Result, ValidatorClientError> - where - C: CosmWasmClient + Sync, - { - self.0.read().await.get_all_nymd_mixnodes().await - } - - pub(crate) async fn get_gateways(&self) -> Result, ValidatorClientError> - where - C: CosmWasmClient + Sync, - { - self.0.read().await.get_all_nymd_gateways().await - } - - #[allow(dead_code)] - pub(crate) async fn some_rewarding_stuff_here(&self) { - todo!() - } -} diff --git a/tauri-wallet/src-tauri/src/operations/account.rs b/tauri-wallet/src-tauri/src/operations/account.rs new file mode 100644 index 0000000000..95416fcb0d --- /dev/null +++ b/tauri-wallet/src-tauri/src/operations/account.rs @@ -0,0 +1,113 @@ +use crate::coin::{Coin, Denom}; +use crate::config::Config; +use crate::error::BackendError; +use crate::format_err; +use crate::state::State; +use bip39::{Language, Mnemonic}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use std::sync::Arc; +use tokio::sync::RwLock; +use ts_rs::TS; +use validator_client::nymd::{AccountId, NymdClient, SigningNymdClient}; + +#[derive(TS, Serialize, Deserialize)] +pub struct Account { + contract_address: String, + client_address: String, + denom: Denom, + mnemonmic: Option, +} + +#[derive(TS, Serialize, Deserialize)] +pub struct Balance { + coin: Coin, + printable_balance: String, +} + +#[tauri::command] +pub async fn connect_with_mnemonic( + mnemonic: String, + state: tauri::State<'_, Arc>>, +) -> Result { + let mnemonic = match Mnemonic::from_str(&mnemonic) { + Ok(mnemonic) => mnemonic, + Err(e) => return Err(BackendError::from(e).to_string()), + }; + let client; + { + let r_state = state.read().await; + client = _connect_with_mnemonic(mnemonic, &r_state.config()); + } + + let contract_address = match client.contract_address() { + Ok(address) => address.to_string(), + Err(e) => return Err(format_err!(e)), + }; + let client_address = client.address().to_string(); + let denom = match client.denom() { + Ok(denom) => denom, + Err(e) => return Err(format_err!(e)), + }; + + let account = Account { + contract_address, + client_address, + denom: Denom::from_str(&denom.to_string())?, + mnemonmic: None, + }; + + let mut w_state = state.write().await; + w_state.set_client(client); + + Ok(account) +} + +#[tauri::command] +pub async fn get_balance(state: tauri::State<'_, Arc>>) -> Result { + let r_state = state.read().await; + let client = r_state.client()?; + match client.get_balance(client.address()).await { + Ok(Some(coin)) => { + let coin = Coin::new( + &coin.amount.to_string(), + &Denom::from_str(&coin.denom.to_string())?, + ); + Ok(Balance { + coin: coin.clone(), + printable_balance: coin.to_major().to_string(), + }) + } + Ok(None) => Err(format!( + "No balance available for address {}", + client.address() + )), + Err(e) => Err(BackendError::from(e).to_string()), + } +} + +#[tauri::command] +pub async fn create_new_account( + state: tauri::State<'_, Arc>>, +) -> Result { + let mnemonic = random_mnemonic(); + let mut client = connect_with_mnemonic(mnemonic.to_string(), state).await?; + client.mnemonmic = Some(mnemonic.to_string()); + Ok(client) +} + +fn random_mnemonic() -> Mnemonic { + let mut rng = rand::thread_rng(); + Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap() +} + +fn _connect_with_mnemonic(mnemonic: Mnemonic, config: &Config) -> NymdClient { + match NymdClient::connect_with_mnemonic( + config.get_nymd_validator_url().unwrap(), + Some(AccountId::from_str(&config.get_mixnet_contract_address()).unwrap()), + mnemonic, + ) { + Ok(client) => client, + Err(e) => panic!("{}", e), + } +} diff --git a/tauri-wallet/src-tauri/src/operations/admin.rs b/tauri-wallet/src-tauri/src/operations/admin.rs new file mode 100644 index 0000000000..8e4f832936 --- /dev/null +++ b/tauri-wallet/src-tauri/src/operations/admin.rs @@ -0,0 +1,84 @@ +use crate::format_err; +use crate::state::State; +use cosmwasm_std::Decimal; +use cosmwasm_std::Uint128; +use mixnet_contract::StateParams; +use serde::{Deserialize, Serialize}; +use std::convert::{TryFrom, TryInto}; +use std::str::FromStr; +use std::sync::Arc; +use tokio::sync::RwLock; +use ts_rs::TS; + +#[derive(Serialize, Deserialize, TS)] +pub struct TauriStateParams { + epoch_length: u32, + minimum_mixnode_bond: String, + minimum_gateway_bond: String, + mixnode_bond_reward_rate: String, + gateway_bond_reward_rate: String, + mixnode_delegation_reward_rate: String, + gateway_delegation_reward_rate: String, + mixnode_active_set_size: u32, +} + +impl From for TauriStateParams { + fn from(p: StateParams) -> TauriStateParams { + TauriStateParams { + epoch_length: p.epoch_length, + minimum_mixnode_bond: p.minimum_mixnode_bond.to_string(), + minimum_gateway_bond: p.minimum_gateway_bond.to_string(), + mixnode_bond_reward_rate: p.mixnode_bond_reward_rate.to_string(), + gateway_bond_reward_rate: p.gateway_bond_reward_rate.to_string(), + mixnode_delegation_reward_rate: p.mixnode_delegation_reward_rate.to_string(), + gateway_delegation_reward_rate: p.gateway_delegation_reward_rate.to_string(), + mixnode_active_set_size: p.mixnode_active_set_size, + } + } +} + +impl TryFrom for StateParams { + type Error = Box; + + fn try_from(p: TauriStateParams) -> Result { + Ok(StateParams { + epoch_length: p.epoch_length, + minimum_mixnode_bond: Uint128::try_from(p.minimum_mixnode_bond.as_str())?, + minimum_gateway_bond: Uint128::try_from(p.minimum_gateway_bond.as_str())?, + mixnode_bond_reward_rate: Decimal::from_str(p.mixnode_bond_reward_rate.as_str())?, + gateway_bond_reward_rate: Decimal::from_str(p.gateway_bond_reward_rate.as_str())?, + mixnode_delegation_reward_rate: Decimal::from_str(p.mixnode_delegation_reward_rate.as_str())?, + gateway_delegation_reward_rate: Decimal::from_str(p.gateway_delegation_reward_rate.as_str())?, + mixnode_active_set_size: p.mixnode_active_set_size, + }) + } +} + +#[tauri::command] +pub async fn get_state_params( + state: tauri::State<'_, Arc>>, +) -> Result { + let r_state = state.read().await; + let client = r_state.client()?; + match client.get_state_params().await { + Ok(params) => Ok(params.into()), + Err(e) => Err(format_err!(e)), + } +} + +#[tauri::command] +pub async fn update_state_params( + params: TauriStateParams, + state: tauri::State<'_, Arc>>, +) -> Result { + let r_state = state.read().await; + let client = r_state.client()?; + let state_params: StateParams = match params.try_into() { + Ok(state_params) => state_params, + Err(e) => return Err(format_err!(e)), + }; + match client.update_state_params(state_params.clone()).await { + Ok(_) => Ok(state_params.into()), + Err(e) => Err(format_err!(e)), + } +} diff --git a/tauri-wallet/src-tauri/src/operations/bond.rs b/tauri-wallet/src-tauri/src/operations/bond.rs new file mode 100644 index 0000000000..b7046feef2 --- /dev/null +++ b/tauri-wallet/src-tauri/src/operations/bond.rs @@ -0,0 +1,64 @@ +use crate::coin::Coin; +use crate::format_err; +use crate::state::State; +use crate::{Gateway, MixNode}; +use cosmwasm_std::Coin as CosmWasmCoin; +use std::convert::TryInto; +use std::sync::Arc; +use tokio::sync::RwLock; + +#[tauri::command] +pub async fn bond_gateway( + gateway: Gateway, + bond: Coin, + state: tauri::State<'_, Arc>>, +) -> Result<(), String> { + let r_state = state.read().await; + let bond: CosmWasmCoin = match bond.try_into() { + Ok(b) => b, + Err(e) => return Err(format_err!(e)), + }; + let client = r_state.client()?; + match client.bond_gateway(gateway, bond).await { + Ok(_result) => Ok(()), + Err(e) => Err(format_err!(e)), + } +} + +#[tauri::command] +pub async fn unbond_gateway(state: tauri::State<'_, Arc>>) -> Result<(), String> { + let r_state = state.read().await; + let client = r_state.client()?; + match client.unbond_gateway().await { + Ok(_result) => Ok(()), + Err(e) => Err(format_err!(e)), + } +} + +#[tauri::command] +pub async fn unbond_mixnode(state: tauri::State<'_, Arc>>) -> Result<(), String> { + let r_state = state.read().await; + let client = r_state.client()?; + match client.unbond_mixnode().await { + Ok(_result) => Ok(()), + Err(e) => Err(format_err!(e)), + } +} + +#[tauri::command] +pub async fn bond_mixnode( + mixnode: MixNode, + bond: Coin, + state: tauri::State<'_, Arc>>, +) -> Result<(), String> { + let r_state = state.read().await; + let bond: CosmWasmCoin = match bond.try_into() { + Ok(b) => b, + Err(e) => return Err(format_err!(e)), + }; + let client = r_state.client()?; + match client.bond_mixnode(mixnode, bond).await { + Ok(_result) => Ok(()), + Err(e) => Err(format_err!(e)), + } +} diff --git a/tauri-wallet/src-tauri/src/operations/delegate.rs b/tauri-wallet/src-tauri/src/operations/delegate.rs new file mode 100644 index 0000000000..9438b5e7c6 --- /dev/null +++ b/tauri-wallet/src-tauri/src/operations/delegate.rs @@ -0,0 +1,94 @@ +use crate::coin::Coin; +use crate::format_err; +use crate::state::State; +use cosmwasm_std::Coin as CosmWasmCoin; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; +use std::sync::Arc; +use tokio::sync::RwLock; +use ts_rs::TS; + +#[derive(TS, Serialize, Deserialize)] +pub struct DelegationResult { + source_address: String, + target_address: String, + amount: Option, +} + +#[tauri::command] +pub async fn delegate_to_mixnode( + identity: &str, + amount: Coin, + state: tauri::State<'_, Arc>>, +) -> Result { + let r_state = state.read().await; + let bond: CosmWasmCoin = match amount.try_into() { + Ok(b) => b, + Err(e) => return Err(format_err!(e)), + }; + let client = r_state.client()?; + match client.delegate_to_mixnode(identity, &bond).await { + Ok(_result) => Ok(DelegationResult { + source_address: client.address().to_string(), + target_address: identity.to_string(), + amount: Some(bond.into()), + }), + Err(e) => Err(format_err!(e)), + } +} + +#[tauri::command] +pub async fn undelegate_from_mixnode( + identity: &str, + state: tauri::State<'_, Arc>>, +) -> Result { + let r_state = state.read().await; + let client = r_state.client()?; + match client.remove_mixnode_delegation(identity).await { + Ok(_result) => Ok(DelegationResult { + source_address: client.address().to_string(), + target_address: identity.to_string(), + amount: None, + }), + Err(e) => Err(format_err!(e)), + } +} + +#[tauri::command] +pub async fn delegate_to_gateway( + identity: &str, + amount: Coin, + state: tauri::State<'_, Arc>>, +) -> Result { + let r_state = state.read().await; + let bond: CosmWasmCoin = match amount.try_into() { + Ok(b) => b, + Err(e) => return Err(format_err!(e)), + }; + let client = r_state.client()?; + match client.delegate_to_gateway(identity, &bond).await { + Ok(_result) => Ok(DelegationResult { + source_address: client.address().to_string(), + target_address: identity.to_string(), + amount: Some(bond.into()), + }), + Err(e) => Err(format_err!(e)), + } +} + +#[tauri::command] +pub async fn undelegate_from_gateway( + identity: &str, + state: tauri::State<'_, Arc>>, +) -> Result { + let r_state = state.read().await; + let client = r_state.client()?; + match client.remove_gateway_delegation(identity).await { + Ok(_result) => Ok(DelegationResult { + source_address: client.address().to_string(), + target_address: identity.to_string(), + amount: None, + }), + Err(e) => Err(format_err!(e)), + } +} diff --git a/tauri-wallet/src-tauri/src/operations/mod.rs b/tauri-wallet/src-tauri/src/operations/mod.rs new file mode 100644 index 0000000000..47f4788d6c --- /dev/null +++ b/tauri-wallet/src-tauri/src/operations/mod.rs @@ -0,0 +1,5 @@ +pub mod account; +pub mod admin; +pub mod bond; +pub mod delegate; +pub mod send; diff --git a/tauri-wallet/src-tauri/src/operations/send.rs b/tauri-wallet/src-tauri/src/operations/send.rs new file mode 100644 index 0000000000..bf223a9c39 --- /dev/null +++ b/tauri-wallet/src-tauri/src/operations/send.rs @@ -0,0 +1,69 @@ +use crate::coin::Coin; +use crate::format_err; +use crate::state::State; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; +use std::str::FromStr; +use std::sync::Arc; +use tendermint_rpc::endpoint::broadcast::tx_commit::Response; +use tokio::sync::RwLock; +use ts_rs::TS; +use validator_client::nymd::{AccountId, CosmosCoin}; + +#[derive(Deserialize, Serialize, TS)] +pub struct TauriTxResult { + code: u32, + gas_wanted: u64, + gas_used: u64, + block_height: u64, + details: TransactionDetails, +} + +#[derive(Deserialize, Serialize, TS)] +pub struct TransactionDetails { + from_address: String, + to_address: String, + amount: Coin, +} + +impl TauriTxResult { + fn new(t: Response, details: TransactionDetails) -> TauriTxResult { + TauriTxResult { + code: t.check_tx.code.value(), + gas_wanted: t.check_tx.gas_wanted.value(), + gas_used: t.check_tx.gas_used.value(), + block_height: t.height.value(), + details, + } + } +} + +#[tauri::command] +pub async fn send( + address: &str, + amount: Coin, + memo: String, + state: tauri::State<'_, Arc>>, +) -> Result { + let address = match AccountId::from_str(address) { + Ok(addy) => addy, + Err(e) => return Err(format_err!(e)), + }; + let cosmos_amount: CosmosCoin = match amount.clone().try_into() { + Ok(b) => b, + Err(e) => return Err(format_err!(e)), + }; + let r_state = state.read().await; + let client = r_state.client()?; + match client.send(&address, vec![cosmos_amount], memo).await { + Ok(result) => Ok(TauriTxResult::new( + result, + TransactionDetails { + from_address: client.address().to_string(), + to_address: address.to_string(), + amount, + }, + )), + Err(e) => Err(format_err!(e)), + } +} diff --git a/tauri-wallet/src-tauri/src/utils.rs b/tauri-wallet/src-tauri/src/utils.rs new file mode 100644 index 0000000000..21a048a60c --- /dev/null +++ b/tauri-wallet/src-tauri/src/utils.rs @@ -0,0 +1,54 @@ +use crate::coin::{Coin, Denom}; +use crate::format_err; +use crate::state::State; +use crate::Operation; +use std::sync::Arc; +use tokio::sync::RwLock; + +#[tauri::command] +pub fn major_to_minor(amount: &str) -> Result { + let coin = Coin::new(amount, &Denom::Major); + Ok(coin.to_minor()) +} + +#[tauri::command] +pub fn minor_to_major(amount: &str) -> Result { + let coin = Coin::new(amount, &Denom::Minor); + Ok(coin.to_major()) +} + +#[tauri::command] +pub async fn owns_mixnode(state: tauri::State<'_, Arc>>) -> Result { + let r_state = state.read().await; + let client = r_state.client()?; + match client.owns_mixnode(client.address()).await { + Ok(o) => Ok(o), + Err(e) => Err(format_err!(e)), + } +} + +#[tauri::command] +pub async fn owns_gateway(state: tauri::State<'_, Arc>>) -> Result { + let r_state = state.read().await; + let client = r_state.client()?; + match client.owns_gateway(client.address()).await { + Ok(o) => Ok(o), + Err(e) => Err(format_err!(e)), + } +} + +#[tauri::command] +pub async fn get_fee( + operation: Operation, + state: tauri::State<'_, Arc>>, +) -> Result { + let r_state = state.read().await; + let client = r_state.client()?; + let fee = client.get_fee(operation); + let mut coin = Coin::new("0", &Denom::Major); + for f in fee.amount { + coin = coin + f.into(); + } + + Ok(coin) +} diff --git a/tauri-wallet/src/types/rust/account.ts b/tauri-wallet/src/types/rust/account.ts new file mode 100644 index 0000000000..d826983913 --- /dev/null +++ b/tauri-wallet/src/types/rust/account.ts @@ -0,0 +1,8 @@ +import { Denom } from "./denom"; + +export interface Account { + contract_address: string; + client_address: string; + denom: Denom; + mnemonmic: string | null; +} \ No newline at end of file diff --git a/tauri-wallet/src/types/rust/stateparams.ts b/tauri-wallet/src/types/rust/stateparams.ts new file mode 100644 index 0000000000..f0c18aa83e --- /dev/null +++ b/tauri-wallet/src/types/rust/stateparams.ts @@ -0,0 +1,10 @@ +export interface TauriStateParams { + epoch_length: number; + minimum_mixnode_bond: string; + minimum_gateway_bond: string; + mixnode_bond_reward_rate: string; + gateway_bond_reward_rate: string; + mixnode_delegation_reward_rate: string; + gateway_delegation_reward_rate: string; + mixnode_active_set_size: number; +} \ No newline at end of file From 27a202cbe827724f55d8b875ee5f2d6e2c5f2e47 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 17 Sep 2021 11:32:41 +0100 Subject: [PATCH 11/16] integrate admin functions updates integrate admin form update function --- tauri-wallet/src/components/AdminForm.tsx | 120 ++++++++++++++-------- tauri-wallet/src/requests/index.ts | 8 ++ tauri-wallet/src/types/rust/index.ts | 2 + 3 files changed, 86 insertions(+), 44 deletions(-) diff --git a/tauri-wallet/src/components/AdminForm.tsx b/tauri-wallet/src/components/AdminForm.tsx index 9c11370ae0..f3ac868a72 100644 --- a/tauri-wallet/src/components/AdminForm.tsx +++ b/tauri-wallet/src/components/AdminForm.tsx @@ -1,7 +1,8 @@ -import React, { useContext } from 'react' +import React, { useContext, useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { Backdrop, + Box, Button, CircularProgress, FormControl, @@ -14,20 +15,45 @@ import { import { useTheme } from '@material-ui/styles' import { ClientContext } from '../context/main' import { NymCard } from '.' +import { getContractParams, setContractParams } from '../requests' +import { TauriStateParams } from '../types' export const Admin: React.FC = () => { const { showAdmin, handleShowAdmin } = useContext(ClientContext) + const [isLoading, setIsLoading] = useState(false) + const [params, setParams] = useState() const onCancel = () => { + setParams(undefined) + setIsLoading(false) handleShowAdmin() } + useEffect(() => { + const requestContractParams = async () => { + if (showAdmin) { + setIsLoading(true) + const params = await getContractParams() + setParams(params) + setIsLoading(false) + } + } + requestContractParams() + }, [showAdmin]) + return ( - + {isLoading && ( + + + + )} + {!isLoading && params && ( + + )} @@ -35,14 +61,18 @@ export const Admin: React.FC = () => { ) } -const AdminForm: React.FC<{ onCancel: () => void }> = ({ onCancel }) => { +const AdminForm: React.FC<{ + params: TauriStateParams + onCancel: () => void +}> = ({ params, onCancel }) => { const { register, handleSubmit, formState: { errors, isSubmitting }, - } = useForm() + } = useForm({ defaultValues: { ...params } }) - const onSubmit = (data: any) => { + const onSubmit = async (data: TauriStateParams) => { + await setContractParams(data) console.log(data) onCancel() } @@ -51,110 +81,112 @@ const AdminForm: React.FC<{ onCancel: () => void }> = ({ onCancel }) => { return ( -
+
diff --git a/tauri-wallet/src/requests/index.ts b/tauri-wallet/src/requests/index.ts index ccf86222a6..ed94e7c503 100644 --- a/tauri-wallet/src/requests/index.ts +++ b/tauri-wallet/src/requests/index.ts @@ -7,6 +7,7 @@ import { Gateway, MixNode, Operation, + TauriStateParams, TauriTxResult, TCreateAccount, TSignInWithMnemonic, @@ -75,3 +76,10 @@ export const unbond = async (type: EnumNodeType) => export const getBalance = async (): Promise => await invoke('get_balance') + +export const getContractParams = async (): Promise => + await invoke('get_state_params') + +export const setContractParams = async ( + params: TauriStateParams +): Promise => await invoke('update_state_params', { params }) diff --git a/tauri-wallet/src/types/rust/index.ts b/tauri-wallet/src/types/rust/index.ts index 74ca39a7df..8e22182af6 100644 --- a/tauri-wallet/src/types/rust/index.ts +++ b/tauri-wallet/src/types/rust/index.ts @@ -1,3 +1,4 @@ +export * from './account' export * from './balance' export * from './coin' export * from './delegationresult' @@ -5,5 +6,6 @@ export * from './denom' export * from './gateway' export * from './mixnode' export * from './operation' +export * from './stateparams' export * from './tauritxresult' export * from './transactiondetails' From 21b008fae989477c728abdc62cc19e8c76db45cc Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 13 Sep 2021 13:30:05 +0100 Subject: [PATCH 12/16] use wrapper functions for send and delegate form updates update working finish bonding and unbonding setup funds allocation check when bonding/sending/delegating update title --- tauri-wallet/src/routes/sign-in.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tauri-wallet/src/routes/sign-in.tsx b/tauri-wallet/src/routes/sign-in.tsx index 3b7f397f62..52b2c011fe 100644 --- a/tauri-wallet/src/routes/sign-in.tsx +++ b/tauri-wallet/src/routes/sign-in.tsx @@ -73,9 +73,7 @@ const SignInContent = ({ }: { showCreateAccount: () => void }) => { - const [mnemonic, setMnemonic] = useState( - 'menmonic phrase' - ) + const [mnemonic, setMnemonic] = useState('menmonic phrase') const [inputError, setInputError] = useState() const [isLoading, setIsLoading] = useState(false) From 56e07753eace256de9a72736d4480e909c4bde1d Mon Sep 17 00:00:00 2001 From: max Date: Wed, 22 Sep 2021 14:44:22 +0200 Subject: [PATCH 13/16] added minimal readme for wallet --- tauri-wallet/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tauri-wallet/README.md diff --git a/tauri-wallet/README.md b/tauri-wallet/README.md new file mode 100644 index 0000000000..db954805b7 --- /dev/null +++ b/tauri-wallet/README.md @@ -0,0 +1,19 @@ + + +# Nym Tauri Wallet + +A Rust and Tauri desktop wallet implementation. + +## Installation prerequisites + +* `Yarn` +* `NodeJS >= v16.8.0` +* `Rust & cargo >= v1.51` + +## Installation & usage + +* `yarn install` +* `yarn dev` From 7318de23f2fe9ebba5a92c8b74f4aa0845b21f3b Mon Sep 17 00:00:00 2001 From: max Date: Wed, 22 Sep 2021 18:07:29 +0200 Subject: [PATCH 14/16] added info on nym-wallet (tauri) in readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f1e797ac9c..b5e263d4aa 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr * nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices. * nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing"). * nym-explorer - a (projected) block explorer and (existing) mixnet viewer. +* nym-wallet (currently in development)- a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework. [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) [![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop) From de601c319a7a9cfc9dbed5982d53e222ab3ac5cb Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 23 Sep 2021 17:06:47 +0100 Subject: [PATCH 15/16] PR updates --- tauri-wallet/src/hooks/useGetBalance.tsx | 6 +++--- tauri-wallet/src/routes/sign-in.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tauri-wallet/src/hooks/useGetBalance.tsx b/tauri-wallet/src/hooks/useGetBalance.tsx index 013e9bd550..5d69944640 100644 --- a/tauri-wallet/src/hooks/useGetBalance.tsx +++ b/tauri-wallet/src/hooks/useGetBalance.tsx @@ -11,17 +11,17 @@ export type TUseGetBalance = { export const useGetBalance = (): TUseGetBalance => { const [balance, setBalance] = useState() - const [error, setErorr] = useState() + const [error, setError] = useState() const [isLoading, setIsLoading] = useState(false) const fetchBalance = () => { setIsLoading(true) - setErorr(undefined) + setError(undefined) invoke('get_balance') .then((balance) => { setBalance(balance as Balance) }) - .catch((e) => setErorr(e)) + .catch((e) => setError(e)) setTimeout(() => { setIsLoading(false) }, 1000) diff --git a/tauri-wallet/src/routes/sign-in.tsx b/tauri-wallet/src/routes/sign-in.tsx index 52b2c011fe..6ef052ef41 100644 --- a/tauri-wallet/src/routes/sign-in.tsx +++ b/tauri-wallet/src/routes/sign-in.tsx @@ -73,7 +73,7 @@ const SignInContent = ({ }: { showCreateAccount: () => void }) => { - const [mnemonic, setMnemonic] = useState('menmonic phrase') + const [mnemonic, setMnemonic] = useState('') const [inputError, setInputError] = useState() const [isLoading, setIsLoading] = useState(false) From 46149012bd3d00125548c7af657329cc540f656a Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 23 Sep 2021 17:42:34 +0100 Subject: [PATCH 16/16] PR updates --- tauri-wallet/src/hooks/useGetBalance.tsx | 2 +- tauri-wallet/src/routes/delegate/DelegateForm.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tauri-wallet/src/hooks/useGetBalance.tsx b/tauri-wallet/src/hooks/useGetBalance.tsx index 5d69944640..8325cbb736 100644 --- a/tauri-wallet/src/hooks/useGetBalance.tsx +++ b/tauri-wallet/src/hooks/useGetBalance.tsx @@ -21,7 +21,7 @@ export const useGetBalance = (): TUseGetBalance => { .then((balance) => { setBalance(balance as Balance) }) - .catch((e) => setError(e)) + .catch(setError) setTimeout(() => { setIsLoading(false) }, 1000) diff --git a/tauri-wallet/src/routes/delegate/DelegateForm.tsx b/tauri-wallet/src/routes/delegate/DelegateForm.tsx index d4c0ae7887..fb32dc2c44 100644 --- a/tauri-wallet/src/routes/delegate/DelegateForm.tsx +++ b/tauri-wallet/src/routes/delegate/DelegateForm.tsx @@ -11,7 +11,7 @@ import { } from '@material-ui/core' import { useForm } from 'react-hook-form' import { NodeTypeSelector } from '../../components/NodeTypeSelector' -import { DelegationResult, EnumNodeType, TFee } from '../../types' +import { EnumNodeType, TFee } from '../../types' import { yupResolver } from '@hookform/resolvers/yup' import { validationSchema } from './validationSchema' import { Alert } from '@material-ui/lab' @@ -93,6 +93,7 @@ export const DelegateForm = ({ setValue('nodeType', nodeType)} + disabled={isSubmitting} />