Skip to main content
Module

x/polkadot/rpc-provider/mock/index.ts

Package publishing for deno.land/x/polkadot
Go to Latest
File

/* eslint-disable camelcase */
import type { Header } from 'https://deno.land/x/polkadot@0.2.40/types/interfaces/index.ts';import type { Codec, Registry } from 'https://deno.land/x/polkadot@0.2.40/types/types/index.ts';import type { ProviderInterface, ProviderInterfaceEmitCb, ProviderInterfaceEmitted } from '../types.ts';import type { MockStateDb, MockStateSubscriptionCallback, MockStateSubscriptions } from './types.ts';
import { EventEmitter } from 'https://esm.sh/eventemitter3@5.0.1';
import { createTestKeyring } from 'https://deno.land/x/polkadot@0.2.40/keyring/testing.ts';import { decorateStorage, Metadata } from 'https://deno.land/x/polkadot@0.2.40/types/mod.ts';import jsonrpc from 'https://deno.land/x/polkadot@0.2.40/types/interfaces/jsonrpc.ts';import rpcHeader from 'https://deno.land/x/polkadot@0.2.40/types-support/json/Header.004.json' assert { type: 'json' };import rpcSignedBlock from 'https://deno.land/x/polkadot@0.2.40/types-support/json/SignedBlock.004.immortal.json' assert { type: 'json' };import rpcMetadata from 'https://deno.land/x/polkadot@0.2.40/types-support/metadata/static-substrate.ts';import { BN, bnToU8a, logger, u8aToHex } from 'https://deno.land/x/polkadot@0.2.40/util/mod.ts';import { randomAsU8a } from 'https://deno.land/x/polkadot@0.2.40/util-crypto/mod.ts';
const INTERVAL = 1000;const SUBSCRIPTIONS: string[] = Array.prototype.concat.apply( [], Object.values(jsonrpc).map((section): string[] => Object .values(section) .filter(({ isSubscription }) => isSubscription) .map(({ jsonrpc }) => jsonrpc) .concat('chain_subscribeNewHead') )) as string[];
const keyring = createTestKeyring({ type: 'ed25519' });const l = logger('api-mock');
/** * A mock provider mainly used for testing. * @return {ProviderInterface} The mock provider * @internal */export class MockProvider implements ProviderInterface { private db: MockStateDb = {};
private emitter = new EventEmitter();
private intervalId?: ReturnType<typeof setInterval> | null;
public isUpdating = true;
private registry: Registry;
private prevNumber = new BN(-1);
private requests: Record<string, (...params: any[]) => unknown> = { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access chain_getBlock: () => this.registry.createType('SignedBlock', rpcSignedBlock.result).toJSON(), chain_getBlockHash: () => '0x1234000000000000000000000000000000000000000000000000000000000000', // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access chain_getFinalizedHead: () => this.registry.createType('Header', rpcHeader.result).hash, // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access chain_getHeader: () => this.registry.createType('Header', rpcHeader.result).toJSON(), rpc_methods: () => this.registry.createType('RpcMethods').toJSON(), state_getKeys: () => [], state_getKeysPaged: () => [], state_getMetadata: () => rpcMetadata, state_getRuntimeVersion: () => this.registry.createType('RuntimeVersion').toHex(), state_getStorage: (storage: MockStateDb, [key]: string[]) => u8aToHex(storage[key]), system_chain: () => 'mockChain', system_health: () => ({}), system_name: () => 'mockClient', system_properties: () => ({ ss58Format: 42 }), system_upgradedToTripleRefCount: () => this.registry.createType('bool', true), system_version: () => '9.8.7', // eslint-disable-next-line @typescript-eslint/no-unsafe-return, sort-keys dev_echo: (_, params: any) => params };
public subscriptions: MockStateSubscriptions = SUBSCRIPTIONS.reduce((subs, name): MockStateSubscriptions => { subs[name] = { callbacks: {}, lastValue: null };
return subs; }, ({} as MockStateSubscriptions));
private subscriptionId = 0;
private subscriptionMap: Record<number, string> = {};
constructor (registry: Registry) { this.registry = registry;
this.init(); }
public get hasSubscriptions (): boolean { return true; }
public clone (): MockProvider { throw new Error('Unimplemented'); }
public async connect (): Promise<void> { // noop }
// eslint-disable-next-line @typescript-eslint/require-await public async disconnect (): Promise<void> { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } }
public get isClonable (): boolean { return false; }
public get isConnected (): boolean { return true; }
public on (type: ProviderInterfaceEmitted, sub: ProviderInterfaceEmitCb): () => void { this.emitter.on(type, sub);
return (): void => { this.emitter.removeListener(type, sub); }; }
// eslint-disable-next-line @typescript-eslint/require-await public async send <T = any> (method: string, params: unknown[]): Promise<T> { l.debug(() => ['send', method, params]);
if (!this.requests[method]) { throw new Error(`provider.send: Invalid method '${method}'`); }
return this.requests[method](this.db, params) as T; }
// eslint-disable-next-line @typescript-eslint/require-await public async subscribe (_type: string, method: string, ...params: unknown[]): Promise<number> { l.debug(() => ['subscribe', method, params]);
if (!this.subscriptions[method]) { throw new Error(`provider.subscribe: Invalid method '${method}'`); }
const callback = params.pop() as MockStateSubscriptionCallback; const id = ++this.subscriptionId;
this.subscriptions[method].callbacks[id] = callback; this.subscriptionMap[id] = method;
if (this.subscriptions[method].lastValue !== null) { callback(null, this.subscriptions[method].lastValue); }
return id; }
// eslint-disable-next-line @typescript-eslint/require-await public async unsubscribe (_type: string, _method: string, id: number): Promise<boolean> { const sub = this.subscriptionMap[id];
l.debug(() => ['unsubscribe', id, sub]);
if (!sub) { throw new Error(`Unable to find subscription for ${id}`); }
delete this.subscriptionMap[id]; delete this.subscriptions[sub].callbacks[id];
return true; }
private init (): void { const emitEvents: ProviderInterfaceEmitted[] = ['connected', 'disconnected']; let emitIndex = 0; let newHead = this.makeBlockHeader(); let counter = -1;
const metadata = new Metadata(this.registry, rpcMetadata);
this.registry.setMetadata(metadata);
const query = decorateStorage(this.registry, metadata.asLatest, metadata.version);
// Do something every 1 seconds this.intervalId = setInterval((): void => { if (!this.isUpdating) { return; }
// create a new header (next block) newHead = this.makeBlockHeader();
// increment the balances and nonce for each account keyring.getPairs().forEach(({ publicKey }, index): void => { this.setStateBn(query.system.account(publicKey), newHead.number.toBn().addn(index)); });
// set the timestamp for the current block this.setStateBn(query.timestamp.now(), Math.floor(Date.now() / 1000)); this.updateSubs('chain_subscribeNewHead', newHead);
// We emit connected/disconnected at intervals if (++counter % 2 === 1) { if (++emitIndex === emitEvents.length) { emitIndex = 0; }
this.emitter.emit(emitEvents[emitIndex]); } }, INTERVAL); }
private makeBlockHeader (): Header { const blockNumber = this.prevNumber.addn(1); const header = this.registry.createType('Header', { digest: { logs: [] }, extrinsicsRoot: randomAsU8a(), number: blockNumber, parentHash: blockNumber.isZero() ? new Uint8Array(32) : bnToU8a(this.prevNumber, { bitLength: 256, isLe: false }), stateRoot: bnToU8a(blockNumber, { bitLength: 256, isLe: false }) });
this.prevNumber = blockNumber;
return header as unknown as Header; }
private setStateBn (key: Uint8Array, value: BN | number): void { this.db[u8aToHex(key)] = bnToU8a(value, { bitLength: 64, isLe: true }); }
private updateSubs (method: string, value: Codec): void { this.subscriptions[method].lastValue = value;
Object .values(this.subscriptions[method].callbacks) .forEach((cb): void => { try { cb(null, value.toJSON()); } catch (error) { l.error(`Error on '${method}' subscription`, error); } }); }}