Skip to content

Commit

Permalink
Implement ViewService#delegationsByAddressIndex in the extension (#734
Browse files Browse the repository at this point in the history
)

* Update bufbuild packages

* Create needed contexts

* Implement ViewService#DelegationsByAddressIndex

* Create getter

* Use new method

* Remove comment

* Add fromAsync shim to test env

* Create initial test suite

* More tests

* Implement one of the filter options

* Implement the other filter option

* Fix tests
  • Loading branch information
jessepinho authored Mar 13, 2024
1 parent 92eb3e7 commit d705c70
Show file tree
Hide file tree
Showing 17 changed files with 651 additions and 191 deletions.
13 changes: 11 additions & 2 deletions apps/extension/src/service-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ import { transportOptions } from '@penumbra-zone/types/registry';

// context
import { CustodyService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/custody/v1/custody_connect';
import { approverCtx, custodyCtx, servicesCtx } from '@penumbra-zone/router/src/ctx';
import { QueryService as StakingService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/core/component/stake/v1/stake_connect';
import {
approverCtx,
custodyCtx,
servicesCtx,
stakingClientCtx,
} from '@penumbra-zone/router/src/ctx';
import { createDirectClient } from '@penumbra-zone/transport-dom/direct';
import { approveTransaction } from './approve-transaction';

Expand All @@ -43,6 +49,7 @@ const services = new Services({
await services.initialize();

let custodyClient: PromiseClient<typeof CustodyService> | undefined;
let stakingClient: PromiseClient<typeof StakingService> | undefined;
const handler = connectChannelAdapter({
// jsonOptions contains typeRegistry providing ser/de
jsonOptions: transportOptions.jsonOptions,
Expand All @@ -55,10 +62,12 @@ const handler = connectChannelAdapter({
createRequestContext: req => {
const contextValues = req.contextValues ?? createContextValues();

// dynamically initialize custodyClient, or reuse if it's already available
// dynamically initialize clients, or reuse if already available
custodyClient ??= createDirectClient(CustodyService, handler, transportOptions);
stakingClient ??= createDirectClient(StakingService, handler, transportOptions);

contextValues.set(custodyCtx, custodyClient);
contextValues.set(stakingClientCtx, stakingClient);
contextValues.set(servicesCtx, services);
contextValues.set(approverCtx, approveTransaction);

Expand Down
93 changes: 0 additions & 93 deletions apps/minifront/src/fetchers/staking.ts

This file was deleted.

166 changes: 102 additions & 64 deletions apps/minifront/src/state/staking/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { StoreApi, UseBoundStore, create } from 'zustand';
import { AllSlices, initializeStore } from '..';
import {
ValidatorInfo,
ValidatorInfoResponse,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/stake/v1/stake_pb';
import { ValidatorInfo } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/stake/v1/stake_pb';
import {
Metadata,
ValueView,
Expand All @@ -16,70 +13,54 @@ import {
IdentityKey,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb';
import { THROTTLE_MS, accountsSelector } from '.';
import { DelegationsByAddressIndexResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb';

const validator1IdentityKey = new IdentityKey({ ik: new Uint8Array([1, 2, 3]) });
const validator1Bech32IdentityKey = bech32IdentityKey(validator1IdentityKey);
const validatorInfoResponse1 = new ValidatorInfoResponse({
validatorInfo: {
status: {
votingPower: { hi: 0n, lo: 2n },
},
validator: {
name: 'Validator 1',
identityKey: validator1IdentityKey,
},
const validatorInfo1 = new ValidatorInfo({
status: {
votingPower: { hi: 0n, lo: 2n },
},
validator: {
name: 'Validator 1',
identityKey: validator1IdentityKey,
},
});

const validator2IdentityKey = new IdentityKey({ ik: new Uint8Array([4, 5, 6]) });
const validator2Bech32IdentityKey = bech32IdentityKey(validator2IdentityKey);
const validatorInfoResponse2 = new ValidatorInfoResponse({
validatorInfo: {
status: {
votingPower: { hi: 0n, lo: 5n },
},
validator: {
name: 'Validator 2',
identityKey: validator2IdentityKey,
},
const validatorInfo2 = new ValidatorInfo({
status: {
votingPower: { hi: 0n, lo: 5n },
},
validator: {
name: 'Validator 2',
identityKey: validator2IdentityKey,
},
});

const validator3IdentityKey = new IdentityKey({ ik: new Uint8Array([7, 8, 9]) });
const validatorInfoResponse3 = new ValidatorInfoResponse({
validatorInfo: {
status: {
votingPower: { hi: 0n, lo: 3n },
},
validator: {
name: 'Validator 3',
identityKey: validator3IdentityKey,
},
const validatorInfo3 = new ValidatorInfo({
status: {
votingPower: { hi: 0n, lo: 3n },
},
validator: {
name: 'Validator 3',
identityKey: validator3IdentityKey,
},
});

const validator4IdentityKey = new IdentityKey({ ik: new Uint8Array([0]) });
const validatorInfoResponse4 = new ValidatorInfoResponse({
validatorInfo: {
status: {
votingPower: { hi: 0n, lo: 9n },
},
validator: {
name: 'Validator 4',
identityKey: validator4IdentityKey,
},
const validatorInfo4 = new ValidatorInfo({
status: {
votingPower: { hi: 0n, lo: 9n },
},
validator: {
name: 'Validator 4',
identityKey: validator4IdentityKey,
},
});

const mockStakeClient = vi.hoisted(() => ({
validatorInfo: vi.fn(async function* () {
yield await Promise.resolve(validatorInfoResponse1);
yield await Promise.resolve(validatorInfoResponse2);
yield await Promise.resolve(validatorInfoResponse3);
yield await Promise.resolve(validatorInfoResponse4);
}),
}));

vi.mock('../../fetchers/balances', () => ({
getBalances: vi.fn(async () =>
Promise.resolve([
Expand All @@ -94,7 +75,7 @@ vi.mock('../../fetchers/balances', () => ({
},
extendedMetadata: {
typeUrl: ValidatorInfo.typeName,
value: validatorInfoResponse1.validatorInfo?.toBinary(),
value: validatorInfo1.toBinary(),
},
},
},
Expand All @@ -121,7 +102,7 @@ vi.mock('../../fetchers/balances', () => ({
},
extendedMetadata: {
typeUrl: ValidatorInfo.typeName,
value: validatorInfoResponse2.validatorInfo?.toBinary(),
value: validatorInfo2.toBinary(),
},
},
},
Expand Down Expand Up @@ -166,10 +147,75 @@ vi.mock('../../fetchers/balances', () => ({

const mockViewClient = vi.hoisted(() => ({
assetMetadataById: vi.fn(() => new Metadata()),
delegationsByAddressIndex: vi.fn(async function* () {
yield await Promise.resolve(
new DelegationsByAddressIndexResponse({
valueView: {
valueView: {
case: 'knownAssetId',
value: {
amount: { hi: 0n, lo: 1n },
extendedMetadata: {
typeUrl: ValidatorInfo.typeName,
value: validatorInfo1.toBinary(),
},
},
},
},
}),
);
yield await Promise.resolve(
new DelegationsByAddressIndexResponse({
valueView: {
valueView: {
case: 'knownAssetId',
value: {
amount: { hi: 0n, lo: 2n },
extendedMetadata: {
typeUrl: ValidatorInfo.typeName,
value: validatorInfo2.toBinary(),
},
},
},
},
}),
);
yield await Promise.resolve(
new DelegationsByAddressIndexResponse({
valueView: {
valueView: {
case: 'knownAssetId',
value: {
amount: { hi: 0n, lo: 0n },
extendedMetadata: {
typeUrl: ValidatorInfo.typeName,
value: validatorInfo3.toBinary(),
},
},
},
},
}),
);
yield await Promise.resolve(
new DelegationsByAddressIndexResponse({
valueView: {
valueView: {
case: 'knownAssetId',
value: {
amount: { hi: 0n, lo: 0n },
extendedMetadata: {
typeUrl: ValidatorInfo.typeName,
value: validatorInfo4.toBinary(),
},
},
},
},
}),
);
}),
}));

vi.mock('../../clients', () => ({
stakeClient: mockStakeClient,
viewClient: mockViewClient,
}));

Expand Down Expand Up @@ -219,18 +265,10 @@ describe('Staking Slice', () => {
* before validator 3 at the end: we have a 0 balance of both, but validator
* 4 has more voting power.
*/
expect(getValidatorInfoFromValueView(delegations[0])).toEqual(
validatorInfoResponse2.validatorInfo,
);
expect(getValidatorInfoFromValueView(delegations[1])).toEqual(
validatorInfoResponse1.validatorInfo,
);
expect(getValidatorInfoFromValueView(delegations[2])).toEqual(
validatorInfoResponse4.validatorInfo,
);
expect(getValidatorInfoFromValueView(delegations[3])).toEqual(
validatorInfoResponse3.validatorInfo,
);
expect(getValidatorInfoFromValueView(delegations[0])).toEqual(validatorInfo2);
expect(getValidatorInfoFromValueView(delegations[1])).toEqual(validatorInfo1);
expect(getValidatorInfoFromValueView(delegations[2])).toEqual(validatorInfo4);
expect(getValidatorInfoFromValueView(delegations[3])).toEqual(validatorInfo3);
});

it('calculates the percentage voting power once all delegations are loaded', async () => {
Expand Down
6 changes: 4 additions & 2 deletions apps/minifront/src/state/staking/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ValidatorInfo } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/stake/v1/stake_pb';
import { AllSlices, SliceCreator } from '..';
import { getDelegationsForAccount } from '../../fetchers/staking';
import {
getAmount,
getAssetIdFromValueView,
Expand All @@ -9,6 +8,7 @@ import {
getDisplayDenomFromView,
getRateData,
getValidatorInfoFromValueView,
getValueView,
getVotingPowerFromValidatorInfo,
} from '@penumbra-zone/getters';
import {
Expand All @@ -34,6 +34,7 @@ import { TransactionPlannerRequest } from '@buf/penumbra-zone_penumbra.bufbuild_
import { BigNumber } from 'bignumber.js';
import { assembleUndelegateClaimRequest } from './assemble-undelegate-claim-request';
import throttle from 'lodash/throttle';
import { viewClient } from '../../clients';

const STAKING_TOKEN_DISPLAY_DENOM_EXPONENT = (() => {
const stakingAsset = localAssets.find(asset => asset.display === STAKING_TOKEN);
Expand Down Expand Up @@ -215,12 +216,13 @@ export const createStakingSlice = (): SliceCreator<StakingSlice> => (set, get) =
};
const throttledFlushToState = throttle(flushToState, THROTTLE_MS, { trailing: true });

for await (const delegation of getDelegationsForAccount(addressIndex)) {
for await (const response of viewClient.delegationsByAddressIndex({ addressIndex })) {
if (newAbortController.signal.aborted) {
throttledFlushToState.cancel();
return;
}

const delegation = getValueView(response);
delegationsToFlush.push(delegation);
validatorInfos.push(getValidatorInfoFromValueView(delegation));

Expand Down
Loading

0 comments on commit d705c70

Please sign in to comment.