diff --git a/packages/api-derive/src/staking/info.ts b/packages/api-derive/src/staking/info.ts index 8b742d7ffa..8c6f5c8289 100644 --- a/packages/api-derive/src/staking/info.ts +++ b/packages/api-derive/src/staking/info.ts @@ -8,7 +8,7 @@ import { DerivedStaking, DerivedUnlocking } from '../types'; import BN from 'bn.js'; import { combineLatest, Observable, of } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; -import { AccountId, BlockNumber, Exposure, Keys, Option, RewardDestination, SessionKey, SessionKeys, StakingLedger, ValidatorPrefs, UnlockChunk } from '@polkadot/types'; +import { AccountId, BlockNumber, Exposure, Keys, Option, RewardDestination, SessionKey, StakingLedger, Tuple, ValidatorPrefs, Vector, UnlockChunk } from '@polkadot/types'; import { isUndefined } from '@polkadot/util'; @@ -72,13 +72,31 @@ function redeemableSum (stakingLedger: StakingLedger | undefined, eraLength: BN, }, new BN(0)); } -function nextSessionId (_nextKeyFor: Option): AccountId | undefined { - const nextKeyFor: SessionKeys | SessionKey | null = _nextKeyFor.unwrapOr(null); +function unwrapSessionIds (stashId: AccountId, validatorIds: AccountId[], auraIds: AccountId[], nextKeys: Option | Vector): { nextSessionId?: AccountId; sessionId?: AccountId } { + // for 2.x we have a Vec<(ValidatorId,Keys)> of the keys + if (Array.isArray(nextKeys)) { + const validatorIdx = validatorIds.indexOf(stashId); + const sessionId = auraIds[validatorIdx]; + const keys = nextKeys.find(([currentId]): boolean => currentId.eq(stashId)) as [AccountId, Keys] | undefined; + const nextSessionId = keys + ? keys[1].ed25519 + : sessionId; - // For substrate 2.x, nextKeyFor is SessionKeys/Keys, for 1.x it is SessionKey - return nextKeyFor - ? (nextKeyFor as SessionKeys).auraKey || nextKeyFor + return { + nextSessionId, + sessionId + }; + } + + // substrate 1.x + const nextSessionId = nextKeys.isSome + ? nextKeys.unwrap() : undefined; + + return { + nextSessionId, + sessionId: nextSessionId + }; } function withStashController (api: ApiInterfaceRx, accountId: AccountId, controllerId: AccountId): Observable { @@ -88,32 +106,43 @@ function withStashController (api: ApiInterfaceRx, accountId: AccountId, control combineLatest([ eraLength(api)(), bestNumber(api)(), + // FIXME while we have 2.x and 1.x support, don't add this to .multi - + // should be added when only 2.x + api.query.aura && api.query.aura.authorities + ? api.query.aura.authorities>() + : of([] as AccountId[]), api.queryMulti([ - [api.query.session.nextKeyFor, controllerId], + api.query.session.queuedKeys + ? [api.query.session.queuedKeys] + : [api.query.session.nextKeyFor, controllerId], + api.query.session.validators, [api.query.staking.ledger, controllerId], [api.query.staking.nominators, stashId], [api.query.staking.payee, stashId], [api.query.staking.stakers, stashId], [api.query.staking.validators, stashId] ]) - ]) as any as Observable<[BN, BlockNumber, [Option, Option, [AccountId[]], RewardDestination, Exposure, [ValidatorPrefs]]]> + ]) as Observable<[BN, BlockNumber, AccountId[], [Option | Vector, AccountId[], Option, [AccountId[]], RewardDestination, Exposure, [ValidatorPrefs]]]> ).pipe( - map(([eraLength, bestNumber, [nextKeyFor, _stakingLedger, [nominators], rewardDestination, stakers, [validatorPrefs]]]): DerivedStaking => { + map(([eraLength, bestNumber, auraIds, [nextKeys, validatorIds, _stakingLedger, [nominators], rewardDestination, stakers, [validatorPrefs]]]): DerivedStaking => { const stakingLedger = _stakingLedger.unwrapOr(null) || undefined; - - return { + const { sessionId, nextSessionId } = unwrapSessionIds(stashId, validatorIds, auraIds, nextKeys); + const result: DerivedStaking = { accountId, controllerId, - nextSessionId: nextSessionId(nextKeyFor), + nextSessionId, nominators, redeemable: redeemableSum(stakingLedger, eraLength, bestNumber), rewardDestination, + sessionId, stakers, stakingLedger, stashId, unlocking: calculateUnlocking(stakingLedger, eraLength, bestNumber), validatorPrefs - } as unknown as DerivedStaking; + }; + + return result; }), drr() ); @@ -124,25 +153,41 @@ function withControllerLedger (api: ApiInterfaceRx, accountId: AccountId, stakin const stashId = stakingLedger.stash; return ( - api.queryMulti([ - [api.query.session.nextKeyFor, controllerId], - [api.query.staking.nominators, stashId], - [api.query.staking.payee, stashId], - [api.query.staking.stakers, stashId], - [api.query.staking.validators, stashId] - ]) as any as Observable<[Option, [AccountId[]], RewardDestination, Exposure, [ValidatorPrefs]]> + combineLatest([ + // FIXME while we have 2.x and 1.x support, don't add this to .multi - + // should be added when only 2.x + api.query.aura && api.query.aura.authorities + ? api.query.aura.authorities>() + : of([] as AccountId[]), + api.queryMulti([ + api.query.session.queuedKeys + ? [api.query.session.queuedKeys] + : [api.query.session.nextKeyFor, controllerId], + api.query.session.validators, + [api.query.staking.nominators, stashId], + [api.query.staking.payee, stashId], + [api.query.staking.stakers, stashId], + [api.query.staking.validators, stashId] + ]) + ]) as Observable<[AccountId[], [Option | Vector, AccountId[], [AccountId[]], RewardDestination, Exposure, [ValidatorPrefs]]]> ).pipe( - map(([nextKeyFor, [nominators], rewardDestination, stakers, [validatorPrefs]]): DerivedStaking => ({ - accountId, - controllerId, - nextSessionId: nextSessionId(nextKeyFor), - nominators, - rewardDestination, - stakers, - stakingLedger, - stashId, - validatorPrefs - } as unknown as DerivedStaking)), + map(([auraIds, [nextKeys, validatorIds, [nominators], rewardDestination, stakers, [validatorPrefs]]]): DerivedStaking => { + const { nextSessionId, sessionId } = unwrapSessionIds(stashId, validatorIds, auraIds, nextKeys); + const result: DerivedStaking = { + accountId, + controllerId, + nextSessionId, + nominators, + rewardDestination, + sessionId, + stakers, + stakingLedger, + stashId, + validatorPrefs + }; + + return result; + }), drr() ); } @@ -158,18 +203,16 @@ export function info (api: ApiInterfaceRx): (_accountId: Uint8Array | string) => api.queryMulti([ [api.query.staking.bonded, accountId], // try to map to controller [api.query.staking.ledger, accountId] // try to map to stash - ]) as any as Observable<[Option, Option]> + ]) as Observable<[Option, Option]> ).pipe( switchMap(([controllerId, stakingLedger]): Observable => controllerId.isSome // we have a controller, so input was a stash, great ? withStashController(api, accountId, controllerId.unwrap()) - : ( - stakingLedger.isSome - ? withControllerLedger(api, accountId, stakingLedger.unwrap()) - // dangit, this is something else, ok, we are done - : of({ accountId } as unknown as DerivedStaking) - ) + : stakingLedger.isSome + ? withControllerLedger(api, accountId, stakingLedger.unwrap()) + // dangit, this is something else, ok, we are done + : of({ accountId }) ), drr() ); diff --git a/packages/api-derive/src/types.ts b/packages/api-derive/src/types.ts index 69b107ca99..29b8109f76 100644 --- a/packages/api-derive/src/types.ts +++ b/packages/api-derive/src/types.ts @@ -64,6 +64,7 @@ export interface DerivedStaking { nominators?: AccountId[]; redeemable?: BN; rewardDestination?: RewardDestination; + sessionId?: AccountId; stakers?: Exposure; stakingLedger?: StakingLedger; stashId?: AccountId; diff --git a/packages/api-derive/test/e2e/promise.spec.ts b/packages/api-derive/test/e2e/promise.spec.ts index 00e2b50d9a..8709dc0d8b 100644 --- a/packages/api-derive/test/e2e/promise.spec.ts +++ b/packages/api-derive/test/e2e/promise.spec.ts @@ -91,122 +91,124 @@ describe.skip('derive e2e', (): void => { }); }); - it('retrieves all staking info (for controller)', (done): Promise<() => void> => { - const accountId = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'; + describe('staking', (): void => { + it('retrieves all staking info (for controller)', (done): Promise<() => void> => { + const accountId = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'; - return api.derive.staking.info(accountId, (info: DerivedStaking): void => { - console.error(JSON.stringify(info)); + return api.derive.staking.info(accountId, (info: DerivedStaking): void => { + console.error(JSON.stringify(info)); - expect(info.accountId.eq(accountId)).toBe(true); - expect(info.controllerId!.eq(accountId)).toBe(true); - expect(info.stashId!.eq('5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY')).toBe(true); - expect(info.stashId!.eq(info.stakingLedger!.stash)).toBe(true); - - done(); - }); - }); - - it('retrieves all staking info (for stash)', (done): Promise<() => void> => { - const accountId = '5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY'; - - return api.derive.staking.info(accountId, (info: DerivedStaking): void => { - console.error(JSON.stringify(info)); - - if (!info.stashId || !info.controllerId || !info.stakingLedger) { - done.fail(new Error('At leat one of info.stashId, info.controllerId or info.stakingLedger is undefined.')); - return; - } - - expect(info.accountId.eq(accountId)).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); - - done(); - }); - }); - - describe('verifies derive.staking.unlocking', (): void => { - const UNBOND_VALUE = 1; - const alicePair = testingPairs().alice; - - it('unbonds dots for Alice (from Alice Stash)', (done): Promise<() => void> => { - return api.tx.staking.unbond(UNBOND_VALUE) - .signAndSend(alicePair, (result: SubmittableResult): void => { - if (result.status.isFinalized) { - done(); - } - }); - }); - - it('verifies that derive.staking.unlocking isn\'t empty/undefined', (): Promise<() => void> => { - return api.derive.staking.info(ALICE_STASH, (info: DerivedStaking): void => { - expect(info.unlocking).toBeDefined(); - }); - }); - }); - - describe('verifies derive.staking.rewardDestination', (): void => { - const PAYEE = 2; - const alicePair = testingPairs().alice; - - it('Set payee for ALICE to 2', (done): Promise<() => void> => { - return api.tx.staking - .setPayee(PAYEE) - .signAndSend(alicePair, (result: SubmittableResult): void => { - if (result.status.isFinalized) { - done(); - } - }); - }); - - it('verifies payee for ALICE_STASH', (done): Promise<() => void> => { - return api.derive.staking.info(ALICE_STASH, (info: DerivedStaking): void => { - if (!info.rewardDestination) { - return done.fail(new Error('rewardDestination is undefined.')); - } else { - expect(info.rewardDestination.toString()).toBe('Controller'); - } + expect(info.accountId.eq(accountId)).toBe(true); + expect(info.controllerId!.eq(accountId)).toBe(true); + expect(info.stashId!.eq('5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY')).toBe(true); + expect(info.stashId!.eq(info.stakingLedger!.stash)).toBe(true); done(); }); }); - it('staking.info updates itself after changing reward destination', async (done): Promise => { - const stashId = testingPairs().alice_stash.address; + it('retrieves all staking info (for stash)', (done): Promise<() => void> => { + const accountId = '5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY'; - // start by setting the reqred to Staked, so we have a common starting point - await api.tx.staking - .setPayee(new RewardDestination('Staked')) - .signAndSend(testingPairs().alice, ({ status }): void => { - if (status.isFinalized) { - console.error('setPayee(Staked) isFinalized'); - } - }); + return api.derive.staking.info(accountId, (info: DerivedStaking): void => { + console.error(JSON.stringify(info)); - let count = 0; // The # of times we got a callback response from api.derive.staking.info - - // Subscribe to staking.info - api.derive.staking.info(stashId, (result): void => { - ++count; - - console.error('***', count, JSON.stringify(result)); - - if (count >= 2 && result.rewardDestination!.toString() === 'Stash') { - done(); + if (!info.stashId || !info.controllerId || !info.stakingLedger) { + done.fail(new Error('At least one of info.stashId, info.controllerId or info.stakingLedger is undefined.')); + return; } - }).catch(console.error); - // Wait a bit, and change reward destination - setTimeout(async (): Promise => { - await api.tx.staking - .setPayee(new RewardDestination('Stash')) - .signAndSend(testingPairs().alice, ({ status }): void => { - if (status.isFinalized) { - console.error('setPayee(Stash) isFinalized'); + expect(info.accountId.eq(accountId)).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); + + done(); + }); + }); + + describe('verifies derive.staking.unlocking', (): void => { + const UNBOND_VALUE = 1; + const alicePair = testingPairs().alice; + + it('unbonds dots for Alice (from Alice Stash)', (done): Promise<() => void> => { + return api.tx.staking.unbond(UNBOND_VALUE) + .signAndSend(alicePair, (result: SubmittableResult): void => { + if (result.status.isFinalized) { + done(); } }); - }, 5000); + }); + + it('verifies that derive.staking.unlocking isn\'t empty/undefined', (): Promise<() => void> => { + return api.derive.staking.info(ALICE_STASH, (info: DerivedStaking): void => { + expect(info.unlocking).toBeDefined(); + }); + }); + }); + + describe('verifies derive.staking.rewardDestination', (): void => { + const PAYEE = 2; + const alicePair = testingPairs().alice; + + it('Set payee for ALICE to 2', (done): Promise<() => void> => { + return api.tx.staking + .setPayee(PAYEE) + .signAndSend(alicePair, (result: SubmittableResult): void => { + if (result.status.isFinalized) { + done(); + } + }); + }); + + it('verifies payee for ALICE_STASH', (done): Promise<() => void> => { + return api.derive.staking.info(ALICE_STASH, (info: DerivedStaking): void => { + if (!info.rewardDestination) { + return done.fail(new Error('rewardDestination is undefined.')); + } else { + expect(info.rewardDestination.toString()).toBe('Controller'); + } + + done(); + }); + }); + + it('staking.info updates itself after changing reward destination', async (done): Promise => { + const stashId = testingPairs().alice_stash.address; + + // start by setting the reqred to Staked, so we have a common starting point + await api.tx.staking + .setPayee(new RewardDestination('Staked')) + .signAndSend(testingPairs().alice, ({ status }): void => { + if (status.isFinalized) { + console.error('setPayee(Staked) isFinalized'); + } + }); + + let count = 0; // The # of times we got a callback response from api.derive.staking.info + + // Subscribe to staking.info + api.derive.staking.info(stashId, (result): void => { + ++count; + + console.error('***', count, JSON.stringify(result)); + + if (count >= 2 && result.rewardDestination!.toString() === 'Stash') { + done(); + } + }).catch(console.error); + + // Wait a bit, and change reward destination + setTimeout(async (): Promise => { + await api.tx.staking + .setPayee(new RewardDestination('Stash')) + .signAndSend(testingPairs().alice, ({ status }): void => { + if (status.isFinalized) { + console.error('setPayee(Stash) isFinalized'); + } + }); + }, 5000); + }); }); }); }); diff --git a/packages/types/src/type/SessionKeys.ts b/packages/types/src/type/SessionKeys.ts index 9154cbc464..70d2e6f70b 100644 --- a/packages/types/src/type/SessionKeys.ts +++ b/packages/types/src/type/SessionKeys.ts @@ -13,22 +13,14 @@ import SessionKey from './SessionKey'; export default class SessionKeys extends Struct { public constructor (value?: any) { super({ - grandpaKey: SessionKey, - auraKey: SessionKey + ed25519: SessionKey }, value); } /** * @description The Aura session */ - public get auraKey (): SessionKey { - return this.get('auraKey') as SessionKey; - } - - /** - * @description The Grandpa Authority - */ - public get grandpaKey (): SessionKey { - return this.get('grandpaKey') as SessionKey; + public get ed25519 (): SessionKey { + return this.get('ed25519') as SessionKey; } }