Skip to content

Commit

Permalink
Merge pull request #384 from Concordium/p8/suspend-resume-validator
Browse files Browse the repository at this point in the history
P8/suspend resume validator
  • Loading branch information
soerenbf authored Feb 13, 2025
2 parents 121997c + 6b043c5 commit f14af37
Show file tree
Hide file tree
Showing 25 changed files with 447 additions and 105 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Account validation/delegation overview now shows if the target pool is suspended or primed for suspension
- A notification is shown for accounts validating/delegating to a pool which is suspended
- Support changing the suspension status of validators. This requires the corresponding version of the concordium ledger app which also adds support for this feature.

## 1.7.4

Expand Down
3 changes: 1 addition & 2 deletions app/components/BakerTransactions/BakerStakeSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ export default function BakerStakeSettings({
Enter your new desired amount to stake. If you
increase the stake it will take effect at the next
pay day, and if you decrease the stake it will take
effect at the first pay day after a cool-down
period.
effect at the first pay day after a cooldown period.
</p>
)}
{pendingChange !== undefined && (
Expand Down
11 changes: 10 additions & 1 deletion app/components/Transfers/DisplayConfigureBaker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import DisplayPublicKey from './DisplayPublicKey';
import {
displayPoolOpen,
displayRestakeEarnings,
displaySuspension,
} from '~/utils/transactionFlows/configureBaker';
import DisplayBakerCommission from './DisplayBakerCommission';
import DisplayMetadataUrl from './DisplayMetadataUrl';
Expand All @@ -37,7 +38,7 @@ export default function DisplayConfigureBaker({ transaction }: Props) {
/>
<DisplayFee className={styles.fee} transaction={transaction} />
{payload.stake !== undefined &&
(payload.stake === 0n ? (
(payload.stake.microCcdAmount === 0n ? (
<>
<h5 className={styles.title}>Stop Validation</h5>
<h5 className={styles.subtitle}>(Stop baking)</h5>
Expand Down Expand Up @@ -66,6 +67,14 @@ export default function DisplayConfigureBaker({ transaction }: Props) {
</p>
</>
)}
{payload.suspended !== undefined && (
<>
<h5 className={styles.title}>Suspension status:</h5>
<p className={styles.amount}>
{displaySuspension(payload.suspended)}
</p>
</>
)}
<DisplayBakerCommission
title="Transaction fee commission"
value={transaction.payload.transactionFeeCommission}
Expand Down
32 changes: 32 additions & 0 deletions app/components/Transfers/configureBaker/BakerSuspensionPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { MultiStepFormPageProps } from '~/components/MultiStepForm';
import Button from '~/cross-app-components/Button';
import { AccountInfo } from '~/utils/types';
import { withPendingBakerChangeGuard } from './util';

import styles from './ConfigureBakerPage.module.scss';

type Props = Pick<MultiStepFormPageProps<boolean>, 'onNext'> & {
accountInfo: AccountInfo | undefined;
isSuspended: boolean;
};

const BakerSuspensionPage = withPendingBakerChangeGuard(
({ onNext, isSuspended }: Props) => (
<>
<p className="flexChildFill">
Submitting this transaction will{' '}
{isSuspended ? 'resume' : 'suspend'} validation for the selected
validator account.
</p>
<Button
onClick={() => onNext(!isSuspended)}
className={styles.continue}
>
Continue
</Button>
</>
)
);

export default BakerSuspensionPage;
4 changes: 2 additions & 2 deletions app/components/Transfers/configureBaker/RemoveBakerPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ const RemoveBakerPage = withPendingBakerChangeGuard(
<>
<p className="flexChildFill">
This will return the staked amount to the public balance of
the account at the first pay day after a cool-down period.
the account at the first pay day after a cooldown period.
{cooldownUntil && (
<>
<br />
<br />
The cool-down period ends at
The cooldown period ends at
<span className="block bodyEmphasized mV10">
{getFormattedDateString(cooldownUntil)}.
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ export default function DelegationAmountPage({
Enter your new desired amount to stake. If you increase
the stake it will take effect at the next pay day, and
if you decrease the stake it will take effect at the
first pay day after a cool-down period.
first pay day after a cooldown period.
</p>
)}
{pendingChange !== undefined && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@ const RemoveDelegationPage = withPendingDelegationChangeGuard(
<>
<p className="flexChildFill">
This will return the delegated amount to the public balance
of the account at the first pay day after a cool-down
period.
of the account at the first pay day after a cooldown period.
{cooldownUntil && (
<>
<br />
<br />
The cool-down period ends at
The cooldown period ends at
<span className="block bodyEmphasized mV10">
{getFormattedDateString(cooldownUntil)}.
</span>
Expand Down
2 changes: 2 additions & 0 deletions app/constants/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"ACCOUNTS_REMOVE_BAKER": "/accounts/baking/remove",
"ACCOUNTS_UPDATE_BAKER_STAKE": "/accounts/baking/update-stake",
"ACCOUNTS_UPDATE_BAKER_POOL": "/accounts/baking/update-pool",
"ACCOUNTS_UPDATE_SUSPENSION": "/accounts/baking/update-suspension",
"ACCOUNTS_DELEGATION": "/accounts/delegation",
"ACCOUNTS_ADD_DELEGATION": "/accounts/delegation/add",
"ACCOUNTS_UPDATE_DELEGATION": "/accounts/delegation/update",
Expand Down Expand Up @@ -79,6 +80,7 @@
"MULTISIGTRANSACTIONS_UPDATE_BAKER_STAKE": "/multi-signature-transaction/account-transaction/update-baker-stake",
"MULTISIGTRANSACTIONS_UPDATE_BAKER_POOL": "/multi-signature-transaction/account-transaction/update-baker-pool",
"MULTISIGTRANSACTIONS_UPDATE_BAKER_KEYS": "/multi-signature-transaction/account-transaction/update-baker-keys",
"MULTISIGTRANSACTIONS_UPDATE_BAKER_SUSPENSION": "/multi-signature-transaction/account-transaction/update-baker-suspension",
"MULTISIGTRANSACTIONS_ADD_DELEGATION": "/multi-signature-transaction/account-transaction/add-delegation",
"MULTISIGTRANSACTIONS_UPDATE_DELEGATION": "/multi-signature-transaction/account-transaction/update-delegation",
"MULTISIGTRANSACTIONS_REMOVE_DELEGATION": "/multi-signature-transaction/account-transaction/remove-delegation",
Expand Down
5 changes: 5 additions & 0 deletions app/features/AccountSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ export const chosenAccountInfoSelector = (
]
);

export const chosenAccountExtrasSelector = (
state: RootState
): AccountExtras | undefined =>
state.accounts.accountExtras?.[chosenAccountSelector(state)?.address ?? ''];

export const accountInfoSelector = (account?: Account) => (
state: RootState
): AccountInfo | undefined =>
Expand Down
22 changes: 15 additions & 7 deletions app/features/ledger/Transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ import {
serializeConfigureDelegation,
serializeConfigureBakerPayload,
getSerializedMetadataUrlWithLength,
getSerializedConfigureBakerBitmap,
} from '~/utils/transactionSerialization';
import pathAsBuffer from './Path';
import {
Expand Down Expand Up @@ -522,7 +521,10 @@ async function signConfigureBaker(
transaction.expiry
);

const bitmap = getSerializedConfigureBakerBitmap(transaction.payload);
const bitmap = serializeConfigureBakerPayload(transaction.payload).slice(
0,
2
); // first 2 bytes are the bitmap

const meta = Buffer.concat([
pathAsBuffer(path),
Expand All @@ -539,6 +541,7 @@ async function signConfigureBaker(
restakeEarnings,
openForDelegation,
keys,
suspended,
...commissions
} = transaction.payload;

Expand All @@ -550,15 +553,15 @@ async function signConfigureBaker(

if (Object.values(dataPayload).some(isDefined) || keys !== undefined) {
p1 = 0x01;
let data = serializeConfigureBakerPayload(dataPayload);
let data = serializeConfigureBakerPayload(dataPayload).slice(2); // first 2 bytes are the bitmap

if (keys !== undefined) {
data = Buffer.concat([
data,
putHexString(keys.electionVerifyKey),
putHexString(keys.electionKeyProof),
putHexString(keys.proofElection),
putHexString(keys.signatureVerifyKey),
putHexString(keys.signatureKeyProof),
putHexString(keys.proofSig),
]);
}

Expand All @@ -569,7 +572,7 @@ async function signConfigureBaker(
p1 = 0x02;
const aggKey = Buffer.concat([
putHexString(keys.aggregationVerifyKey),
putHexString(keys.aggregationKeyProof),
putHexString(keys.proofAggregation),
]);
response = await send(aggKey);
}
Expand All @@ -593,10 +596,15 @@ async function signConfigureBaker(

if (Object.values(commissions).some(isDefined)) {
p1 = 0x05;
const comms = serializeConfigureBakerPayload(commissions);
const comms = serializeConfigureBakerPayload(commissions).slice(2);
response = await send(comms);
}

if (suspended !== undefined) {
p1 = 0x06;
response = await send(Buffer.from([Number(suspended)]));
}

return response.slice(0, 64);
}

Expand Down
11 changes: 8 additions & 3 deletions app/pages/Accounts/AccountDetailsPage/AccountDetailsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/* eslint-disable no-nested-ternary */
import { isBakerAccount, isDelegatorAccount } from '@concordium/web-sdk';
import { AccountInfoType } from '@concordium/web-sdk';
import React from 'react';
import { useSelector } from 'react-redux';
import { Redirect, Route } from 'react-router';
import MasterDetailPageLayout from '~/components/MasterDetailPageLayout';
import {
chosenAccountSelector,
chosenAccountInfoSelector,
chosenAccountExtrasSelector,
} from '~/features/AccountSlice';
import routes from '~/constants/routes.json';
import { accountHasDeployedCredentialsSelector } from '~/features/CredentialSlice';
Expand Down Expand Up @@ -35,6 +36,7 @@ const ToCreateScheduled = () => (
export default withAccountSync(function DetailsPage() {
const account = useSelector(chosenAccountSelector);
const accountInfo = useSelector(chosenAccountInfoSelector);
const accountExtras = useSelector(chosenAccountExtrasSelector);
const accountChanged = useSelector(
(s: RootState) => s.accounts.accountChanged
);
Expand All @@ -43,9 +45,11 @@ export default withAccountSync(function DetailsPage() {
account ? accountHasDeployedCredentialsSelector(account) : () => false
);

const isBaker = accountInfo !== undefined && isBakerAccount(accountInfo);
const isBaker =
accountInfo !== undefined && accountInfo.type === AccountInfoType.Baker;
const isDelegating =
accountInfo !== undefined && isDelegatorAccount(accountInfo);
accountInfo !== undefined &&
accountInfo.type === AccountInfoType.Delegator;
const isDelegationPV = pv !== undefined && hasDelegationProtocol(pv);

if (!account) {
Expand Down Expand Up @@ -91,6 +95,7 @@ export default withAccountSync(function DetailsPage() {
<Baking
account={account}
accountInfo={accountInfo}
accountExtras={accountExtras}
/>
) : (
<ToAccounts />
Expand Down
93 changes: 93 additions & 0 deletions app/pages/Accounts/AccountDetailsPage/Baking/BakerSuspension.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* eslint-disable react/display-name */
import React, { ComponentType, useCallback } from 'react';

import withExchangeRate from '~/components/Transfers/withExchangeRate';
import withNonce, { AccountAndNonce } from '~/components/Transfers/withNonce';
import { isDefined } from '~/utils/basicHelpers';
import {
AccountInfo,
ConfigureBaker as ConfigureBakerTransaction,
MakeRequired,
NotOptional,
} from '~/utils/types';
import { ensureProps } from '~/utils/componentHelpers';
import routes from '~/constants/routes.json';
import {
BakerSuspensionDependencies,
BakerSuspensionFlowState,
bakerSuspensionTitle,
convertToBakerSuspensionTransaction,
} from '~/utils/transactionFlows/bakerSuspension';
import BakerSuspensionPage from '~/components/Transfers/configureBaker/BakerSuspensionPage';

import AccountTransactionFlow, {
AccountTransactionFlowLoading,
} from '../../AccountTransactionFlow';

type Props = BakerSuspensionDependencies &
NotOptional<AccountAndNonce> & {
accountInfo: AccountInfo;
isSuspended?: boolean;
};

type UnsafeProps = MakeRequired<Partial<Props>, 'account' | 'accountInfo'>;

const hasNecessaryProps = (props: UnsafeProps): props is Props => {
return [props.exchangeRate, props.nonce].every(isDefined);
};

const withDeps = (component: ComponentType<Props>) =>
withNonce(
withExchangeRate(
ensureProps(
component,
hasNecessaryProps,
<AccountTransactionFlowLoading title="Change suspension status" />
)
)
);

export default withDeps(function BakerSuspension(props: Props) {
const {
nonce,
account,
exchangeRate,
accountInfo,
isSuspended = false,
} = props;

// eslint-disable-next-line react-hooks/exhaustive-deps
const convert = useCallback(
() =>
convertToBakerSuspensionTransaction(
account,
nonce,
exchangeRate
)(!isSuspended),
[account, nonce, exchangeRate, isSuspended]
);

return (
<AccountTransactionFlow<
BakerSuspensionFlowState,
ConfigureBakerTransaction
>
title={bakerSuspensionTitle(isSuspended)}
convert={convert}
multisigRoute={routes.MULTISIGTRANSACTIONS_UPDATE_BAKER_SUSPENSION}
firstPageBack
>
{{
suspended: {
render: (_, onNext) => (
<BakerSuspensionPage
onNext={onNext}
accountInfo={accountInfo}
isSuspended={isSuspended}
/>
),
},
}}
</AccountTransactionFlow>
);
});
Loading

0 comments on commit f14af37

Please sign in to comment.