Rebrand: polkadot → pezkuwi, substrate → bizinikiwi, kusama → dicle

This commit is contained in:
Kurdistan Tech Ministry committed 2026-01-07 02:29:40 +03:00
commit d5f038faea
1383 files changed
+1088018

No files matched your search

+259
View File
@@ -0,0 +1,259 @@
// Copyright 2017-2025 @pezkuwi/rpc-provider authors & contributors
// SPDX-License-Identifier: Apache-2.0
/* eslint-disable camelcase */
import type { Header } from '@pezkuwi/types/interfaces';
import type { Codec, Registry } from '@pezkuwi/types/types';
import type { ProviderInterface, ProviderInterfaceEmitCb, ProviderInterfaceEmitted } from '../types.js';
import type { MockStateDb, MockStateSubscriptionCallback, MockStateSubscriptions } from './types.js';
import { EventEmitter } from 'eventemitter3';
import { createTestKeyring } from '@pezkuwi/keyring/testing';
import { decorateStorage, Metadata } from '@pezkuwi/types';
import jsonrpc from '@pezkuwi/types/interfaces/jsonrpc';
import rpcHeader from '@pezkuwi/types-support/json/Header.004.json' assert { type: 'json' };
import rpcSignedBlock from '@pezkuwi/types-support/json/SignedBlock.004.immortal.json' assert { type: 'json' };
import rpcMetadata from '@pezkuwi/types-support/metadata/static-bizinikiwi';
import { BN, bnToU8a, logger, u8aToHex } from '@pezkuwi/util';
import { randomAsU8a } from '@pezkuwi/util-crypto';
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);
}
});
}
}
@@ -0,0 +1,35 @@
// Copyright 2017-2025 @pezkuwi/rpc-provider authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Mock } from './types.js';
import nock from 'nock';
interface Request {
code?: number;
method: string;
reply?: Record<string, unknown>;
}
interface HttpMock extends Mock {
post: (uri: string) => {
reply: (code: number, handler: (uri: string, body: { id: string }) => unknown) => HttpMock
}
}
export const TEST_HTTP_URL = 'http://localhost:9944';
export function mockHttp (requests: Request[]): Mock {
nock.cleanAll();
return requests.reduce((scope: HttpMock, request: Request) =>
scope
.post('/')
.reply(request.code || 200, (_uri: string, body: { id: string }) => {
scope.body = scope.body || {};
scope.body[request.method] = body;
return Object.assign({ id: body.id, jsonrpc: '2.0' }, request.reply || {}) as unknown;
}),
nock(TEST_HTTP_URL) as unknown as HttpMock);
}
+92
View File
@@ -0,0 +1,92 @@
// Copyright 2017-2025 @pezkuwi/rpc-provider authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { Server, WebSocket } from 'mock-socket';
import { stringify } from '@pezkuwi/util';
interface Scope {
body: Record<string, Record<string, unknown>>;
requests: number;
server: Server;
done: any;
}
interface ErrorDef {
id: number;
error: {
code: number;
message: string;
};
}
interface ReplyDef {
id: number;
reply: {
result: unknown;
};
}
interface RpcBase {
id: number;
jsonrpc: '2.0';
}
type RpcError = RpcBase & ErrorDef;
type RpcReply = RpcBase & { result: unknown };
export type Request = { method: string } & (ErrorDef | ReplyDef);
global.WebSocket = WebSocket as typeof global.WebSocket;
export const TEST_WS_URL = 'ws://localhost:9955';
// should be JSONRPC def return
function createError ({ error: { code, message }, id }: ErrorDef): RpcError {
return {
error: {
code,
message
},
id,
jsonrpc: '2.0'
};
}
// should be JSONRPC def return
function createReply ({ id, reply: { result } }: ReplyDef): RpcReply {
return {
id,
jsonrpc: '2.0',
result
};
}
// scope definition returned
export function mockWs (requests: Request[], wsUrl: string = TEST_WS_URL): Scope {
const server = new Server(wsUrl);
let requestCount = 0;
const scope: Scope = {
body: {},
done: () => new Promise<void>((resolve) => server.stop(resolve)),
requests: 0,
server
};
server.on('connection', (socket): void => {
socket.on('message', (body): void => {
const request = requests[requestCount];
const response = (request as ErrorDef).error
? createError(request as ErrorDef)
: createReply(request as ReplyDef);
scope.body[request.method] = body as unknown as Record<string, unknown>;
requestCount++;
socket.send(stringify(response));
});
});
return scope;
}
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2017-2025 @pezkuwi/rpc-provider authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
import type { ProviderInterfaceEmitted } from '../types.js';
import { TypeRegistry } from '@pezkuwi/types/create';
import { MockProvider } from './index.js';
describe('on', (): void => {
const registry = new TypeRegistry();
let mock: MockProvider;
beforeEach((): void => {
mock = new MockProvider(registry);
});
afterEach(async () => {
await mock.disconnect();
});
// eslint-disable-next-line jest/expect-expect
it('emits both connected and disconnected events', async (): Promise<void> => {
const events: Record<string, boolean> = { connected: false, disconnected: false };
await new Promise<boolean>((resolve) => {
const handler = (type: ProviderInterfaceEmitted): void => {
mock.on(type, (): void => {
events[type] = true;
if (Object.values(events).filter((value): boolean => value).length === 2) {
resolve(true);
}
});
};
handler('connected');
handler('disconnected');
});
});
});
@@ -0,0 +1,38 @@
// Copyright 2017-2025 @pezkuwi/rpc-provider authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
import { TypeRegistry } from '@pezkuwi/types/create';
import { MockProvider } from './index.js';
describe('send', (): void => {
const registry = new TypeRegistry();
let mock: MockProvider;
beforeEach((): void => {
mock = new MockProvider(registry);
});
afterEach(async () => {
await mock.disconnect();
});
it('fails on non-supported methods', (): Promise<any> => {
return mock
.send('something_invalid', [])
.catch((error): void => {
// eslint-disable-next-line jest/no-conditional-expect
expect((error as Error).message).toMatch(/Invalid method/);
});
});
it('returns values for mocked requests', (): Promise<void> => {
return mock
.send('system_name', [])
.then((result): void => {
expect(result).toBe('mockClient');
});
});
});
@@ -0,0 +1,81 @@
// Copyright 2017-2025 @pezkuwi/rpc-provider authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
import { TypeRegistry } from '@pezkuwi/types/create';
import { MockProvider } from './index.js';
describe('subscribe', (): void => {
const registry = new TypeRegistry();
let mock: MockProvider;
beforeEach((): void => {
mock = new MockProvider(registry);
});
afterEach(async () => {
await mock.disconnect();
});
it('fails on unknown methods', async (): Promise<void> => {
await mock
.subscribe('test', 'test_notFound')
.catch((error): void => {
// eslint-disable-next-line jest/no-conditional-expect
expect((error as Error).message).toMatch(/Invalid method 'test_notFound'/);
});
});
it('returns a subscription id', async (): Promise<void> => {
await mock
.subscribe('chain_newHead', 'chain_subscribeNewHead', (): void => undefined)
.then((id): void => {
expect(id).toEqual(1);
});
});
it('calls back with the last known value', async (): Promise<void> => {
mock.isUpdating = false;
mock.subscriptions.chain_subscribeNewHead.lastValue = 'testValue';
await new Promise<boolean>((resolve) => {
mock.subscribe('chain_newHead', 'chain_subscribeNewHead', (_: any, value: string): void => {
// eslint-disable-next-line jest/no-conditional-expect
expect(value).toEqual('testValue');
resolve(true);
}).catch(console.error);
});
});
// eslint-disable-next-line jest/expect-expect
it('calls back with new headers', async (): Promise<void> => {
await new Promise<boolean>((resolve) => {
mock.subscribe('chain_newHead', 'chain_subscribeNewHead', (_: any, header: { number: number }): void => {
if (header.number === 4) {
resolve(true);
}
}).catch(console.error);
});
});
// eslint-disable-next-line jest/expect-expect
it('handles errors within callbacks gracefully', async (): Promise<void> => {
let hasThrown = false;
await new Promise<boolean>((resolve) => {
mock.subscribe('chain_newHead', 'chain_subscribeNewHead', (_: any, header: { number: number }): void => {
if (!hasThrown) {
hasThrown = true;
throw new Error('testing');
}
if (header.number === 3) {
resolve(true);
}
}).catch(console.error);
});
});
});
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2017-2025 @pezkuwi/rpc-provider authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Server } from 'mock-socket';
export type Global = typeof globalThis & {
WebSocket: typeof WebSocket;
fetch: any;
}
export interface Mock {
body: Record<string, Record<string, unknown>>;
requests: number;
server: Server;
done: () => Promise<void>;
}
export type MockStateSubscriptionCallback = (error: Error | null, value: any) => void;
export interface MockStateSubscription {
callbacks: Record<number, MockStateSubscriptionCallback>;
lastValue: any;
}
export interface MockStateSubscriptions {
// known
chain_subscribeNewHead: MockStateSubscription;
state_subscribeStorage: MockStateSubscription;
// others
[key: string]: MockStateSubscription;
}
export type MockStateDb = Record<string, Uint8Array>;
export type MockStateRequests = Record<string, (db: MockStateDb, params: any[]) => string>;
@@ -0,0 +1,57 @@
// Copyright 2017-2025 @pezkuwi/rpc-provider authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
import { TypeRegistry } from '@pezkuwi/types/create';
import { MockProvider } from './index.js';
describe('unsubscribe', (): void => {
const registry = new TypeRegistry();
let mock: MockProvider;
let id: number;
beforeEach((): Promise<void> => {
mock = new MockProvider(registry);
return mock
.subscribe('chain_newHead', 'chain_subscribeNewHead', (): void => undefined)
.then((_id): void => {
id = _id;
});
});
afterEach(async () => {
await mock.disconnect();
});
it('fails on unknown ids', async (): Promise<void> => {
await mock
.unsubscribe('chain_newHead', 'chain_subscribeNewHead', 5)
.catch((error): boolean => {
// eslint-disable-next-line jest/no-conditional-expect
expect((error as Error).message).toMatch(/Unable to find/);
return false;
});
});
// eslint-disable-next-line jest/expect-expect
it('unsubscribes successfully', async (): Promise<void> => {
await mock.unsubscribe('chain_newHead', 'chain_subscribeNewHead', id);
});
it('fails on double unsubscribe', async (): Promise<void> => {
await mock.unsubscribe('chain_newHead', 'chain_subscribeNewHead', id)
.then((): Promise<boolean> =>
mock.unsubscribe('chain_newHead', 'chain_subscribeNewHead', id)
)
.catch((error): boolean => {
// eslint-disable-next-line jest/no-conditional-expect
expect((error as Error).message).toMatch(/Unable to find/);
return false;
});
});
});