Skip to content

Commit

Permalink
Detect server error in account state (#1240)
Browse files Browse the repository at this point in the history
* error display on delegation page

* better error display

* version bump

* better handling of first-boot
  • Loading branch information
SebastienGllmt authored Dec 20, 2019
1 parent 8c9d3e0 commit f9e2d53
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 110 deletions.
21 changes: 17 additions & 4 deletions app/components/wallet/staking/dashboard/StakingDashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import WarningBox from '../../../widgets/WarningBox';
import InformativeError from '../../../widgets/InformativeError';
import LoadingSpinner from '../../../widgets/LoadingSpinner';
import VerticallyCenteredLayout from '../../../layout/VerticallyCenteredLayout';
import LocalizableError from '../../../../i18n/LocalizableError';
import InvalidURIImg from '../../../../assets/images/uri/invalid-uri.inline.svg';
import ErrorBlock from '../../../widgets/ErrorBlock';

const messages = defineMessages({
positionsLabel: {
Expand Down Expand Up @@ -42,7 +45,7 @@ type Props = {|
themeVars: Object,
totalGraphData: Array<Object>,
positionsGraphData: Array<Object>,
stakePools: null | Array<Node>,
stakePools: {| error: LocalizableError, |} | {| pools: null | Array<Node> |},
epochProgress: Node,
userSummary: Node,
rewardPopup: void | Node,
Expand Down Expand Up @@ -121,14 +124,24 @@ export default class StakingDashboard extends Component<Props> {

displayStakePools: void => Node = () => {
const { intl } = this.context;
if (this.props.stakePools == null) {
if (this.props.stakePools.error) {
return (
<div className={styles.poolError}>
<center><InvalidURIImg /></center>
<ErrorBlock
error={this.props.stakePools.error}
/>
</div>
);
}
if (this.props.stakePools.pools === null) {
return (
<VerticallyCenteredLayout>
<LoadingSpinner />
</VerticallyCenteredLayout>
);
}
if (this.props.stakePools.length === 0) {
if (this.props.stakePools.pools.length === 0) {
return (
<InformativeError
title={intl.formatMessage(emptyDashboardMessages.title)}
Expand All @@ -139,7 +152,7 @@ export default class StakingDashboard extends Component<Props> {
return (
<div className={styles.bodyWrapper}>
<div className={styles.stakePool}>
{this.props.stakePools}
{this.props.stakePools.pools}
</div>
</div>
);
Expand Down
4 changes: 4 additions & 0 deletions app/components/wallet/staking/dashboard/StakingDashboard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
flex: 1;
}

.poolError {
margin-top: 42px;
}

.rewards {
min-width: 420px;
align-self: flex-end;
Expand Down
123 changes: 76 additions & 47 deletions app/containers/wallet/staking/StakingDashboardPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import environment from '../../../environment';
import { LOVELACES_PER_ADA } from '../../../config/numbersConfig';
import { digetForHash } from '../../../api/ada/lib/storage/database/primitives/api/utils';
import { handleExternalLinkClick } from '../../../utils/routing';
import { GetPoolInfoApiError } from '../../../api/ada/errors';
import LocalizableError from '../../../i18n/LocalizableError';

import { formattedWalletAmount } from '../../../utils/formatters';

Expand Down Expand Up @@ -98,8 +100,15 @@ export default class StakingDashboardPage extends Component<Props, State> {

const getTimeBasedElements = this.getTimeBasedElements();

const stakePools = this.getStakePools();
const errorIfPresent = this.getErrorInFetch();
const stakePools = errorIfPresent == null
? this.getStakePools()
: errorIfPresent;

const showRewardAmount = delegationStore.getCurrentDelegation.wasExecuted &&
delegationStore.getCurrentDelegation.result != null &&
delegationStore.getDelegatedBalance.wasExecuted &&
errorIfPresent == null;
const { getThemeVars } = this.props.stores.profile;
return (
<StakingDashboard
Expand All @@ -112,15 +121,17 @@ export default class StakingDashboardPage extends Component<Props, State> {
? undefined
: hideOrFormat(publicDeriver.amount)
}
totalRewards={delegationStore.getDelegatedBalance.result == null
? undefined
: hideOrFormat(
delegationStore.getDelegatedBalance.result
.accountPart
.dividedBy(LOVELACES_PER_ADA)
)}
totalRewards={
!showRewardAmount || delegationStore.getDelegatedBalance.result == null
? undefined
: hideOrFormat(
delegationStore.getDelegatedBalance.result
.accountPart
.dividedBy(LOVELACES_PER_ADA)
)
}
totalDelegated={
delegationStore.getDelegatedBalance.result == null
!showRewardAmount || delegationStore.getDelegatedBalance.result == null
? undefined
: hideOrFormat(
delegationStore.getDelegatedBalance.result.utxoPart.plus(
Expand Down Expand Up @@ -428,55 +439,73 @@ export default class StakingDashboardPage extends Component<Props, State> {
};
}

getStakePools: void => null | Array<Node> = () => {
getErrorInFetch: void => void | {| error: LocalizableError, |} = () => {
const delegationStore = this.props.stores.substores[environment.API].delegation;
if (delegationStore.error != null) {
return { error: delegationStore.error };
}
const keyState = delegationStore.stakingKeyState;
if (
keyState &&
keyState.state.delegation.pools.length === 0 &&
delegationStore.getCurrentDelegation.result != null
) {
return { error: new GetPoolInfoApiError() };
}
return undefined;
}

getStakePools: void => {| pools: null | Array<Node> |} = () => {
const delegationStore = this.props.stores.substores[environment.API].delegation;
if (
!delegationStore.getCurrentDelegation.wasExecuted ||
delegationStore.getCurrentDelegation.isExecuting
) {
return null;
return { pools: null };
}
if (delegationStore.stakingKeyState == null) {
return [];
return { pools: [] };
}
const keyState = delegationStore.stakingKeyState;
const { intl } = this.context;
return keyState.state.delegation.pools.map(pool => {
const meta = keyState.poolInfo.get(pool[0]);
if (meta == null) {
throw new Error(`${nameof(this.getStakePools)} no meta for ${pool[0]}`);
}
const name = meta.info?.name ?? intl.formatMessage(globalMessages.unknownPoolLabel);

const moreInfo = meta.info?.homepage != null
? {
openPoolPage: handleExternalLinkClick,
url: meta.info.homepage,
return {
pools: keyState.state.delegation.pools.map(pool => {
const meta = keyState.poolInfo.get(pool[0]);
if (meta == null) {
throw new Error(`${nameof(this.getStakePools)} no meta for ${pool[0]}`);
}
: undefined;
const name = meta.info?.name ?? intl.formatMessage(globalMessages.unknownPoolLabel);

// TODO: implement this eventually
const stakePoolMeta = {
// percentage: '30',
// fullness: '18',
// margins: '12',
// created: '29/02/2019 12:42:41 PM',
// cost: '12,688.00000',
// stake: '9,688.00000',
// pledge: '85.567088',
// rewards: '81.000088',
// age: '23',
};
return (
<StakePool
poolName={name}
key={digetForHash(JSON.stringify(meta), 0)}
data={stakePoolMeta}
hash={pool[0]}
moreInfo={moreInfo}
classicTheme={this.props.stores.profile.isClassicTheme}
/>
);
});
const moreInfo = meta.info?.homepage != null
? {
openPoolPage: handleExternalLinkClick,
url: meta.info.homepage,
}
: undefined;

// TODO: implement this eventually
const stakePoolMeta = {
// percentage: '30',
// fullness: '18',
// margins: '12',
// created: '29/02/2019 12:42:41 PM',
// cost: '12,688.00000',
// stake: '9,688.00000',
// pledge: '85.567088',
// rewards: '81.000088',
// age: '23',
};
return (
<StakePool
poolName={name}
key={digetForHash(JSON.stringify(meta), 0)}
data={stakePoolMeta}
hash={pool[0]}
moreInfo={moreInfo}
classicTheme={this.props.stores.profile.isClassicTheme}
/>
);
})
};
}
}
127 changes: 69 additions & 58 deletions app/stores/ada/DelegationStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
AccountStateSuccess,
RemotePoolMetaSuccess,
} from '../../api/ada/lib/state-fetch/types';
import LocalizableError from '../../i18n/LocalizableError';

export default class DelegationStore extends Store {

Expand All @@ -28,6 +29,8 @@ export default class DelegationStore extends Store {
@observable getCurrentDelegation: LocalizedRequest<GetCurrentDelegationFunc>
= new LocalizedRequest<GetCurrentDelegationFunc>(getCurrentDelegation);

@observable error: LocalizableError | any;

@observable stakingKeyState: void | {|
state: AccountStateSuccess,
/**
Expand Down Expand Up @@ -59,72 +62,79 @@ export default class DelegationStore extends Store {
],
// $FlowFixMe error in mobx types
async () => {
this.getDelegatedBalance.reset();
this.getCurrentDelegation.reset();
runInAction(() => {
this.stakingKeyState = undefined;
});

const publicDeriver = this.stores.substores.ada.wallets.selected;
if (publicDeriver == null) {
throw new Error(`${nameof(this._startWatch)} no public deriver selected`);
}
const withStakingKey = asGetAllAccounting(publicDeriver.self);
if (withStakingKey == null) {
throw new Error(`${nameof(this._startWatch)} missing staking key functionality`);
}
try {
this.getDelegatedBalance.reset();
this.getCurrentDelegation.reset();
runInAction(() => {
this.error = undefined;
this.stakingKeyState = undefined;
});

const stakingKeyResp = await withStakingKey.getStakingKey();
const publicDeriver = this.stores.substores.ada.wallets.selected;
if (publicDeriver == null) {
throw new Error(`${nameof(this._startWatch)} no public deriver selected`);
}
const withStakingKey = asGetAllAccounting(publicDeriver.self);
if (withStakingKey == null) {
throw new Error(`${nameof(this._startWatch)} missing staking key functionality`);
}

const stateFetcher = this.stores.substores[environment.API].stateFetchStore.fetcher;
const accountStateResp = await stateFetcher.getAccountState({
addresses: [stakingKeyResp.addr.Hash],
});
const stateForStakingKey = accountStateResp[stakingKeyResp.addr.Hash];
const stakingKeyResp = await withStakingKey.getStakingKey();

if (!stateForStakingKey.delegation) {
return runInAction(() => {
this.stakingKeyState = undefined;
throw new Error(`${nameof(this._startWatch)} stake key invalid - ${stateForStakingKey.comment}`);
const stateFetcher = this.stores.substores[environment.API].stateFetchStore.fetcher;
const accountStateResp = await stateFetcher.getAccountState({
addresses: [stakingKeyResp.addr.Hash],
});
}
const poolInfoResp = await stateFetcher.getPoolInfo({
ids: stateForStakingKey.delegation.pools.map(delegation => delegation[0]),
});
const meta = new Map(stateForStakingKey.delegation.pools.map(delegation => {
const info = poolInfoResp[delegation[0]];
if (!info.history) {
const stateForStakingKey = accountStateResp[stakingKeyResp.addr.Hash];

if (!stateForStakingKey.delegation) {
return runInAction(() => {
this.stakingKeyState = undefined;
throw new Error(`${nameof(this._startWatch)} pool info missing ${info.error}`);
throw new Error(`${nameof(this._startWatch)} stake key invalid - ${stateForStakingKey.comment}`);
});
}
return [delegation[0], info];
}));
runInAction(() => {
this.stakingKeyState = {
state: stateForStakingKey,
poolInfo: meta,
};
});

const delegatedBalance = this.getDelegatedBalance.execute({
publicDeriver: withStakingKey,
accountState: stateForStakingKey,
stakingPubKey: stakingKeyResp.addr.Hash,
}).promise;
if (delegatedBalance == null) throw new Error('Should never happen');

const currentDelegation = this.getCurrentDelegation.execute({
publicDeriver: withStakingKey,
stakingKeyAddressId: stakingKeyResp.addr.AddressId,
}).promise;
if (currentDelegation == null) throw new Error('Should never happen');

await Promise.all([
delegatedBalance,
currentDelegation,
]);
const poolInfoResp = await stateFetcher.getPoolInfo({
ids: stateForStakingKey.delegation.pools.map(delegation => delegation[0]),
});
const meta = new Map(stateForStakingKey.delegation.pools.map(delegation => {
const info = poolInfoResp[delegation[0]];
if (!info.history) {
return runInAction(() => {
this.stakingKeyState = undefined;
throw new Error(`${nameof(this._startWatch)} pool info missing ${info.error}`);
});
}
return [delegation[0], info];
}));
runInAction(() => {
this.stakingKeyState = {
state: stateForStakingKey,
poolInfo: meta,
};
});

const delegatedBalance = this.getDelegatedBalance.execute({
publicDeriver: withStakingKey,
accountState: stateForStakingKey,
stakingPubKey: stakingKeyResp.addr.Hash,
}).promise;
if (delegatedBalance == null) throw new Error('Should never happen');

const currentDelegation = this.getCurrentDelegation.execute({
publicDeriver: withStakingKey,
stakingKeyAddressId: stakingKeyResp.addr.AddressId,
}).promise;
if (currentDelegation == null) throw new Error('Should never happen');

await Promise.all([
delegatedBalance,
currentDelegation,
]);
} catch (e) {
runInAction(() => {
this.error = e;
});
}
},
);
}
Expand All @@ -136,5 +146,6 @@ export default class DelegationStore extends Store {
this.getDelegatedBalance.reset();
this.getCurrentDelegation.reset();
this.stakingKeyState = undefined;
this.error = undefined;
}
}
Loading

0 comments on commit f9e2d53

Please sign in to comment.