From 27a15331c1d58a362716cb7b7c6d2839d226e8d2 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sat, 29 Aug 2020 20:59:17 -0700 Subject: [PATCH 01/20] balance-info --- README.md | 6 +- openapi/openapi-proposal.yaml | 2 +- .../accounts/AccountsBalanceInfoController.ts | 73 +++++++++++++++++++ src/controllers/accounts/index.ts | 1 + src/main.ts | 4 + .../AccountsBalanceInfoService.spec.ts | 21 ++++++ .../accounts/AccountsBalanceInfoService.ts | 55 ++++++++++++++ src/services/accounts/index.ts | 1 + 8 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 src/controllers/accounts/AccountsBalanceInfoController.ts create mode 100644 src/services/accounts/AccountsBalanceInfoService.spec.ts create mode 100644 src/services/accounts/AccountsBalanceInfoService.ts diff --git a/README.md b/README.md index 88d00ca2e..1ac4c4657 100644 --- a/README.md +++ b/README.md @@ -82,14 +82,12 @@ a 32-byte hex string (`0x` followed by 64 hexadecimal digits) that denotes the b - [`/accounts/ADDRESS/staking-payouts` fetch staking payouts for `ADDRESS`.](/src/controllers/accounts/AccountsStakingPayoutsController.ts) +- [`/accounts/ADDRESS/balance-info` fetch balances info for `ADDRESS`](src/controllers/accounts/AccountsBalanceInfoController.ts) (Replaces `/balance/ADDRESS`.) + - [`/block` fetch latest finalized block details.](/src/controllers/blocks/BlocksController.ts) - [`/block/NUMBER` fetch block details at the block identified by 'NUMBER`.](/src/controllers/blocks/BlocksController.ts) -- [`/balance/ADDRESS` fetch balances for `ADDRESS` at latest finalized block.](src/controllers/accounts/AccountsBalanceInfoController.ts) - -- [`/balance/ADDRESS/NUMBER` fetch balances for `ADDRESS` at the block identified by 'NUMBER`.](src/controllers/accounts/AccountsBalanceInfoController.ts) - - [`/staking/ADDRESS` fetch the staking info for `ADDRESS` at latest finalized block.](src/controllers/accounts/AccountsStakingInfoController.ts) - [`/staking/ADDRESS/NUMBER` fetch the staking info for `ADDRESS` at the block identified by 'NUMBER`.](src/controllers/accounts/AccountsStakingInfoController.ts) diff --git a/openapi/openapi-proposal.yaml b/openapi/openapi-proposal.yaml index 0c8529e18..9184db86a 100755 --- a/openapi/openapi-proposal.yaml +++ b/openapi/openapi-proposal.yaml @@ -26,7 +26,7 @@ paths: summary: Get balance information for an account. description: Returns information about an account's balance. Replaces `/balance/{address}` from versions < v1.0.0. - operationId: getBalanceSummaryByAccountId + operationId: getAccountBalanceInfo parameters: - name: accountId in: path diff --git a/src/controllers/accounts/AccountsBalanceInfoController.ts b/src/controllers/accounts/AccountsBalanceInfoController.ts new file mode 100644 index 000000000..a2a924c42 --- /dev/null +++ b/src/controllers/accounts/AccountsBalanceInfoController.ts @@ -0,0 +1,73 @@ +import { ApiPromise } from '@polkadot/api'; +import { RequestHandler } from 'express'; +import { IAddressParam } from 'src/types/requests'; + +import { validateAddress } from '../../middleware'; +import { AccountsBalanceInfoService } from '../../services'; +import AbstractController from '../AbstractController'; + +/** + * GET balance information for an address. + * + * Paths: + * - `address`: The address to query. + * + * Query + * - (Optional) `number`: Block hash or height at which to query. If not provided, queries + * finalized head. + * + * Returns: + * - `at`: Block number and hash at which the call was made. + * - `nonce`: Account nonce. + * - `free`: Free balance of the account. Not equivalent to _spendable_ balance. This is the only + * balance that matters in terms of most operations on tokens. + * - `reserved`: Reserved balance of the account. + * - `miscFrozen`: The amount that `free` may not drop below when withdrawing for anything except + * transaction fee payment. + * - `feeFrozen`: The amount that `free` may not drop below when withdrawing specifically for + * transaction fee payment. + * - `locks`: Array of locks on a balance. There can be many of these on an account and they + * "overlap", so the same balance is frozen by multiple locks. Contains: + * - `id`: An identifier for this lock. Only one lock may be in existence for each identifier. + * - `amount`: The amount below which the free balance may not drop with this lock in effect. + * - `reasons`: If true, then the lock remains in effect even for payment of transaction fees. + * + * Substrate Reference: + * - FRAME System: https://crates.parity.io/frame_system/index.html + * - Balances Pallet: https://crates.parity.io/pallet_balances/index.html + * - `AccountInfo`: https://crates.parity.io/frame_system/struct.AccountInfo.html + * - `AccountData`: https://crates.parity.io/pallet_balances/struct.AccountData.html + * - `BalanceLock`: https://crates.parity.io/pallet_balances/struct.BalanceLock.html + */ +export default class AccountsBalanceController extends AbstractController< + AccountsBalanceInfoService +> { + constructor(api: ApiPromise) { + super(api, '/balance/:address', new AccountsBalanceInfoService(api)); + this.initRoutes(); + } + + protected initRoutes(): void { + this.router.use(this.path, validateAddress); + + this.safeMountAsyncGetHandlers([['', this.getAccountBalanceInfo]]); + } + + /** + * Get the latest account balance summary of `address`. + * + * @param req Express Request + * @param res Express Response + */ + private getAccountBalanceInfo: RequestHandler = async ( + { params: { address }, query: { at } }, + res + ): Promise => { + const hash = await this.getHashFromAt(at); + + AccountsBalanceController.sanitizedSend( + res, + await this.service.fetchAccountBalanceInfo(hash, address) + ); + }; +} diff --git a/src/controllers/accounts/index.ts b/src/controllers/accounts/index.ts index 03d5cfb3e..f1f5fae71 100644 --- a/src/controllers/accounts/index.ts +++ b/src/controllers/accounts/index.ts @@ -1 +1,2 @@ export { default as AccountsStakingPayouts } from './AccountsStakingPayoutsController'; +export { default as AccountsBalanceInfo } from './AccountsBalanceInfoController'; diff --git a/src/main.ts b/src/main.ts index 56703c1a7..5eb47d5b6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -99,12 +99,16 @@ async function main() { const runtimeSpecController = new controllers.RuntimeSpec(api); const runtimeMetadataController = new controllers.RuntimeMetadata(api); const transactionDryRunController = new controllers.TransactionDryRun(api); + const accountsBalanceInfoController = new controllers.AccountsBalanceInfo( + api + ); // Create our App const app = new App({ preMiddleware, controllers: [ accountsStakingPayoutsController, + accountsBalanceInfoController, nodeNetworkController, nodeVersionController, nodeTransactionPoolController, diff --git a/src/services/accounts/AccountsBalanceInfoService.spec.ts b/src/services/accounts/AccountsBalanceInfoService.spec.ts new file mode 100644 index 000000000..97e3f17ed --- /dev/null +++ b/src/services/accounts/AccountsBalanceInfoService.spec.ts @@ -0,0 +1,21 @@ +import { sanitizeNumbers } from '../../sanitize/sanitizeNumbers'; +import { blockHash789629, mockApi, testAddress } from '../test-helpers/mock'; +import * as accountsBalanceInfo789629 from '../test-helpers/responses/accounts/balanceInfo789629.json'; +import { AccountsBalanceInfoService } from './AccountsBalanceInfoService'; + +const accountsBalanceInfoService = new AccountsBalanceInfoService(mockApi); + +describe('AccountsBalanceInfoService', () => { + describe('fetchAccountBalanceInfo', () => { + it('works when ApiPromise works (block 789629)', async () => { + expect( + sanitizeNumbers( + await accountsBalanceInfoService.fetchAccountBalanceInfo( + blockHash789629, + testAddress + ) + ) + ).toStrictEqual(accountsBalanceInfo789629); + }); + }); +}); diff --git a/src/services/accounts/AccountsBalanceInfoService.ts b/src/services/accounts/AccountsBalanceInfoService.ts new file mode 100644 index 000000000..0ebe19601 --- /dev/null +++ b/src/services/accounts/AccountsBalanceInfoService.ts @@ -0,0 +1,55 @@ +import { BlockHash } from '@polkadot/types/interfaces'; +import { IAccountBalanceInfo } from 'src/types/responses'; + +import { AbstractService } from '../AbstractService'; + +export class AccountsBalanceInfoService extends AbstractService { + /** + * Fetch balance information for an account at a given block. + * + * @param hash `BlockHash` to make call at + * @param address address of the account to get the balance info of + */ + async fetchAccountBalanceInfo( + hash: BlockHash, + address: string + ): Promise { + const api = await this.ensureMeta(hash); + + const [header, locks, sysAccount] = await Promise.all([ + api.rpc.chain.getHeader(hash), + api.query.balances.locks.at(hash, address), + api.query.system.account.at(hash, address), + ]); + + const account = + sysAccount.data != null + ? sysAccount.data + : await api.query.balances.account.at(hash, address); + + const at = { + hash, + height: header.number.toNumber().toString(10), + }; + + if (account && locks && sysAccount) { + const { free, reserved, miscFrozen, feeFrozen } = account; + const { nonce } = sysAccount; + + return { + at, + nonce, + free, + reserved, + miscFrozen, + feeFrozen, + locks, + }; + } else { + throw { + at, + error: 'Account not found', + }; + } + } +} diff --git a/src/services/accounts/index.ts b/src/services/accounts/index.ts index 884f9e649..63b805ead 100644 --- a/src/services/accounts/index.ts +++ b/src/services/accounts/index.ts @@ -1 +1,2 @@ export * from './AccountsStakingPayoutsService'; +export * from './AccountsBalanceInfoService'; From 9e3cf2c6aaa17e5e60e7c2b759564b2395b7ee1e Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sat, 29 Aug 2020 21:07:35 -0700 Subject: [PATCH 02/20] update uri --- src/controllers/accounts/AccountsBalanceInfoController.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/controllers/accounts/AccountsBalanceInfoController.ts b/src/controllers/accounts/AccountsBalanceInfoController.ts index a2a924c42..df3684266 100644 --- a/src/controllers/accounts/AccountsBalanceInfoController.ts +++ b/src/controllers/accounts/AccountsBalanceInfoController.ts @@ -43,7 +43,11 @@ export default class AccountsBalanceController extends AbstractController< AccountsBalanceInfoService > { constructor(api: ApiPromise) { - super(api, '/balance/:address', new AccountsBalanceInfoService(api)); + super( + api, + '/accounts/:address/balance-info', + new AccountsBalanceInfoService(api) + ); this.initRoutes(); } From 77494ab5509c5c35492a67beeaf5592dbee63b88 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sat, 29 Aug 2020 23:30:16 -0700 Subject: [PATCH 03/20] save --- .../accounts/AccountsBalanceInfoController.ts | 6 +-- .../AccountsStakingInfoController.ts | 51 +++++++------------ src/controllers/accounts/index.ts | 1 + src/controllers/v0/v0accounts/index.ts | 2 +- src/main.ts | 10 ++-- .../AccountsStakingInfoService.spec.ts | 26 +++++----- .../AccountsStakingInfoService.ts | 19 +++---- src/services/accounts/index.ts | 1 + src/services/v0/v0accounts/index.ts | 2 +- 9 files changed, 53 insertions(+), 65 deletions(-) rename src/controllers/{v0/v0accounts => accounts}/AccountsStakingInfoController.ts (68%) rename src/services/{v0/v0accounts => accounts}/AccountsStakingInfoService.spec.ts (72%) rename src/services/{v0/v0accounts => accounts}/AccountsStakingInfoService.ts (80%) diff --git a/src/controllers/accounts/AccountsBalanceInfoController.ts b/src/controllers/accounts/AccountsBalanceInfoController.ts index df3684266..26516be6c 100644 --- a/src/controllers/accounts/AccountsBalanceInfoController.ts +++ b/src/controllers/accounts/AccountsBalanceInfoController.ts @@ -12,9 +12,9 @@ import AbstractController from '../AbstractController'; * Paths: * - `address`: The address to query. * - * Query - * - (Optional) `number`: Block hash or height at which to query. If not provided, queries - * finalized head. + * Query: + * - (Optional)`at`: Block at which to retrieve runtime version information at. Block + * identifier, as the block height or block hash. Defaults to most recent block. * * Returns: * - `at`: Block number and hash at which the call was made. diff --git a/src/controllers/v0/v0accounts/AccountsStakingInfoController.ts b/src/controllers/accounts/AccountsStakingInfoController.ts similarity index 68% rename from src/controllers/v0/v0accounts/AccountsStakingInfoController.ts rename to src/controllers/accounts/AccountsStakingInfoController.ts index c9f64093d..39b71ade9 100644 --- a/src/controllers/v0/v0accounts/AccountsStakingInfoController.ts +++ b/src/controllers/accounts/AccountsStakingInfoController.ts @@ -1,18 +1,20 @@ import { ApiPromise } from '@polkadot/api'; import { RequestHandler } from 'express'; -import { IAddressNumberParams, IAddressParam } from 'src/types/requests'; +import { IAddressParam } from 'src/types/requests'; -import { validateAddress } from '../../../middleware/'; -import { AccountsStakingInfoService } from '../../../services/v0'; -import AbstractController from '../../AbstractController'; +import { validateAddress } from '../../middleware'; +import { AccountsStakingInfoService } from '../../services'; +import AbstractController from '../AbstractController'; /** * GET staking information for an address. * * Paths: * - `address`: The _Stash_ address for staking. - * - (Optional) `number`: Block hash or height at which to query. If not provided, queries - * finalized head. + * + * Query: + * - (Optional)`at`: Block at which to retrieve runtime version information at. Block + * identifier, as the block height or block hash. Defaults to most recent block. * * Returns: * - `at`: Block number and hash at which the call was made. @@ -49,17 +51,18 @@ export default class AccountsStakingInfoController extends AbstractController< AccountsStakingInfoService > { constructor(api: ApiPromise) { - super(api, '/staking/:address', new AccountsStakingInfoService(api)); + super( + api, + '/accounts/:address/staking-info', + new AccountsStakingInfoService(api) + ); this.initRoutes(); } protected initRoutes(): void { this.router.use(this.path, validateAddress); - this.safeMountAsyncGetHandlers([ - ['', this.getAccountStakingSummary], - ['/:number', this.getAccountStakingSummaryAtBlock], - ]); + this.safeMountAsyncGetHandlers([['', this.getAccountStakingInfo]]); } /** @@ -68,31 +71,11 @@ export default class AccountsStakingInfoController extends AbstractController< * @param req Express Request * @param res Express Response */ - private getAccountStakingSummary: RequestHandler = async ( - { params: { address } }, + private getAccountStakingInfo: RequestHandler = async ( + { params: { address }, query: { at } }, res ): Promise => { - const hash = await this.api.rpc.chain.getFinalizedHead(); - - AccountsStakingInfoController.sanitizedSend( - res, - await this.service.fetchAccountStakingInfo(hash, address) - ); - }; - - /** - * Get the account staking summary of `address` at a block identified by its - * hash or number. - * - * @param req Express Request - * @param res Express Response - */ - private getAccountStakingSummaryAtBlock: RequestHandler< - IAddressNumberParams - > = async (req, res): Promise => { - const { address, number } = req.params; - const hash = await this.getHashForBlock(number); - + const hash = await this.getHashFromAt(at); AccountsStakingInfoController.sanitizedSend( res, await this.service.fetchAccountStakingInfo(hash, address) diff --git a/src/controllers/accounts/index.ts b/src/controllers/accounts/index.ts index f1f5fae71..c927291d1 100644 --- a/src/controllers/accounts/index.ts +++ b/src/controllers/accounts/index.ts @@ -1,2 +1,3 @@ export { default as AccountsStakingPayouts } from './AccountsStakingPayoutsController'; export { default as AccountsBalanceInfo } from './AccountsBalanceInfoController'; +export { default as AccountsStakingInfo } from './AccountsStakingInfoController'; diff --git a/src/controllers/v0/v0accounts/index.ts b/src/controllers/v0/v0accounts/index.ts index 7af601abd..8a6fbcfa7 100644 --- a/src/controllers/v0/v0accounts/index.ts +++ b/src/controllers/v0/v0accounts/index.ts @@ -1,3 +1,3 @@ export { default as v0AccountsBalanceInfo } from './AccountsBalanceInfoController'; -export { default as v0AccountsStakingInfo } from './AccountsStakingInfoController'; +export { default as v0AccountsStakingInfo } from '../../accounts/AccountsStakingInfoController'; export { default as v0AccountsVestingInfo } from './AccountsVestingInfoController'; diff --git a/src/main.ts b/src/main.ts index 5eb47d5b6..be8c1fdc7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -90,6 +90,12 @@ async function main() { const accountsStakingPayoutsController = new controllers.AccountsStakingPayouts( api ); + const accountsBalanceInfoController = new controllers.AccountsBalanceInfo( + api + ); + const accountsStakingInfoController = new controllers.AccountsStakingInfo( + api + ); const nodeNetworkController = new controllers.NodeNetwork(api); const nodeVersionController = new controllers.NodeVersion(api); const nodeTransactionPoolController = new controllers.NodeTransactionPool( @@ -99,9 +105,6 @@ async function main() { const runtimeSpecController = new controllers.RuntimeSpec(api); const runtimeMetadataController = new controllers.RuntimeMetadata(api); const transactionDryRunController = new controllers.TransactionDryRun(api); - const accountsBalanceInfoController = new controllers.AccountsBalanceInfo( - api - ); // Create our App const app = new App({ @@ -109,6 +112,7 @@ async function main() { controllers: [ accountsStakingPayoutsController, accountsBalanceInfoController, + accountsStakingInfoController, nodeNetworkController, nodeVersionController, nodeTransactionPoolController, diff --git a/src/services/v0/v0accounts/AccountsStakingInfoService.spec.ts b/src/services/accounts/AccountsStakingInfoService.spec.ts similarity index 72% rename from src/services/v0/v0accounts/AccountsStakingInfoService.spec.ts rename to src/services/accounts/AccountsStakingInfoService.spec.ts index 2423f8386..27840d88d 100644 --- a/src/services/v0/v0accounts/AccountsStakingInfoService.spec.ts +++ b/src/services/accounts/AccountsStakingInfoService.spec.ts @@ -1,7 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { sanitizeNumbers } from '../../../sanitize/sanitizeNumbers'; -import { polkadotRegistry } from '../../../test-helpers/registries'; +import { BadRequest, InternalServerError } from 'http-errors'; + +import { sanitizeNumbers } from '../../sanitize/sanitizeNumbers'; +import { polkadotRegistry } from '../../test-helpers/registries'; import { blockHash789629, bondedAt, @@ -9,8 +11,8 @@ import { mockApi, testAddress, testAddressController, -} from '../../test-helpers/mock'; -import * as response789629 from '../../test-helpers/responses/accounts/stakingInfo789629.json'; +} from '../test-helpers/mock'; +import * as response789629 from '../test-helpers/responses/accounts/stakingInfo789629.json'; import { AccountsStakingInfoService } from './AccountsStakingInfoService'; const accountStakingInfoService = new AccountsStakingInfoService(mockApi); @@ -39,10 +41,9 @@ describe('AccountsStakingInfoService', () => { blockHash789629, 'NotStash' ) - ).rejects.toStrictEqual({ - error: 'The address NotStash is not a stash address.', - statusCode: 400, - }); + ).rejects.toStrictEqual( + new BadRequest('The address NotStash is not a stash address.') + ); (mockApi.query.staking.bonded as any).at = bondedAt; }); @@ -58,10 +59,11 @@ describe('AccountsStakingInfoService', () => { blockHash789629, testAddress ) - ).rejects.toStrictEqual({ - error: `Staking ledger could not be found for controller address "${testAddressController.toString()}"`, - statusCode: 404, - }); + ).rejects.toStrictEqual( + new InternalServerError( + `Staking ledger could not be found for controller address "${testAddressController.toString()}"` + ) + ); (mockApi.query.staking.ledger as any).at = ledgerAt; }); diff --git a/src/services/v0/v0accounts/AccountsStakingInfoService.ts b/src/services/accounts/AccountsStakingInfoService.ts similarity index 80% rename from src/services/v0/v0accounts/AccountsStakingInfoService.ts rename to src/services/accounts/AccountsStakingInfoService.ts index 55903a2ea..8ff325704 100644 --- a/src/services/v0/v0accounts/AccountsStakingInfoService.ts +++ b/src/services/accounts/AccountsStakingInfoService.ts @@ -1,7 +1,8 @@ import { BlockHash } from '@polkadot/types/interfaces'; +import { BadRequest, InternalServerError } from 'http-errors'; import { IAccountStakingInfo } from 'src/types/responses'; -import { AbstractService } from '../../AbstractService'; +import { AbstractService } from '../AbstractService'; export class AccountsStakingInfoService extends AbstractService { /** @@ -27,11 +28,9 @@ export class AccountsStakingInfoService extends AbstractService { }; if (controllerOption.isNone) { - throw { - // TODO convert to newer type error - error: `The address ${stash} is not a stash address.`, - statusCode: 400, - }; + throw new BadRequest( + `The address ${stash} is not a stash address.` + ); } const controller = controllerOption.unwrap(); @@ -49,12 +48,10 @@ export class AccountsStakingInfoService extends AbstractService { const stakingLedger = stakingLedgerOption.unwrapOr(null); if (stakingLedger === null) { - // TODO convert to newer type error // should never throw because by time we get here we know we have a bonded pair - throw { - error: `Staking ledger could not be found for controller address "${controller.toString()}"`, - statusCode: 404, - }; + throw new InternalServerError( + `Staking ledger could not be found for controller address "${controller.toString()}"` + ); } const numSlashingSpans = slashingSpansOption.isSome diff --git a/src/services/accounts/index.ts b/src/services/accounts/index.ts index 63b805ead..7611749d2 100644 --- a/src/services/accounts/index.ts +++ b/src/services/accounts/index.ts @@ -1,2 +1,3 @@ export * from './AccountsStakingPayoutsService'; export * from './AccountsBalanceInfoService'; +export * from './AccountsStakingInfoService'; diff --git a/src/services/v0/v0accounts/index.ts b/src/services/v0/v0accounts/index.ts index 2b08eda1a..a215f56a5 100644 --- a/src/services/v0/v0accounts/index.ts +++ b/src/services/v0/v0accounts/index.ts @@ -1,3 +1,3 @@ -export * from './AccountsStakingInfoService'; +export * from '../../accounts/AccountsStakingInfoService'; export * from './AccountsBalanceInfoService'; export * from './AccountsVestingInfoService'; From 6e4959f0daedd6e992428ce67d903f06ef367900 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sat, 29 Aug 2020 23:48:34 -0700 Subject: [PATCH 04/20] save --- .../AccountsStakingInfoController.ts | 101 ++++++++++++++++++ .../AccountsStakingInfoService.spec.ts | 69 ++++++++++++ .../v0accounts/AccountsStakingInfoService.ts | 72 +++++++++++++ 3 files changed, 242 insertions(+) create mode 100644 src/controllers/v0/v0accounts/AccountsStakingInfoController.ts create mode 100644 src/services/v0/v0accounts/AccountsStakingInfoService.spec.ts create mode 100644 src/services/v0/v0accounts/AccountsStakingInfoService.ts diff --git a/src/controllers/v0/v0accounts/AccountsStakingInfoController.ts b/src/controllers/v0/v0accounts/AccountsStakingInfoController.ts new file mode 100644 index 000000000..c9f64093d --- /dev/null +++ b/src/controllers/v0/v0accounts/AccountsStakingInfoController.ts @@ -0,0 +1,101 @@ +import { ApiPromise } from '@polkadot/api'; +import { RequestHandler } from 'express'; +import { IAddressNumberParams, IAddressParam } from 'src/types/requests'; + +import { validateAddress } from '../../../middleware/'; +import { AccountsStakingInfoService } from '../../../services/v0'; +import AbstractController from '../../AbstractController'; + +/** + * GET staking information for an address. + * + * Paths: + * - `address`: The _Stash_ address for staking. + * - (Optional) `number`: Block hash or height at which to query. If not provided, queries + * finalized head. + * + * Returns: + * - `at`: Block number and hash at which the call was made. + * - `rewardDestination`: The account to which rewards will be paid. Can be 'Staked' (Stash + * account, adding to the amount at stake), 'Stash' (Stash address, not adding to the amount at + * stake), or 'Controller' (Controller address). + * - `controller`: Controller address for the given Stash. + * - `numSlashingSpans`: Number of slashing spans on Stash account; `null` if provided address is + * not a Controller. + * - `staking`: The staking ledger. Empty object if provided address is not a Controller. + * - `stash`: The stash account whose balance is actually locked and at stake. + * - `total`: The total amount of the stash's balance that we are currently accounting for. + * Simply `active + unlocking`. + * - `active`: The total amount of the stash's balance that will be at stake in any forthcoming + * eras. + * - `unlocking`: Any balance that is becoming free, which may eventually be transferred out of + * the stash (assuming it doesn't get slashed first). Represented as an array of objects, each + * with an `era` at which `value` will be unlocked. + * - `claimedRewards`: Array of eras for which the stakers behind a validator have claimed + * rewards. Only updated for _validators._ + * + * Note: Runtime versions of Kusama less than 1062 will either have `lastReward` in place of + * `claimedRewards`, or no field at all. This is related to changes in reward distribution. See: + * - Lazy Payouts: https://github.com/paritytech/substrate/pull/4474 + * - Simple Payouts: https://github.com/paritytech/substrate/pull/5406 + * + * Substrate Reference: + * - Staking Pallet: https://crates.parity.io/pallet_staking/index.html + * - `RewardDestination`: https://crates.parity.io/pallet_staking/enum.RewardDestination.html + * - `Bonded`: https://crates.parity.io/pallet_staking/struct.Bonded.html + * - `StakingLedger`: https://crates.parity.io/pallet_staking/struct.StakingLedger.html + */ +export default class AccountsStakingInfoController extends AbstractController< + AccountsStakingInfoService +> { + constructor(api: ApiPromise) { + super(api, '/staking/:address', new AccountsStakingInfoService(api)); + this.initRoutes(); + } + + protected initRoutes(): void { + this.router.use(this.path, validateAddress); + + this.safeMountAsyncGetHandlers([ + ['', this.getAccountStakingSummary], + ['/:number', this.getAccountStakingSummaryAtBlock], + ]); + } + + /** + * Get the latest account staking summary of `address`. + * + * @param req Express Request + * @param res Express Response + */ + private getAccountStakingSummary: RequestHandler = async ( + { params: { address } }, + res + ): Promise => { + const hash = await this.api.rpc.chain.getFinalizedHead(); + + AccountsStakingInfoController.sanitizedSend( + res, + await this.service.fetchAccountStakingInfo(hash, address) + ); + }; + + /** + * Get the account staking summary of `address` at a block identified by its + * hash or number. + * + * @param req Express Request + * @param res Express Response + */ + private getAccountStakingSummaryAtBlock: RequestHandler< + IAddressNumberParams + > = async (req, res): Promise => { + const { address, number } = req.params; + const hash = await this.getHashForBlock(number); + + AccountsStakingInfoController.sanitizedSend( + res, + await this.service.fetchAccountStakingInfo(hash, address) + ); + }; +} diff --git a/src/services/v0/v0accounts/AccountsStakingInfoService.spec.ts b/src/services/v0/v0accounts/AccountsStakingInfoService.spec.ts new file mode 100644 index 000000000..2423f8386 --- /dev/null +++ b/src/services/v0/v0accounts/AccountsStakingInfoService.spec.ts @@ -0,0 +1,69 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { sanitizeNumbers } from '../../../sanitize/sanitizeNumbers'; +import { polkadotRegistry } from '../../../test-helpers/registries'; +import { + blockHash789629, + bondedAt, + ledgerAt, + mockApi, + testAddress, + testAddressController, +} from '../../test-helpers/mock'; +import * as response789629 from '../../test-helpers/responses/accounts/stakingInfo789629.json'; +import { AccountsStakingInfoService } from './AccountsStakingInfoService'; + +const accountStakingInfoService = new AccountsStakingInfoService(mockApi); + +describe('AccountsStakingInfoService', () => { + describe('fetchAccountStakingInfo', () => { + it('works with a valid stash address (block 789629)', async () => { + expect( + sanitizeNumbers( + await accountStakingInfoService.fetchAccountStakingInfo( + blockHash789629, + testAddress + ) + ) + ).toStrictEqual(response789629); + }); + + it('throws a 400 when the given address is not a stash', async () => { + (mockApi.query.staking.bonded as any).at = () => + Promise.resolve().then(() => + polkadotRegistry.createType('Option', null) + ); + + await expect( + accountStakingInfoService.fetchAccountStakingInfo( + blockHash789629, + 'NotStash' + ) + ).rejects.toStrictEqual({ + error: 'The address NotStash is not a stash address.', + statusCode: 400, + }); + + (mockApi.query.staking.bonded as any).at = bondedAt; + }); + + it('throws a 404 when the staking ledger cannot be found', async () => { + (mockApi.query.staking.ledger as any).at = () => + Promise.resolve().then(() => + polkadotRegistry.createType('Option', null) + ); + + await expect( + accountStakingInfoService.fetchAccountStakingInfo( + blockHash789629, + testAddress + ) + ).rejects.toStrictEqual({ + error: `Staking ledger could not be found for controller address "${testAddressController.toString()}"`, + statusCode: 404, + }); + + (mockApi.query.staking.ledger as any).at = ledgerAt; + }); + }); +}); diff --git a/src/services/v0/v0accounts/AccountsStakingInfoService.ts b/src/services/v0/v0accounts/AccountsStakingInfoService.ts new file mode 100644 index 000000000..55903a2ea --- /dev/null +++ b/src/services/v0/v0accounts/AccountsStakingInfoService.ts @@ -0,0 +1,72 @@ +import { BlockHash } from '@polkadot/types/interfaces'; +import { IAccountStakingInfo } from 'src/types/responses'; + +import { AbstractService } from '../../AbstractService'; + +export class AccountsStakingInfoService extends AbstractService { + /** + * Fetch staking information for a _Stash_ account at a given block. + * + * @param hash `BlockHash` to make call at + * @param stash address of the _Stash_ account to get the staking info of + */ + async fetchAccountStakingInfo( + hash: BlockHash, + stash: string + ): Promise { + const api = await this.ensureMeta(hash); + + const [header, controllerOption] = await Promise.all([ + api.rpc.chain.getHeader(hash), + api.query.staking.bonded.at(hash, stash), // Option representing the controller + ]); + + const at = { + hash, + height: header.number.unwrap().toString(10), + }; + + if (controllerOption.isNone) { + throw { + // TODO convert to newer type error + error: `The address ${stash} is not a stash address.`, + statusCode: 400, + }; + } + + const controller = controllerOption.unwrap(); + + const [ + stakingLedgerOption, + rewardDestination, + slashingSpansOption, + ] = await Promise.all([ + api.query.staking.ledger.at(hash, controller), + api.query.staking.payee.at(hash, stash), + api.query.staking.slashingSpans.at(hash, stash), + ]); + + const stakingLedger = stakingLedgerOption.unwrapOr(null); + + if (stakingLedger === null) { + // TODO convert to newer type error + // should never throw because by time we get here we know we have a bonded pair + throw { + error: `Staking ledger could not be found for controller address "${controller.toString()}"`, + statusCode: 404, + }; + } + + const numSlashingSpans = slashingSpansOption.isSome + ? slashingSpansOption.unwrap().prior.length + 1 + : 0; + + return { + at, + controller, + rewardDestination, + numSlashingSpans, + staking: stakingLedger, + }; + } +} From 35e8acc7e0d2b57cdfc9009e0423792cd0c5bf17 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 30 Aug 2020 17:13:24 -0700 Subject: [PATCH 05/20] Add vesting service --- .../AccountsVestingInfoService.spec.ts | 21 +++++++++ .../accounts/AccountsVestingInfoService.ts | 43 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/services/accounts/AccountsVestingInfoService.spec.ts create mode 100644 src/services/accounts/AccountsVestingInfoService.ts diff --git a/src/services/accounts/AccountsVestingInfoService.spec.ts b/src/services/accounts/AccountsVestingInfoService.spec.ts new file mode 100644 index 000000000..c70a50f48 --- /dev/null +++ b/src/services/accounts/AccountsVestingInfoService.spec.ts @@ -0,0 +1,21 @@ +import { sanitizeNumbers } from '../../sanitize/sanitizeNumbers'; +import { blockHash789629, mockApi, testAddress } from '../test-helpers/mock'; +import * as response789629 from '../test-helpers/responses/accounts/vestingInfo789629.json'; +import { AccountsVestingInfoService } from './AccountsVestingInfoService'; + +const accountsVestingInfoService = new AccountsVestingInfoService(mockApi); + +describe('AccountVestingInfoService', () => { + describe('fetchAccountVestingInfo', () => { + it('works when ApiPromise works (block 789629)', async () => { + expect( + sanitizeNumbers( + await accountsVestingInfoService.fetchAccountVestingInfo( + blockHash789629, + testAddress + ) + ) + ).toStrictEqual(response789629); + }); + }); +}); diff --git a/src/services/accounts/AccountsVestingInfoService.ts b/src/services/accounts/AccountsVestingInfoService.ts new file mode 100644 index 000000000..29d36943f --- /dev/null +++ b/src/services/accounts/AccountsVestingInfoService.ts @@ -0,0 +1,43 @@ +import { Metadata } from '@polkadot/types'; +import { BlockHash } from '@polkadot/types/interfaces'; +import { IAccountVestingInfo } from 'src/types/responses'; + +import { AbstractService } from '../AbstractService'; + +export class AccountsVestingInfoService extends AbstractService { + /** + * Fetch vesting information for an account at a given block. + * + * @param hash `BlockHash` to make call at + * @param address address of the account to get the vesting info of + */ + async fetchAccountVestingInfo( + hash: BlockHash, + address: string + ): Promise { + const api = await this.ensureMeta(hash); + + const [{ number }, vesting] = await Promise.all([ + api.rpc.chain.getHeader(hash), + api.query.vesting.vesting.at(hash, address), + ]); + + const at = { + hash, + height: number.toNumber().toString(10), + }; + + return { + at, + vesting: vesting.isNone ? {} : vesting.unwrap(), + }; + } + + async fetchMetadata(hash: BlockHash): Promise { + const api = await this.ensureMeta(hash); + + const metadata = await api.rpc.state.getMetadata(hash); + + return metadata; + } +} From 3c00fed864187528a79e8a5e34739019fb9ed981 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 30 Aug 2020 17:23:39 -0700 Subject: [PATCH 06/20] save --- .../accounts/AccountsStakingInfoController.ts | 1 + .../accounts/AccountsVestingInfoController.ts | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/controllers/accounts/AccountsVestingInfoController.ts diff --git a/src/controllers/accounts/AccountsStakingInfoController.ts b/src/controllers/accounts/AccountsStakingInfoController.ts index 39b71ade9..621656331 100644 --- a/src/controllers/accounts/AccountsStakingInfoController.ts +++ b/src/controllers/accounts/AccountsStakingInfoController.ts @@ -76,6 +76,7 @@ export default class AccountsStakingInfoController extends AbstractController< res ): Promise => { const hash = await this.getHashFromAt(at); + AccountsStakingInfoController.sanitizedSend( res, await this.service.fetchAccountStakingInfo(hash, address) diff --git a/src/controllers/accounts/AccountsVestingInfoController.ts b/src/controllers/accounts/AccountsVestingInfoController.ts new file mode 100644 index 000000000..9b8b80b1d --- /dev/null +++ b/src/controllers/accounts/AccountsVestingInfoController.ts @@ -0,0 +1,63 @@ +import { ApiPromise } from '@polkadot/api'; +import { RequestHandler } from 'express'; +import { IAddressNumberParams, IAddressParam } from 'src/types/requests'; + +import { validateAddress } from '../../middleware'; +import { AccountsVestingInfoService } from '../../services/v0'; +import AbstractController from '../AbstractController'; + +/** + * GET vesting information for an address. + * + * Paths: + * - `address`: Address to query. + * - (Optional) `number`: Block hash or height at which to query. If not provided, queries + * finalized head. + * + * Returns: + * - `at`: Block number and hash at which the call was made. + * - `vesting`: Vesting schedule for an account. + * - `locked`: Number of tokens locked at start. + * - `perBlock`: Number of tokens that gets unlocked every block after `startingBlock`. + * - `startingBlock`: Starting block for unlocking(vesting). + * + * Substrate Reference: + * - Vesting Pallet: https://crates.parity.io/pallet_vesting/index.html + * - `VestingInfo`: https://crates.parity.io/pallet_vesting/struct.VestingInfo.html + */ +export default class AccountsVestingInfoController extends AbstractController< + AccountsVestingInfoService +> { + constructor(api: ApiPromise) { + super( + api, + '/accounts/:address/vesting-info', + new AccountsVestingInfoService(api) + ); + this.initRoutes(); + } + + protected initRoutes(): void { + this.router.use(this.path, validateAddress); + + this.safeMountAsyncGetHandlers([['', this.getAccountVestingInfo]]); + } + + /** + * Get the latest account vesting summary of `address`. + * + * @param req Express Request + * @param res Express Response + */ + private getAccountVestingInfo: RequestHandler = async ( + { params: { address }, query: { at } }, + res + ): Promise => { + const hash = await this.getHashFromAt(at); + + AccountsVestingInfoController.sanitizedSend( + res, + await this.service.fetchAccountVestingInfo(hash, address) + ); + }; +} From a628449e0253efc7057919fb54ba9682745c867c Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 30 Aug 2020 17:25:53 -0700 Subject: [PATCH 07/20] save --- src/controllers/accounts/AccountsVestingInfoController.ts | 2 +- src/controllers/accounts/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/accounts/AccountsVestingInfoController.ts b/src/controllers/accounts/AccountsVestingInfoController.ts index 9b8b80b1d..b8a5c5a20 100644 --- a/src/controllers/accounts/AccountsVestingInfoController.ts +++ b/src/controllers/accounts/AccountsVestingInfoController.ts @@ -1,6 +1,6 @@ import { ApiPromise } from '@polkadot/api'; import { RequestHandler } from 'express'; -import { IAddressNumberParams, IAddressParam } from 'src/types/requests'; +import { IAddressParam } from 'src/types/requests'; import { validateAddress } from '../../middleware'; import { AccountsVestingInfoService } from '../../services/v0'; diff --git a/src/controllers/accounts/index.ts b/src/controllers/accounts/index.ts index c927291d1..c18d34411 100644 --- a/src/controllers/accounts/index.ts +++ b/src/controllers/accounts/index.ts @@ -1,3 +1,4 @@ export { default as AccountsStakingPayouts } from './AccountsStakingPayoutsController'; export { default as AccountsBalanceInfo } from './AccountsBalanceInfoController'; export { default as AccountsStakingInfo } from './AccountsStakingInfoController'; +export { default as AccountsVestingInfo } from './AccountsVestingInfoController'; From 2736993a734a8d0142f7e931506f0ff369a798d1 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 30 Aug 2020 20:57:27 -0700 Subject: [PATCH 08/20] implement vesting --- .../accounts/AccountsVestingInfoController.ts | 8 ++++++-- src/main.ts | 4 ++++ src/services/accounts/AccountsVestingInfoService.ts | 9 --------- src/services/accounts/index.ts | 1 + 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/controllers/accounts/AccountsVestingInfoController.ts b/src/controllers/accounts/AccountsVestingInfoController.ts index b8a5c5a20..2975a3504 100644 --- a/src/controllers/accounts/AccountsVestingInfoController.ts +++ b/src/controllers/accounts/AccountsVestingInfoController.ts @@ -3,7 +3,7 @@ import { RequestHandler } from 'express'; import { IAddressParam } from 'src/types/requests'; import { validateAddress } from '../../middleware'; -import { AccountsVestingInfoService } from '../../services/v0'; +import { AccountsVestingInfoService } from '../../services'; import AbstractController from '../AbstractController'; /** @@ -14,6 +14,10 @@ import AbstractController from '../AbstractController'; * - (Optional) `number`: Block hash or height at which to query. If not provided, queries * finalized head. * + * Query params: + * - (Optional)`at`: Block at which to retrieve runtime version information at. Block + * identifier, as the block height or block hash. Defaults to most recent block. + * * Returns: * - `at`: Block number and hash at which the call was made. * - `vesting`: Vesting schedule for an account. @@ -44,7 +48,7 @@ export default class AccountsVestingInfoController extends AbstractController< } /** - * Get the latest account vesting summary of `address`. + * Get vesting information for an account. * * @param req Express Request * @param res Express Response diff --git a/src/main.ts b/src/main.ts index be8c1fdc7..c7bf91849 100644 --- a/src/main.ts +++ b/src/main.ts @@ -105,6 +105,9 @@ async function main() { const runtimeSpecController = new controllers.RuntimeSpec(api); const runtimeMetadataController = new controllers.RuntimeMetadata(api); const transactionDryRunController = new controllers.TransactionDryRun(api); + const accountsVestingInfoController = new controllers.AccountsVestingInfo( + api + ); // Create our App const app = new App({ @@ -113,6 +116,7 @@ async function main() { accountsStakingPayoutsController, accountsBalanceInfoController, accountsStakingInfoController, + accountsVestingInfoController, nodeNetworkController, nodeVersionController, nodeTransactionPoolController, diff --git a/src/services/accounts/AccountsVestingInfoService.ts b/src/services/accounts/AccountsVestingInfoService.ts index 29d36943f..1d183211d 100644 --- a/src/services/accounts/AccountsVestingInfoService.ts +++ b/src/services/accounts/AccountsVestingInfoService.ts @@ -1,4 +1,3 @@ -import { Metadata } from '@polkadot/types'; import { BlockHash } from '@polkadot/types/interfaces'; import { IAccountVestingInfo } from 'src/types/responses'; @@ -32,12 +31,4 @@ export class AccountsVestingInfoService extends AbstractService { vesting: vesting.isNone ? {} : vesting.unwrap(), }; } - - async fetchMetadata(hash: BlockHash): Promise { - const api = await this.ensureMeta(hash); - - const metadata = await api.rpc.state.getMetadata(hash); - - return metadata; - } } diff --git a/src/services/accounts/index.ts b/src/services/accounts/index.ts index 7611749d2..746962a72 100644 --- a/src/services/accounts/index.ts +++ b/src/services/accounts/index.ts @@ -1,3 +1,4 @@ export * from './AccountsStakingPayoutsService'; export * from './AccountsBalanceInfoService'; export * from './AccountsStakingInfoService'; +export * from './AccountsVestingInfoService'; From ff11b9dcd1cde7c7b273ace862522b080c41972c Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 30 Aug 2020 21:03:55 -0700 Subject: [PATCH 09/20] update readme --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1ac4c4657..535cb8f08 100644 --- a/README.md +++ b/README.md @@ -82,24 +82,22 @@ a 32-byte hex string (`0x` followed by 64 hexadecimal digits) that denotes the b - [`/accounts/ADDRESS/staking-payouts` fetch staking payouts for `ADDRESS`.](/src/controllers/accounts/AccountsStakingPayoutsController.ts) -- [`/accounts/ADDRESS/balance-info` fetch balances info for `ADDRESS`](src/controllers/accounts/AccountsBalanceInfoController.ts) (Replaces `/balance/ADDRESS`.) +- [`/accounts/ADDRESS/balance-info` fetch balances info for `ADDRESS`.](src/controllers/accounts/AccountsBalanceInfoController.ts) (Replaces `/balance/ADDRESS`.) + +- [`/accounts/ADDRESS/vesting-info` vesting info for `ADDRESS`.](src/controllers/accounts/AccountsVestingInfoController.ts) (Replaces `/vesting/ADDRESS`.) + +- [`/accounts/ADDRESS/staking-info` fetch the staking info for `ADDRESS`.](src/controllers/accounts/AccountsStakingInfoController.ts) (Replaces `/staking/ADDRESS`.) - [`/block` fetch latest finalized block details.](/src/controllers/blocks/BlocksController.ts) - [`/block/NUMBER` fetch block details at the block identified by 'NUMBER`.](/src/controllers/blocks/BlocksController.ts) -- [`/staking/ADDRESS` fetch the staking info for `ADDRESS` at latest finalized block.](src/controllers/accounts/AccountsStakingInfoController.ts) - - [`/staking/ADDRESS/NUMBER` fetch the staking info for `ADDRESS` at the block identified by 'NUMBER`.](src/controllers/accounts/AccountsStakingInfoController.ts) - [`/staking-info` fetch information on general staking progress at the latest finalized block.](src/controllers/pallets/PalletsStakingProgressController.ts) - [`/staking-info/NUMBER` fetch information on general staking progress at the block identified by 'NUMBER`.](src/controllers/pallets/PalletsStakingProgressController.ts) -- [`/vesting/ADDRESS` fetch the vesting info for `ADDRESS` at latest finalized block.](src/controllers/accounts/AccountsVestingInfoController.ts) - -- [`/vesting/ADDRESS/NUMBER` fetch the vesting info for `ADDRESS` at the block identified by 'NUMBER`.](src/controllers/accounts/AccountsVestingInfoController.ts) - - [`/node/network` fetch information about the Substrate node's activity in the peer-to-peer network.](src/controllers/node/NodeNetworkController.ts) - [`/node/transaction-pool` fetch pending extrinsics from the Substrate node.](src/controllers/node/NodeTransactionPoolController.ts) From 93a89a6f9901eedd9e46a93b305a4cbcf82aeaca Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 30 Aug 2020 21:06:28 -0700 Subject: [PATCH 10/20] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 535cb8f08..44f1e6109 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,6 @@ a 32-byte hex string (`0x` followed by 64 hexadecimal digits) that denotes the b - [`/block/NUMBER` fetch block details at the block identified by 'NUMBER`.](/src/controllers/blocks/BlocksController.ts) -- [`/staking/ADDRESS/NUMBER` fetch the staking info for `ADDRESS` at the block identified by 'NUMBER`.](src/controllers/accounts/AccountsStakingInfoController.ts) - - [`/staking-info` fetch information on general staking progress at the latest finalized block.](src/controllers/pallets/PalletsStakingProgressController.ts) - [`/staking-info/NUMBER` fetch information on general staking progress at the block identified by 'NUMBER`.](src/controllers/pallets/PalletsStakingProgressController.ts) From a517eb9260caecf0efcec7268bea4044bab6a1fe Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 30 Aug 2020 21:24:45 -0700 Subject: [PATCH 11/20] updates test to reflect error messages --- src/controllers/index.ts | 2 +- .../PalletsStakingProgressController.ts | 100 +++++++++ src/controllers/pallets/index.ts | 1 + src/services/index.ts | 4 +- .../PalletsStakingProgressService.spec.ts | 72 ++++++ .../pallets/PalletsStakingProgressService.ts | 207 ++++++++++++++++++ src/services/pallets/index.ts | 1 + 7 files changed, 383 insertions(+), 4 deletions(-) create mode 100644 src/controllers/pallets/PalletsStakingProgressController.ts create mode 100644 src/controllers/pallets/index.ts create mode 100644 src/services/pallets/PalletsStakingProgressService.spec.ts create mode 100644 src/services/pallets/PalletsStakingProgressService.ts create mode 100644 src/services/pallets/index.ts diff --git a/src/controllers/index.ts b/src/controllers/index.ts index d1a7d1d78..1d88c002d 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -2,7 +2,7 @@ export * from './transaction'; export * from './accounts'; // export * from './blocks'; export * from './runtime'; -// export * from './pallets'; +export * from './pallets'; export * from './node'; export * as v0 from './v0'; diff --git a/src/controllers/pallets/PalletsStakingProgressController.ts b/src/controllers/pallets/PalletsStakingProgressController.ts new file mode 100644 index 000000000..d78378330 --- /dev/null +++ b/src/controllers/pallets/PalletsStakingProgressController.ts @@ -0,0 +1,100 @@ +import { ApiPromise } from '@polkadot/api'; +import { RequestHandler } from 'express'; + +import { PalletsStakingProgressService } from '../../services'; +import AbstractController from '../AbstractController'; + +/** + * GET get progress on the general staking pallet system + * + * Paths: + * - (Optional) `number`: Block hash or height at which to query. If not provided, queries + * finalized head. + * + * Returns: + * - `at`: Block number and hash at which the call was made. + * - `activeEra`: `EraIndex` of the era being rewarded. + * - `forceEra`: Current status of era forcing. + * - `nextActiveEraEstimate`: **Upper bound estimate** of the block height at which the next + * active era will start. Not included in response when `forceEra.isForceNone`. + * - `nextSessionEstimate`: **Upper bound estimate** of the block height at which the next + * session will start. + * - `unappliedSlashes`: Array of upcoming `UnappliedSlash` indexed by era. Each `UnappliedSlash` + * contains: + * - `validator`: Stash account ID of the offending validator. + * - `own`: The amount the validator will be slashed. + * - `others`: Array of tuples of (accountId, amount) representing all the stashes of other + * slashed stakers and the amount they will be slashed. + * - `reporters`: Array of account IDs of the reporters of the offense. + * - `payout`: Amount of bounty payout to reporters. + * - `electionStatus`: Information about the off-chain election. Not included in response when + * `forceEra.isForceNone`. Response includes: + * - `status`: Era election status; either `Close: null` or `Open: `. A status of + * `Close` indicates that the submission window for solutions from off-chain Phragmen is not + * open. A status of `Open` indicates the submission window for off-chain Phragmen solutions + * has been open since BlockNumber. N.B. when the submission window is open, certain + * extrinsics are not allowed because they would mutate the state that the off-chain Phragmen + * calculation relies on for calculating results. + * - `toggleEstimate`: **Upper bound estimate** of the block height at which the `status` will + * switch. + * - `idealValidatorCount`: Upper bound of validator set size; considered the ideal size. Not + * included in response when `forceEra.isForceNone`. + * - `validatorSet`: Stash account IDs of the validators for the current session. Not included in + * response when `forceEra.isForceNone`. + * + * Note about 'active' vs. 'current' era: The _active_ era is the era currently being rewarded. + * That is, an elected validator set will be in place for an entire active era, as long as none + * are kicked out due to slashing. Elections take place at the end of each _current_ era, which + * is the latest planned era, and may not equal the active era. Normally, the current era index + * increments one session before the active era, in order to perform the election and queue the + * validator set for the next active era. For example: + * + * ``` + * Time: ---------> + * CurrentEra: 1 | 2 | + * ActiveEra: | 1 | 2 | + * SessionIdx: | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | + * Elections: ^ ^ + * Set Changes: ^ ^ + * ``` + * + * Substrate Reference: + * - Staking Pallet: https://crates.parity.io/pallet_staking/index.html + * - Session Pallet: https://crates.parity.io/pallet_session/index.html + * - `Forcing`: https://crates.parity.io/pallet_staking/enum.Forcing.html + * - `ElectionStatus`: https://crates.parity.io/pallet_staking/enum.ElectionStatus.html + */ +export default class PalletsStakingProgressController extends AbstractController< + PalletsStakingProgressService +> { + constructor(api: ApiPromise) { + super( + api, + 'pallets/staking/progress', + new PalletsStakingProgressService(api) + ); + this.initRoutes(); + } + + protected initRoutes(): void { + this.safeMountAsyncGetHandlers([['', this.getStakingPalletProgress]]); + } + + /** + * Get the progress of the staking pallet system. + * + * @param _req Express Request + * @param res Express Response + */ + private getStakingPalletProgress: RequestHandler = async ( + { query: { at } }, + res + ): Promise => { + const hash = await this.getHashFromAt(at); + + PalletsStakingProgressController.sanitizedSend( + res, + await this.service.derivePalletStakingProgress(hash) + ); + }; +} diff --git a/src/controllers/pallets/index.ts b/src/controllers/pallets/index.ts new file mode 100644 index 000000000..c057aa32d --- /dev/null +++ b/src/controllers/pallets/index.ts @@ -0,0 +1 @@ +export { default as v0PalletsStakingProgress } from './PalletsStakingProgressController'; diff --git a/src/services/index.ts b/src/services/index.ts index f09d28835..3b043f4c6 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,8 +1,6 @@ export * from './accounts'; // export * from './blocks'; -// export * from './pallets'; +export * from './pallets'; export * from './transaction'; export * from './runtime'; export * from './node'; - -// export * as v0 from './v0'; diff --git a/src/services/pallets/PalletsStakingProgressService.spec.ts b/src/services/pallets/PalletsStakingProgressService.spec.ts new file mode 100644 index 000000000..4ed3c744e --- /dev/null +++ b/src/services/pallets/PalletsStakingProgressService.spec.ts @@ -0,0 +1,72 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { InternalServerError } from 'http-errors'; + +import { sanitizeNumbers } from '../../sanitize/sanitizeNumbers'; +import { polkadotRegistry } from '../../test-helpers/registries'; +import { + activeEraAt, + blockHash789629, + erasStartSessionIndexAt, + mockApi, +} from '../test-helpers/mock'; +import * as palletsStakingProgress789629SResponse from '../test-helpers/responses/pallets/stakingProgress789629.json'; +import { PalletsStakingProgressService } from './PalletsStakingProgressService'; + +/** + * Mock PalletStakingProgressService instance. + */ +const palletStakingProgressService = new PalletsStakingProgressService(mockApi); + +describe('PalletStakingProgressService', () => { + describe('derivePalletStakingProgress', () => { + it('works when ApiPromise works (block 789629)', async () => { + expect( + sanitizeNumbers( + await palletStakingProgressService.derivePalletStakingProgress( + blockHash789629 + ) + ) + ).toStrictEqual(palletsStakingProgress789629SResponse); + }); + + it('throws when ErasStartSessionIndex.isNone', async () => { + (mockApi.query.staking.erasStartSessionIndex as any).at = () => + Promise.resolve().then(() => + polkadotRegistry.createType('Option', null) + ); + + await expect( + palletStakingProgressService.derivePalletStakingProgress( + blockHash789629 + ) + ).rejects.toStrictEqual( + new InternalServerError( + 'EraStartSessionIndex is None when Some was expected.' + ) + ); + + (mockApi.query.staking + .erasStartSessionIndex as any).at = erasStartSessionIndexAt; + }); + + it('throws when activeEra.isNone', async () => { + (mockApi.query.staking.activeEra as any).at = () => + Promise.resolve().then(() => + polkadotRegistry.createType('Option', null) + ); + + await expect( + palletStakingProgressService.derivePalletStakingProgress( + blockHash789629 + ) + ).rejects.toStrictEqual( + new InternalServerError( + 'ActiveEra is None when Some was expected.' + ) + ); + + (mockApi.query.staking.activeEra as any).at = activeEraAt; + }); + }); +}); diff --git a/src/services/pallets/PalletsStakingProgressService.ts b/src/services/pallets/PalletsStakingProgressService.ts new file mode 100644 index 000000000..b041ca918 --- /dev/null +++ b/src/services/pallets/PalletsStakingProgressService.ts @@ -0,0 +1,207 @@ +import { ApiPromise } from '@polkadot/api'; +import { BlockHash, EraIndex } from '@polkadot/types/interfaces'; +import * as BN from 'bn.js'; +import { InternalServerError } from 'http-errors'; +import { IPalletStakingProgress } from 'src/types/responses'; + +import { AbstractService } from '../AbstractService'; + +export class PalletsStakingProgressService extends AbstractService { + /** + * Fetch and derive generalized staking information at a given block. + * + * @param hash `BlockHash` to make call at + */ + async derivePalletStakingProgress( + hash: BlockHash + ): Promise { + const api = await this.ensureMeta(hash); + + const [ + validatorCount, + forceEra, + eraElectionStatus, + validators, + { number }, + ] = await Promise.all([ + api.query.staking.validatorCount.at(hash), + api.query.staking.forceEra.at(hash), + api.query.staking.eraElectionStatus.at(hash), + api.query.session.validators.at(hash), + api.rpc.chain.getHeader(hash), + ]); + + const { + eraLength, + eraProgress, + sessionLength, + sessionProgress, + activeEra, + } = await this.deriveSessionAndEraProgress(api, hash); + + const unappliedSlashesAtActiveEra = await api.query.staking.unappliedSlashes.at( + hash, + activeEra + ); + + const currentBlockNumber = number.toBn(); + + const nextSession = sessionLength + .sub(sessionProgress) + .add(currentBlockNumber); + + const baseResponse = { + at: { + hash: hash.toJSON(), + height: currentBlockNumber.toString(10), + }, + activeEra: activeEra.toString(10), + forceEra: forceEra.toJSON(), + nextSessionEstimate: nextSession.toString(10), + unappliedSlashes: unappliedSlashesAtActiveEra.map((slash) => + slash.toJSON() + ), + }; + + if (forceEra.isForceNone) { + // Most likely we are in a PoA network with no elections. Things + // like `ValidatorCount` and `Validators` are hardcoded from genesis + // to support a transition into NPoS, but are irrelevant here and would be + // confusing to include. Thus, we craft a response excluding those values. + return baseResponse; + } + + const nextActiveEra = forceEra.isForceAlways + ? nextSession // there is a new era every session + : eraLength.sub(eraProgress).add(currentBlockNumber); // the nextActiveEra is at the end of this era + + const electionLookAhead = await this.deriveElectionLookAhead(api, hash); + + const nextCurrentEra = nextActiveEra + .sub(currentBlockNumber) + .sub(sessionLength) + .gt(new BN(0)) + ? nextActiveEra.sub(sessionLength) // current era simply one session before active era + : nextActiveEra.add(eraLength).sub(sessionLength); // we are in the last session of an active era + + let toggle; + if (electionLookAhead.eq(new BN(0))) { + // no offchain solutions accepted + toggle = null; + } else if (eraElectionStatus.isClose) { + // election window is yet to open + toggle = nextCurrentEra.sub(electionLookAhead); + } else { + // election window closes at the end of the current era + toggle = nextCurrentEra; + } + + return { + ...baseResponse, + nextActiveEraEstimate: nextActiveEra.toString(10), + electionStatus: { + status: eraElectionStatus.toJSON(), + toggleEstimate: toggle?.toString(10) ?? null, + }, + idealValidatorCount: validatorCount.toString(10), + validatorSet: validators.map((accountId) => accountId.toString()), + }; + } + + /** + * Derive information on the progress of the current session and era. + * + * @param api ApiPromise with ensured metadata + * @param hash `BlockHash` to make call at + */ + private async deriveSessionAndEraProgress( + api: ApiPromise, + hash: BlockHash + ): Promise<{ + eraLength: BN; + eraProgress: BN; + sessionLength: BN; + sessionProgress: BN; + activeEra: EraIndex; + }> { + const [ + currentSlot, + epochIndex, + genesisSlot, + currentIndex, + activeEraOption, + ] = await Promise.all([ + api.query.babe.currentSlot.at(hash), + api.query.babe.epochIndex.at(hash), + api.query.babe.genesisSlot.at(hash), + api.query.session.currentIndex.at(hash), + api.query.staking.activeEra.at(hash), + ]); + + if (activeEraOption.isNone) { + // TODO refactor to newer error type + throw new InternalServerError( + 'ActiveEra is None when Some was expected.' + ); + } + const { index: activeEra } = activeEraOption.unwrap(); + + const activeEraStartSessionIndexOption = await api.query.staking.erasStartSessionIndex.at( + hash, + activeEra + ); + if (activeEraStartSessionIndexOption.isNone) { + throw new InternalServerError( + 'EraStartSessionIndex is None when Some was expected.' + ); + } + const activeEraStartSessionIndex = activeEraStartSessionIndexOption.unwrap(); + + const { epochDuration: sessionLength } = api.consts.babe; + const eraLength = api.consts.staking.sessionsPerEra.mul(sessionLength); + const epochStartSlot = epochIndex.mul(sessionLength).add(genesisSlot); + const sessionProgress = currentSlot.sub(epochStartSlot); + const eraProgress = currentIndex + .sub(activeEraStartSessionIndex) + .mul(sessionLength) + .add(sessionProgress); + + return { + eraLength, + eraProgress, + sessionLength, + sessionProgress, + activeEra, + }; + } + + /** + * Get electionLookAhead as a const if available. Otherwise derive + * `electionLookAhead` based on the `specName` & `epochDuration`. + * N.B. Values are hardcoded based on `specName`s polkadot, kusama, and westend. + * There are no guarantees that this will return the expected values for + * other `specName`s. + * + * @param api ApiPromise with ensured metadata + * @param hash `BlockHash` to make call at + */ + private async deriveElectionLookAhead( + api: ApiPromise, + hash: BlockHash + ): Promise { + if (api.consts.staking.electionLookahead) { + return api.consts.staking.electionLookahead; + } + + const { specName } = await api.rpc.state.getRuntimeVersion(hash); + const { epochDuration } = api.consts.babe; + + // TODO - create a configurable epochDivisor env for a more generic solution + const epochDurationDivisor = + specName.toString() === 'polkadot' + ? new BN(16) // polkadot electionLookAhead = epochDuration / 16 + : new BN(4); // kusama, westend, `substrate/bin/node` electionLookAhead = epochDuration / 4 + + return epochDuration.div(epochDurationDivisor); + } +} diff --git a/src/services/pallets/index.ts b/src/services/pallets/index.ts new file mode 100644 index 000000000..e7a3c6acb --- /dev/null +++ b/src/services/pallets/index.ts @@ -0,0 +1 @@ +export * from './PalletsStakingProgressService'; From fe92628a352417cd72fe1620e347f61cd0f3c6de Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 30 Aug 2020 21:35:21 -0700 Subject: [PATCH 12/20] add pallets/staking/progress --- src/controllers/pallets/PalletsStakingProgressController.ts | 6 +++--- src/controllers/pallets/index.ts | 2 +- src/main.ts | 6 +++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/controllers/pallets/PalletsStakingProgressController.ts b/src/controllers/pallets/PalletsStakingProgressController.ts index d78378330..6c3b57c01 100644 --- a/src/controllers/pallets/PalletsStakingProgressController.ts +++ b/src/controllers/pallets/PalletsStakingProgressController.ts @@ -70,14 +70,14 @@ export default class PalletsStakingProgressController extends AbstractController constructor(api: ApiPromise) { super( api, - 'pallets/staking/progress', + '/pallets/staking/progress', new PalletsStakingProgressService(api) ); this.initRoutes(); } protected initRoutes(): void { - this.safeMountAsyncGetHandlers([['', this.getStakingPalletProgress]]); + this.safeMountAsyncGetHandlers([['', this.getPalletStakingProgress]]); } /** @@ -86,7 +86,7 @@ export default class PalletsStakingProgressController extends AbstractController * @param _req Express Request * @param res Express Response */ - private getStakingPalletProgress: RequestHandler = async ( + private getPalletStakingProgress: RequestHandler = async ( { query: { at } }, res ): Promise => { diff --git a/src/controllers/pallets/index.ts b/src/controllers/pallets/index.ts index c057aa32d..bac9fbe88 100644 --- a/src/controllers/pallets/index.ts +++ b/src/controllers/pallets/index.ts @@ -1 +1 @@ -export { default as v0PalletsStakingProgress } from './PalletsStakingProgressController'; +export { default as palletsStakingProgress } from './PalletsStakingProgressController'; diff --git a/src/main.ts b/src/main.ts index c7bf91849..f9e6c4653 100644 --- a/src/main.ts +++ b/src/main.ts @@ -96,6 +96,9 @@ async function main() { const accountsStakingInfoController = new controllers.AccountsStakingInfo( api ); + const accountsVestingInfoController = new controllers.AccountsVestingInfo( + api + ); const nodeNetworkController = new controllers.NodeNetwork(api); const nodeVersionController = new controllers.NodeVersion(api); const nodeTransactionPoolController = new controllers.NodeTransactionPool( @@ -105,7 +108,7 @@ async function main() { const runtimeSpecController = new controllers.RuntimeSpec(api); const runtimeMetadataController = new controllers.RuntimeMetadata(api); const transactionDryRunController = new controllers.TransactionDryRun(api); - const accountsVestingInfoController = new controllers.AccountsVestingInfo( + const palletsStakingProgressController = new controllers.palletsStakingProgress( api ); @@ -124,6 +127,7 @@ async function main() { runtimeSpecController, runtimeMetadataController, transactionDryRunController, + palletsStakingProgressController, ...v0Controllers, ], postMiddleware: [ From aa7ba21e5efe3c1b5bcbb2fa3990b247e85d9b6c Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 30 Aug 2020 21:38:38 -0700 Subject: [PATCH 13/20] pallets/staking/progress --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 44f1e6109..0277227a9 100644 --- a/README.md +++ b/README.md @@ -92,9 +92,7 @@ a 32-byte hex string (`0x` followed by 64 hexadecimal digits) that denotes the b - [`/block/NUMBER` fetch block details at the block identified by 'NUMBER`.](/src/controllers/blocks/BlocksController.ts) -- [`/staking-info` fetch information on general staking progress at the latest finalized block.](src/controllers/pallets/PalletsStakingProgressController.ts) - -- [`/staking-info/NUMBER` fetch information on general staking progress at the block identified by 'NUMBER`.](src/controllers/pallets/PalletsStakingProgressController.ts) +- [`/pallets/staking/progress` fetch information on general staking progress.](src/controllers/pallets/PalletsStakingProgressController.ts) (Replaces `/staking-info`.) - [`/node/network` fetch information about the Substrate node's activity in the peer-to-peer network.](src/controllers/node/NodeNetworkController.ts) From debd74bb33acc1486c6642de9de35f6d7562f116 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 30 Aug 2020 21:51:05 -0700 Subject: [PATCH 14/20] correct controller import --- src/controllers/v0/v0accounts/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/v0/v0accounts/index.ts b/src/controllers/v0/v0accounts/index.ts index 8a6fbcfa7..7af601abd 100644 --- a/src/controllers/v0/v0accounts/index.ts +++ b/src/controllers/v0/v0accounts/index.ts @@ -1,3 +1,3 @@ export { default as v0AccountsBalanceInfo } from './AccountsBalanceInfoController'; -export { default as v0AccountsStakingInfo } from '../../accounts/AccountsStakingInfoController'; +export { default as v0AccountsStakingInfo } from './AccountsStakingInfoController'; export { default as v0AccountsVestingInfo } from './AccountsVestingInfoController'; From e70b3e394e5503e16f87a6a524614ced178d34d1 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 30 Aug 2020 21:56:37 -0700 Subject: [PATCH 15/20] export correction --- src/services/v0/v0accounts/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/v0/v0accounts/index.ts b/src/services/v0/v0accounts/index.ts index a215f56a5..2b08eda1a 100644 --- a/src/services/v0/v0accounts/index.ts +++ b/src/services/v0/v0accounts/index.ts @@ -1,3 +1,3 @@ -export * from '../../accounts/AccountsStakingInfoService'; +export * from './AccountsStakingInfoService'; export * from './AccountsBalanceInfoService'; export * from './AccountsVestingInfoService'; From cb931d05c6eb5fdcc8c7f86aae710f9c74393c2b Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 2 Sep 2020 17:22:42 -0700 Subject: [PATCH 16/20] fix typo --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 6be4de56a..5b82e8719 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,6 @@ a 32-byte hex string (`0x` followed by 64 hexadecimal digits) that denotes the b - [`/accounts/ADDRESS/staking-payouts` fetch staking payouts for `ADDRESS`.](/src/controllers/accounts/AccountsStakingPayoutsController.ts) - - [`/accounts/ADDRESS/balance-info` fetch balances info for `ADDRESS`.](src/controllers/accounts/AccountsBalanceInfoController.ts) (Replaces `/balance/ADDRESS`.) - [`/accounts/ADDRESS/vesting-info` vesting info for `ADDRESS`.](src/controllers/accounts/AccountsVestingInfoController.ts) (Replaces `/vesting/ADDRESS`.) From a58a96dadd0469d9a28015ab3c22ffd32735e522 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 2 Sep 2020 17:42:00 -0700 Subject: [PATCH 17/20] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b82e8719..15d919b95 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ a 32-byte hex string (`0x` followed by 64 hexadecimal digits) that denotes the b - [`/accounts/ADDRESS/staking-info` fetch the staking info for `ADDRESS`.](src/controllers/accounts/AccountsStakingInfoController.ts) (Replaces `/staking/ADDRESS`.) -- [`/blocks` fetch a block.](/src/controllers/blocks/BlocksController.ts) (Replaces `/block`.) +- [`/blocks/{head, BlockId}` fetch the finalized head or block identified by BlockId.](/src/controllers/blocks/BlocksController.ts) (Replaces `/block`.) - [`/pallets/staking/progress` fetch information on general staking progress.](src/controllers/pallets/PalletsStakingProgressController.ts) (Replaces `/staking-info`.) From d4f90971c09a0ecbce312969bdc3dfe7d56e2bab Mon Sep 17 00:00:00 2001 From: Zeke Mostov <32168567+emostov@users.noreply.github.com> Date: Fri, 4 Sep 2020 08:38:27 -0700 Subject: [PATCH 18/20] Apply suggestions from code review Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- src/controllers/pallets/PalletsStakingProgressController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/pallets/PalletsStakingProgressController.ts b/src/controllers/pallets/PalletsStakingProgressController.ts index 6c3b57c01..73a5fcf9b 100644 --- a/src/controllers/pallets/PalletsStakingProgressController.ts +++ b/src/controllers/pallets/PalletsStakingProgressController.ts @@ -5,7 +5,7 @@ import { PalletsStakingProgressService } from '../../services'; import AbstractController from '../AbstractController'; /** - * GET get progress on the general staking pallet system + * GET progress on the general Staking pallet system. * * Paths: * - (Optional) `number`: Block hash or height at which to query. If not provided, queries From 493f792995fdedcb5bd8708a14dc9b119023ddb5 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Fri, 4 Sep 2020 08:58:26 -0700 Subject: [PATCH 19/20] update docs --- src/controllers/accounts/AccountsVestingInfoController.ts | 2 -- src/controllers/pallets/PalletsStakingProgressController.ts | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/controllers/accounts/AccountsVestingInfoController.ts b/src/controllers/accounts/AccountsVestingInfoController.ts index 2975a3504..754c4ebe7 100644 --- a/src/controllers/accounts/AccountsVestingInfoController.ts +++ b/src/controllers/accounts/AccountsVestingInfoController.ts @@ -11,8 +11,6 @@ import AbstractController from '../AbstractController'; * * Paths: * - `address`: Address to query. - * - (Optional) `number`: Block hash or height at which to query. If not provided, queries - * finalized head. * * Query params: * - (Optional)`at`: Block at which to retrieve runtime version information at. Block diff --git a/src/controllers/pallets/PalletsStakingProgressController.ts b/src/controllers/pallets/PalletsStakingProgressController.ts index 6c3b57c01..fd2a6de6e 100644 --- a/src/controllers/pallets/PalletsStakingProgressController.ts +++ b/src/controllers/pallets/PalletsStakingProgressController.ts @@ -11,6 +11,10 @@ import AbstractController from '../AbstractController'; * - (Optional) `number`: Block hash or height at which to query. If not provided, queries * finalized head. * + * Query params: + * - (Optional)`at`: Block at which to retrieve runtime version information at. Block + * identifier, as the block height or block hash. Defaults to most recent block. + * * Returns: * - `at`: Block number and hash at which the call was made. * - `activeEra`: `EraIndex` of the era being rewarded. From 81b0179f31df08e69ece18ef91c2a91048703ecb Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Fri, 4 Sep 2020 09:01:21 -0700 Subject: [PATCH 20/20] Update openapi --- openapi/openapi-proposal.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi/openapi-proposal.yaml b/openapi/openapi-proposal.yaml index de46e71d7..9283cdb21 100755 --- a/openapi/openapi-proposal.yaml +++ b/openapi/openapi-proposal.yaml @@ -433,7 +433,7 @@ paths: get: tags: - staking - summary: Get a progress report on the chain's staking system. + summary: Get progress on the general Staking pallet system. description: Returns information on the progress of key components of the staking system and estimates of future points of interest. Replaces `/staking-info` from versions < v1.0.0.