SessionId extraction for 2.x (#1127)

* SessionId extraction for 2.x

* Run through e2e & update
This commit is contained in:
Jaco Greeff
2019-07-15 15:01:36 +02:00
committed by GitHub
parent 283f0c0d64
commit ee27e31275
4 changed files with 190 additions and 152 deletions
+81 -38
View File
@@ -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<Keys | SessionKey>): AccountId | undefined {
const nextKeyFor: SessionKeys | SessionKey | null = _nextKeyFor.unwrapOr(null);
function unwrapSessionIds (stashId: AccountId, validatorIds: AccountId[], auraIds: AccountId[], nextKeys: Option<SessionKey> | Vector<Tuple>): { 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<DerivedStaking> {
@@ -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<Vector<AccountId>>()
: 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<Keys | SessionKey>, Option<StakingLedger>, [AccountId[]], RewardDestination, Exposure, [ValidatorPrefs]]]>
]) as Observable<[BN, BlockNumber, AccountId[], [Option<SessionKey> | Vector<Tuple>, AccountId[], Option<StakingLedger>, [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<Keys | SessionKey>, [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<Vector<AccountId>>()
: 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<SessionKey> | Vector<Tuple>, 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<AccountId>, Option<StakingLedger>]>
]) as Observable<[Option<AccountId>, Option<StakingLedger>]>
).pipe(
switchMap(([controllerId, stakingLedger]): Observable<DerivedStaking> =>
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()
);
+1
View File
@@ -64,6 +64,7 @@ export interface DerivedStaking {
nominators?: AccountId[];
redeemable?: BN;
rewardDestination?: RewardDestination;
sessionId?: AccountId;
stakers?: Exposure;
stakingLedger?: StakingLedger;
stashId?: AccountId;
+105 -103
View File
@@ -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<void> => {
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<void> => {
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<void> => {
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<void> => {
await api.tx.staking
.setPayee(new RewardDestination('Stash'))
.signAndSend(testingPairs().alice, ({ status }): void => {
if (status.isFinalized) {
console.error('setPayee(Stash) isFinalized');
}
});
}, 5000);
});
});
});
});
+3 -11
View File
@@ -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;
}
}