Skip to content

Commit

Permalink
feat: inject controllers using metadata's definition of pallets (#1592)
Browse files Browse the repository at this point in the history
* adds naming to AbstractController and to controllers

* wip:injected controllers

* inject controllers using metadata

* inject controllers using metadata

* removed unused method

* injects controllers using pallets in metadata

* lint

* update requiredPallets abstract definition

* update injected flag to make injected controllers not default (no breaking changes)

* Update README.md

Co-authored-by: Dominique <dominique@imodworks.io>

* Update src/chains-config/index.ts

Co-authored-by: Dominique <dominique@imodworks.io>

* fix default options

* remove log

* update readme

* Update src/controllers/coretime/CoretimeChainController.ts

Co-authored-by: Dominique <dominique@imodworks.io>

* Update src/controllers/coretime/CoretimeGenericController.ts

Co-authored-by: Dominique <dominique@imodworks.io>

* linting

* rebase and fix tests

---------

Co-authored-by: Dominique <dominique@imodworks.io>
  • Loading branch information
filvecchiato and Imod7 authored Mar 3, 2025
1 parent e65ba2b commit 2aa6e6a
Show file tree
Hide file tree
Showing 59 changed files with 326 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ For more information on our configuration manager visit its readme [here](https:
- `SAS_EXPRESS_PORT`: port on which the server will be listening, defaults to `8080`.
- `SAS_EXPRESS_KEEP_ALIVE_TIMEOUT`: Set the `keepAliveTimeout` in express.
- `SAS_EXPRESS_MAX_BODY`: Set the size of request body payload, defaults to `100kb`
- `SAS_EXPRESS_INJECTED_CONTROLLERS`: When set (_e.g. SAS_EXPRESS_INJECTED_CONTROLLERS=true_), automatically detects the available pallets in the chain's metadata and injects the appropriate controllers. If set to `false`, it uses the controllers defined in the chains configuration files. Defaults to `false` when the flag is not defined. Note: This flag does not replace completely the need of the chains-config files since we are still retrieving the options from that file.

### Substrate node

Expand Down
1 change: 1 addition & 0 deletions src/SidecarConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export class SidecarConfig {
PORT: config.Get(MODULES.EXPRESS, CONFIG.PORT) as number,
KEEP_ALIVE_TIMEOUT: config.Get(MODULES.EXPRESS, CONFIG.KEEP_ALIVE_TIMEOUT) as number,
MAX_BODY: config.Get(MODULES.EXPRESS, CONFIG.MAX_BODY) as string,
INJECTED_CONTROLLERS: config.Get(MODULES.EXPRESS, CONFIG.INJECTED_CONTROLLERS) as boolean,
},
SUBSTRATE: {
URL: config.Get(MODULES.SUBSTRATE, CONFIG.URL) as string,
Expand Down
13 changes: 13 additions & 0 deletions src/Specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@ export class Specs {
type: 'string',
}),
);

this._specs.appendSpec(
MODULES.EXPRESS,
this._specs.getSpec(
CONFIG.INJECTED_CONTROLLERS,
'Use the controllers from the chain configuration or use controllers injected from pallets definitions',
{
default: 'false',
type: 'boolean',
regexp: /^true|false$/,
},
),
);
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/chains-config/astarControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export const astarControllers: ControllerConfig = {
'PalletsAssets',
'PalletsErrors',
'PalletsStorage',
'Paras',
'RuntimeCode',
'RuntimeMetadata',
'RuntimeSpec',
Expand Down
1 change: 0 additions & 1 deletion src/chains-config/bifrostControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export const bifrostControllers: ControllerConfig = {
'NodeTransactionPool',
'NodeVersion',
'PalletsStorage',
'Paras',
'RuntimeCode',
'RuntimeMetadata',
'RuntimeSpec',
Expand Down
1 change: 0 additions & 1 deletion src/chains-config/bifrostPolkadotControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export const bifrostPolkadotControllers: ControllerConfig = {
'NodeTransactionPool',
'NodeVersion',
'PalletsStorage',
'Paras',
'RuntimeCode',
'RuntimeMetadata',
'RuntimeSpec',
Expand Down
1 change: 0 additions & 1 deletion src/chains-config/calamariControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export const calamariControllers: ControllerConfig = {
'NodeVersion',
'PalletsStakingProgress',
'PalletsStorage',
'Paras',
'RuntimeCode',
'RuntimeMetadata',
'RuntimeSpec',
Expand Down
12 changes: 0 additions & 12 deletions src/chains-config/coretimeControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,11 @@ import { initLRUCache, QueryFeeDetailsCache } from './cache';
*/
export const coretimeControllers: ControllerConfig = {
controllers: [
'AccountsBalanceInfo',
'AccountsConvert',
'AccountsProxyInfo',
'Blocks',
'BlocksExtrinsics',
'BlocksTrace',
'BlocksRawExtrinsics',
'NodeNetwork',
'NodeVersion',
'PalletsConsts',
'PalletsErrors',
'PalletsEvents',
'PalletsNominationPools',
'PalletsOnGoingReferenda',
'PalletsStakingProgress',
'PalletsStakingValidators',
'PalletsStorage',
'RuntimeCode',
'RuntimeMetadata',
'RuntimeSpec',
Expand Down
39 changes: 38 additions & 1 deletion src/chains-config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import { ApiPromise } from '@polkadot/api';
import { ISidecarConfig } from 'src/types/sidecar-config';

import { controllers } from '../controllers';
import AbstractController from '../controllers/AbstractController';
Expand Down Expand Up @@ -49,7 +50,7 @@ import { shidenControllers } from './shidenControllers';
import { soraControllers } from './soraControllers';
import { westendControllers } from './westendControllers';

const specToControllerMap: { [x: string]: ControllerConfig } = {
export const specToControllerMap: { [x: string]: ControllerConfig } = {
westend: westendControllers,
polkadot: polkadotControllers,
polymesh: polymeshControllers,
Expand Down Expand Up @@ -117,3 +118,39 @@ function getControllersFromConfig(api: ApiPromise, config: ControllerConfig) {
return acc;
}, [] as AbstractController<AbstractService>[]);
}

/**
* Return an array of instantiated controller instances based off of a `specName`.
* @param pallets pallets available to define controllers
* @param api ApiPromise to inject into controllers
* @param specName specName of chain to get options
*/

export const getControllersByPallets = (pallets: string[], api: ApiPromise, specName: string) => {
const controllersSet: AbstractController<AbstractService>[] = [];
const config = specToControllerMap?.[specName]?.options || defaultControllers?.options;

Object.values(controllers).forEach((controller) => {
if (controller.canInjectByPallets(pallets)) {
controllersSet.push(new controller(api, config));
}
});

return controllersSet;
};

export const getControllers = (
api: ApiPromise,
config: ISidecarConfig,
specName: string,
): AbstractController<AbstractService>[] => {
if (config.EXPRESS.INJECTED_CONTROLLERS) {
return getControllersByPallets(
(api.registry.metadata.toJSON().pallets as unknown as Record<string, unknown>[]).map((p) => p.name as string),
api,
specName,
);
} else {
return getControllersForSpec(api, specName);
}
};
1 change: 0 additions & 1 deletion src/chains-config/karuraControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export const karuraControllers: ControllerConfig = {
'NodeTransactionPool',
'NodeVersion',
'PalletsStorage',
'Paras',
'RuntimeCode',
'RuntimeMetadata',
'RuntimeSpec',
Expand Down
2 changes: 0 additions & 2 deletions src/chains-config/mantaControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export const mantaControllers: ControllerConfig = {
'AccountsStakingInfo',
'AccountsStakingPayouts',
'AccountsValidate',
'AccountsVestingInfo',
'Blocks',
'BlocksExtrinsics',
'BlocksRawExtrinsics',
Expand All @@ -35,7 +34,6 @@ export const mantaControllers: ControllerConfig = {
'NodeVersion',
'PalletsStakingProgress',
'PalletsStorage',
'Paras',
'RuntimeCode',
'RuntimeMetadata',
'RuntimeSpec',
Expand Down
26 changes: 26 additions & 0 deletions src/controllers/AbstractController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,21 @@ type SidecarRequestHandler =
| RequestHandler<IParaIdParam>
| RequestHandler;

/*
* The required pallets for a controller can be a list of pallets in string, or a list of list of pallets, which represents an OR statement.
* Example:
* - [['pallet1', 'pallet2']] => Requires pallet1 AND pallet2
* - [['pallet1', 'pallet2'], ['pallet1', 'pallet3']] => Requires (pallet1 AND pallet2) OR (pallet1 AND pallet3)
*/
export type RequiredPallets = string[][];

/**
* Abstract base class for creating controller classes.
*/
export default abstract class AbstractController<T extends AbstractService> {
private _router: Router = express.Router();
static controllerName: string;
static requiredPallets: RequiredPallets;

constructor(
protected api: ApiPromise,
Expand Down Expand Up @@ -266,4 +276,20 @@ export default abstract class AbstractController<T extends AbstractService> {
static sanitizedSend<T>(res: Response<AnyJson>, body: T, options: ISanitizeOptions = {}): void {
res.send(sanitizeNumbers(body, options));
}

static canInjectByPallets(availablePallets: string[]): boolean {
if (!this.requiredPallets) {
return true;
}
if (this.requiredPallets.length === 1) {
if (this.requiredPallets[0].length === 0) {
return true;
}
return this.requiredPallets[0].every((pallet) => availablePallets.includes(pallet));
} else if (this.requiredPallets.length > 1) {
return this.requiredPallets.some((pallets) => pallets.every((p) => availablePallets.includes(p)));
}
// If requiredPallets is empty, then we can inject
return true;
}
}
4 changes: 3 additions & 1 deletion src/controllers/accounts/AccountsAssetsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,13 @@ import AbstractController from '../AbstractController';
*
*/
export default class AccountsAssetsController extends AbstractController<AccountsAssetsService> {
static controllerName = 'AccountsAssets';
static requiredPallets = [['Assets']];

constructor(api: ApiPromise) {
super(api, '/accounts/:address', new AccountsAssetsService(api));
this.initRoutes();
}

protected initRoutes(): void {
this.router.use(this.path, validateAddress);

Expand Down
2 changes: 2 additions & 0 deletions src/controllers/accounts/AccountsBalanceInfoController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ import AbstractController from '../AbstractController';
* - `BalanceLock`: https://crates.parity.io/pallet_balances/struct.BalanceLock.html
*/
export default class AccountsBalanceController extends AbstractController<AccountsBalanceInfoService> {
static controllerName = 'AccountsBalanceInfo';
static requiredPallets = [['Balances', 'System']];
constructor(api: ApiPromise) {
super(api, '/accounts/:address/balance-info', new AccountsBalanceInfoService(api));
this.initRoutes();
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/accounts/AccountsCompareController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { ICompareQueryParams } from '../../types/requests';
import AbstractController from '../AbstractController';

export default class AccountsCompareController extends AbstractController<AccountsCompareService> {
static controllerName = 'AccountsCompare';
static requiredPallets = [];
constructor(api: ApiPromise) {
super(api, '/accounts/compare', new AccountsCompareService(api));
this.initRoutes();
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/accounts/AccountsConvertController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { IAddressParam, IConvertQueryParams } from '../../types/requests';
import AbstractController from '../AbstractController';

export default class AccountsConvertController extends AbstractController<AccountsConvertService> {
static controllerName = 'AccountsConvert';
static requiredPallets = [];
constructor(api: ApiPromise) {
super(api, '/accounts/:address/convert', new AccountsConvertService(api));
this.initRoutes();
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/accounts/AccountsPoolAssetsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ import AbstractController from '../AbstractController';
*
*/
export default class AccountsPoolAssetsController extends AbstractController<AccountsPoolAssetsService> {
static controllerName = 'AccountsPoolAssets';
static requiredPallets = [['PoolAssets', 'Assets']];
constructor(api: ApiPromise) {
super(api, '/accounts/:address', new AccountsPoolAssetsService(api));
this.initRoutes();
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/accounts/AccountsProxyInfoController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { AccountsProxyInfoService } from '../../services';
import AbstractController from '../AbstractController';

export default class AccountsProxyInfoController extends AbstractController<AccountsProxyInfoService> {
static controllerName = 'AccountsProxyInfo';
static requiredPallets = [['Proxy']];
constructor(api: ApiPromise) {
super(api, '/accounts/:address/proxy-info', new AccountsProxyInfoService(api));
this.initRoutes();
Expand Down
7 changes: 7 additions & 0 deletions src/controllers/accounts/AccountsStakingInfoController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ import AbstractController from '../AbstractController';
* - `StakingLedger`: https://crates.parity.io/pallet_staking/struct.StakingLedger.html
*/
export default class AccountsStakingInfoController extends AbstractController<AccountsStakingInfoService> {
static controllerName = 'AccountsStakingInfo';
static requiredPallets = [
['Staking', 'System'],
['ParachainStaking', 'ParachainSystem'],
['ParachainStaking', 'System'],
['Staking', 'ParachainSystem'],
];
constructor(api: ApiPromise) {
super(api, '/accounts/:address/staking-info', new AccountsStakingInfoService(api));
this.initRoutes();
Expand Down
7 changes: 7 additions & 0 deletions src/controllers/accounts/AccountsStakingPayoutsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ import AbstractController from '../AbstractController';
*
*/
export default class AccountsStakingPayoutsController extends AbstractController<AccountsStakingPayoutsService> {
static controllerName = 'AccountsStakingPayouts';
static requiredPallets = [
['Staking', 'System'],
['ParachainStaking', 'ParachainSystem'],
['ParachainStaking', 'System'],
['Staking', 'ParachainSystem'],
];
constructor(api: ApiPromise) {
super(api, '/accounts/:address/staking-payouts', new AccountsStakingPayoutsService(api));
this.initRoutes();
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/accounts/AccountsValidateController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { AccountsValidateService } from '../../services/accounts';
import AbstractController from '../AbstractController';

export default class ValidateAddressController extends AbstractController<AccountsValidateService> {
static controllerName = 'AccountsValidate';
static requiredPallets = [];
constructor(api: ApiPromise) {
super(api, '/accounts/:address/validate', new AccountsValidateService(api));
this.initRoutes();
Expand Down
3 changes: 3 additions & 0 deletions src/controllers/accounts/AccountsVestingInfoController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ import AbstractController from '../AbstractController';
* - `VestingInfo`: https://crates.parity.io/pallet_vesting/struct.VestingInfo.html
*/
export default class AccountsVestingInfoController extends AbstractController<AccountsVestingInfoService> {
static controllerName = 'AccountsVestingInfo';
static requiredPallets = [['Vesting'], ['CalamariVesting']];

constructor(api: ApiPromise) {
super(api, '/accounts/:address/vesting-info', new AccountsVestingInfoService(api));
this.initRoutes();
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/blocks/BlocksController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ import AbstractController from '../AbstractController';
*/
export default class BlocksController extends AbstractController<BlocksService> {
private blockStore: LRUCache<string, IBlock>;
static controllerName = 'Blocks';
static requiredPallets = [['System', 'Session']];
constructor(
api: ApiPromise,
private readonly options: ControllerOptions,
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/blocks/BlocksExtrinsicsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import AbstractController from '../AbstractController';

export default class BlocksExtrinsicsController extends AbstractController<BlocksService> {
private blockStore: LRUCache<string, IBlock>;
static controllerName = 'BlocksExtrinsics';
static requiredPallets = [['System', 'Session']];
constructor(api: ApiPromise, options: ControllerOptions) {
super(
api,
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/blocks/BlocksRawExtrinsicsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { INumberParam, IRequestHandlerWithMetrics } from '../../types/requests';
import AbstractController from '../AbstractController';

export default class BlocksRawExtrinsicsController extends AbstractController<BlocksService> {
static controllerName = 'BlocksRawExtrinsics';
static requiredPallets = [];
constructor(api: ApiPromise, options: ControllerOptions) {
super(
api,
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/blocks/BlocksTraceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import AbstractController from '../AbstractController';
import BlocksController from './BlocksController';

export default class BlocksTraceController extends AbstractController<BlocksTraceService> {
static controllerName = 'BlocksTrace';
static requiredPallets = [];
constructor(api: ApiPromise) {
super(api, '/experimental/blocks', new BlocksTraceService(api));
this.initRoutes();
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/contracts/ContractsInkController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { IBodyContractMetadata, IContractQueryParam, IPostRequestHandler } from
import AbstractController from '../AbstractController';

export default class ContractsInkController extends AbstractController<ContractsInkService> {
static controllerName = 'ContractsInk';
static requiredPallets = [];
constructor(api: ApiPromise) {
super(api, '/contracts/ink/:address', new ContractsInkService(api));
this.initRoutes();
Expand Down
Loading

0 comments on commit 2aa6e6a

Please sign in to comment.