From 09ee77d0bc8890c7a985b89e868c2d596a0064dd Mon Sep 17 00:00:00 2001 From: kwingram25 Date: Mon, 5 Aug 2019 16:13:42 +0000 Subject: [PATCH] Pass online/offline status indicators with staking accounts (#1265) * staking online status * restrict onlineStatus.online to validators * get onlineStatus for stash/validator only * tests * fixes * Add offline * Revert stash, change to stashId, linting * Heartbeat signature * Make CodeClimate (slightly) happier * keep compatibility * change status type * rv null possible * cleanup --- packages/api-derive/src/imOnline/index.ts | 5 +++ .../src/imOnline/receivedHeartbeats.ts | 37 ++++++++++++++++ packages/api-derive/src/index.spec.ts | 4 ++ packages/api-derive/src/index.ts | 3 +- packages/api-derive/src/staking/index.ts | 1 + packages/api-derive/src/staking/info.ts | 17 ++++---- .../api-derive/src/staking/recentlyOffline.ts | 43 +++++++++++++++++++ packages/api-derive/src/types.ts | 24 ++++++++++- .../test/e2e/api-derive/promise-dev.spec.ts | 2 +- 9 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 packages/api-derive/src/imOnline/index.ts create mode 100644 packages/api-derive/src/imOnline/receivedHeartbeats.ts create mode 100644 packages/api-derive/src/staking/recentlyOffline.ts diff --git a/packages/api-derive/src/imOnline/index.ts b/packages/api-derive/src/imOnline/index.ts new file mode 100644 index 0000000000..1350fc0b16 --- /dev/null +++ b/packages/api-derive/src/imOnline/index.ts @@ -0,0 +1,5 @@ +// Copyright 2017-2019 @polkadot/api-derive authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +export * from './receivedHeartbeats'; diff --git a/packages/api-derive/src/imOnline/receivedHeartbeats.ts b/packages/api-derive/src/imOnline/receivedHeartbeats.ts new file mode 100644 index 0000000000..15ed53c162 --- /dev/null +++ b/packages/api-derive/src/imOnline/receivedHeartbeats.ts @@ -0,0 +1,37 @@ +// Copyright 2017-2019 @polkadot/api-derive authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { AccountId, SessionIndex } from '@polkadot/types/interfaces'; + +import { of, Observable } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { ApiInterfaceRx } from '@polkadot/api/types'; +import { Bytes } from '@polkadot/types'; + +import { drr } from '../util/drr'; + +/** + * @description Return a boolean array indicating whether the passed accounts had received heartbeats in the current session + */ +export function receivedHeartbeats (api: ApiInterfaceRx): (addresses: (AccountId | string)[]) => Observable { + return (addresses: (AccountId | string)[]): Observable => { + return api.query.imOnline && api.query.imOnline.receivedHeartbeats + ? api.query.session + .currentIndex() + .pipe( + switchMap((currentIndex): Observable => + api.query.imOnline.receivedHeartbeats.multi( + addresses.map((address): [SessionIndex, AccountId | string] => + [currentIndex, address] + ) + ) + ), + map((heartbeats): boolean[] => + heartbeats.map((heartbeat): boolean => !heartbeat.isEmpty) + ), + drr() + ) + : of([...new Array(addresses.length).keys()].map((): boolean => false)); + }; +} diff --git a/packages/api-derive/src/index.spec.ts b/packages/api-derive/src/index.spec.ts index e8eeadad9c..f7121084df 100644 --- a/packages/api-derive/src/index.spec.ts +++ b/packages/api-derive/src/index.spec.ts @@ -61,6 +61,10 @@ describe('derive', (): void => { testFunction(api)('session', 'eraLength', []); testFunction(api)('session', 'eraProgress', []); testFunction(api)('session', 'sessionProgress', []); + + testFunction(api)('staking', 'info', []); + testFunction(api)('staking', 'recentlyOffline', []); + testFunction(api)('staking', 'controllers', []); }); describe('custom', (): void => { diff --git a/packages/api-derive/src/index.ts b/packages/api-derive/src/index.ts index 7336d83457..42c8102c89 100644 --- a/packages/api-derive/src/index.ts +++ b/packages/api-derive/src/index.ts @@ -13,6 +13,7 @@ import * as chain from './chain'; import * as contracts from './contracts'; import * as democracy from './democracy'; import * as elections from './elections'; +import * as imOnline from './imOnline'; import * as session from './session'; import * as staking from './staking'; @@ -56,7 +57,7 @@ function injectFunctions (api: ApiInterfaceRx, allSections: AllSect }, {} as unknown as DeriveSections); } -export const derive = { accounts, balances, chain, contracts, democracy, elections, session, staking }; +export const derive = { accounts, balances, chain, contracts, democracy, elections, imOnline, session, staking }; export type Derive = typeof derive; // FIXME I have no idea how to get this done diff --git a/packages/api-derive/src/staking/index.ts b/packages/api-derive/src/staking/index.ts index e207bed584..0bbbc12584 100644 --- a/packages/api-derive/src/staking/index.ts +++ b/packages/api-derive/src/staking/index.ts @@ -4,3 +4,4 @@ export * from './controllers'; export * from './info'; +export * from './recentlyOffline'; diff --git a/packages/api-derive/src/staking/info.ts b/packages/api-derive/src/staking/info.ts index f509ff41cc..f16587f0d8 100644 --- a/packages/api-derive/src/staking/info.ts +++ b/packages/api-derive/src/staking/info.ts @@ -19,6 +19,7 @@ import { isUndefined } from '@polkadot/util'; import { bestNumber } from '../chain/bestNumber'; import { eraLength } from '../session/eraLength'; +import { recentlyOffline } from './recentlyOffline'; import { drr } from '../util/drr'; type MultiResult = [Option, [ @@ -128,20 +129,20 @@ function retrieveMulti (api: ApiInterfaceRx, stashId: AccountId, controllerId: A } function retrieveInfo (api: ApiInterfaceRx, stashId: AccountId, controllerId: AccountId): Observable { - return ( - combineLatest([ - eraLength(api)(), - bestNumber(api)(), - retrieveMulti(api, stashId, controllerId) - ]) - ).pipe( - map(([eraLength, bestNumber, [nextKeys, [queuedKeys, _stakingLedger, [nominators], rewardDestination, stakers, [validatorPrefs]]]]): DerivedStaking => { + return combineLatest([ + eraLength(api)(), + bestNumber(api)(), + recentlyOffline(api)(), + retrieveMulti(api, stashId, controllerId) + ]).pipe( + map(([eraLength, bestNumber, recentlyOffline, [nextKeys, [queuedKeys, _stakingLedger, [nominators], rewardDestination, stakers, [validatorPrefs]]]]): DerivedStaking => { const stakingLedger = _stakingLedger.unwrapOr(undefined); return { accountId: stashId, controllerId, nominators, + offline: recentlyOffline[stashId.toString()], redeemable: redeemableSum(stakingLedger, eraLength, bestNumber), rewardDestination, stakers, diff --git a/packages/api-derive/src/staking/recentlyOffline.ts b/packages/api-derive/src/staking/recentlyOffline.ts new file mode 100644 index 0000000000..f7e66d8dd0 --- /dev/null +++ b/packages/api-derive/src/staking/recentlyOffline.ts @@ -0,0 +1,43 @@ +// Copyright 2017-2019 @polkadot/api-derive authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { AccountId, BlockNumber } from '@polkadot/types/interfaces'; +import { Codec } from '@polkadot/types/types'; + +import BN from 'bn.js'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { ApiInterfaceRx } from '@polkadot/api/types'; +import { DerivedRecentlyOffline } from '../types'; + +import { drr } from '../util/drr'; + +/** + * @description Retrieve a keyed record of accounts recently reported to be offline + */ +export function recentlyOffline (api: ApiInterfaceRx): () => Observable { + return (): Observable => + api.query.staking + .recentlyOffline<[AccountId, BlockNumber, BN][] & Codec>() + .pipe( + map((recentlyOffline): DerivedRecentlyOffline => + recentlyOffline.reduce((result: DerivedRecentlyOffline, [accountId, blockNumber, count]): DerivedRecentlyOffline => { + const key = accountId.toString(); + + if (!result[key]) { + result[key] = []; + } + + result[key].push({ + blockNumber, + count + }); + + return result; + }, + {}) + ), + drr() + ); +} diff --git a/packages/api-derive/src/types.ts b/packages/api-derive/src/types.ts index 3cd55e8e33..2787a6c6c0 100644 --- a/packages/api-derive/src/types.ts +++ b/packages/api-derive/src/types.ts @@ -51,6 +51,13 @@ export interface DerivedFees { transferFee: BN; } +export interface RecentlyOffline { + blockNumber: BlockNumber; + count: BN; +} + +export type DerivedRecentlyOffline = Record; + export interface DerivedReferendumVote { accountId: AccountId; balance: Balance; @@ -70,7 +77,22 @@ export interface DerivedSessionInfo { sessionProgress: BN; } -export interface DerivedStaking { +export type DerivedStakingAccount = [AccountId, DerivedStakingOnlineStatus]; + +export type DerivedStakingAccounts = DerivedStakingAccount[]; + +export interface DerivedStakingOnlineStatus { + online?: { + isOnline: boolean; + blockNumber?: BlockNumber; + }; + offline?: { + blockNumber: BlockNumber; + count: BN; + }[]; +} + +export interface DerivedStaking extends DerivedStakingOnlineStatus { accountId: AccountId; controllerId?: AccountId; nextSessionId?: AccountId; diff --git a/packages/api/test/e2e/api-derive/promise-dev.spec.ts b/packages/api/test/e2e/api-derive/promise-dev.spec.ts index 4381da9390..ee6bcaae11 100644 --- a/packages/api/test/e2e/api-derive/promise-dev.spec.ts +++ b/packages/api/test/e2e/api-derive/promise-dev.spec.ts @@ -73,7 +73,7 @@ describeE2E({ } expect(info.accountId.eq(accountId)).toBe(true); - expect(info.controllerId.eq('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY')).toBe(true); + expect(info.controllerId!.eq('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY')).toBe(true); expect(info.stashId.eq(accountId)).toBe(true); expect(info.stashId.eq(info.stakingLedger.stash)).toBe(true);