Skip to content

Commit

Permalink
Data store tests pt3.
Browse files Browse the repository at this point in the history
  • Loading branch information
kukabi committed Sep 13, 2023
1 parent 75941ad commit a59ac75
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 24 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testTimeout: 15_000,
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"lint": "eslint ./",
"build": "webpack --config ./src/client/webpack.prod.js",
"dev": "webpack serve --config ./src/client/webpack.dev.js",
"test": "jest --detectOpenHandles",
"test": "jest --detectOpenHandles --forceExit",
"start": "node ./dist/server/server.js",
"deploy": "gh-pages -d ./dist/client",
"init-jest": "jest init"
Expand Down
60 changes: 42 additions & 18 deletions src/client/data/data-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { getSS58Address } from '../util/crypto-util';
*/
class DataStore {
private network!: Network;
private substrateClient!: ApiPromise;
private substrateClient?: ApiPromise;
private networkStatusClient!: RPCSubscriptionService<NetworkStatusUpdate>;
private activeValidatorListClient!: RPCSubscriptionService<ValidatorListUpdate>;
private readonly eventBus = EventBus.getInstance();
Expand Down Expand Up @@ -132,13 +132,12 @@ class DataStore {
* Establishes the Substrate WS-RPC connection.
*/
async connectSubstrateRPC() {
this.substrateClient = new ApiPromise({
provider: new WsProvider(this.network.rpcURL),
});
try {
// connection timeout handling
await setAsyncTimeout(async (done) => {
await this.substrateClient.isReady;
this.substrateClient = await ApiPromise.create({
provider: new WsProvider(this.network.rpcURL),
});
done(0);
}, Constants.CONNECTION_TIMEOUT_MS);
this.eventBus.dispatch<string>(ChainvizEvent.SUBSTRATE_API_READY);
Expand Down Expand Up @@ -287,14 +286,17 @@ class DataStore {
* @returns list of blocks
*/
async getInitialBlocks() {
if (!this.substrateClient) {
return;
}
const finalizedBlocks: Block[] = [];
// get finalized blocks
const finalizedBlockHash = await this.substrateClient.rpc.chain.getFinalizedHead();
let finalizedBlock = await this.getBlockByHash(finalizedBlockHash);
if (finalizedBlock) {
finalizedBlock.isFinalized = true;
finalizedBlocks.push(finalizedBlock);
for (let i = 0; i < Constants.INITIAL_BLOCK_COUNT; i++) {
for (let i = 0; i < Constants.INITIAL_FINALIZED_BLOCK_COUNT - 1; i++) {
finalizedBlock = await this.getBlockByHash(finalizedBlock.block.header.parentHash);
if (finalizedBlock) {
finalizedBlock.isFinalized = true;
Expand All @@ -308,16 +310,15 @@ class DataStore {
}
// get unfinalized blocks
const unFinalizedBlocks: Block[] = [];
let nonFinalizedHeader = await this.substrateClient.rpc.chain.getHeader();
let unFinalizedHeader = await this.substrateClient.rpc.chain.getHeader();
while (
nonFinalizedHeader.number.toNumber() !=
finalizedBlocks[0].block.header.number.toNumber()
unFinalizedHeader.number.toNumber() != finalizedBlocks[0].block.header.number.toNumber()
) {
const nonFinalizedBlock = await this.getBlockByHash(nonFinalizedHeader.hash);
if (nonFinalizedBlock) {
unFinalizedBlocks.push(nonFinalizedBlock);
nonFinalizedHeader = await this.substrateClient.rpc.chain.getHeader(
nonFinalizedHeader.parentHash,
const unFinalizedBlock = await this.getBlockByHash(unFinalizedHeader.hash);
if (unFinalizedBlock) {
unFinalizedBlocks.push(unFinalizedBlock);
unFinalizedHeader = await this.substrateClient.rpc.chain.getHeader(
unFinalizedHeader.parentHash,
);
} else {
break;
Expand All @@ -333,6 +334,9 @@ class DataStore {
* @returns parachain/thread ids
*/
private async getParaIds(): Promise<number[]> {
if (!this.substrateClient) {
return [];
}
return (await this.substrateClient.query.paras.parachains()).toJSON() as number[];
}

Expand All @@ -357,10 +361,10 @@ class DataStore {
* @returns unsubscribe promise
*/
subscribeToNewBlocks() {
if (this.newBlockSubscription) {
if (!this.substrateClient || this.newBlockSubscription) {
return;
}
this.newBlockSubscription = this.substrateClient.rpc.chain.subscribeNewHeads(
this.newBlockSubscription = this.substrateClient!.rpc.chain.subscribeNewHeads(
async (header) => {
this.onNewBlock(header);
},
Expand All @@ -373,10 +377,10 @@ class DataStore {
* @returns unsubscribe promise
*/
subscribeToFinalizedBlocks() {
if (this.finalizedHeaderSubscription) {
if (!this.substrateClient || this.finalizedHeaderSubscription) {
return;
}
this.finalizedHeaderSubscription = this.substrateClient.rpc.chain.subscribeFinalizedHeads(
this.finalizedHeaderSubscription = this.substrateClient!.rpc.chain.subscribeFinalizedHeads(
async (header) => {
this.onFinalizedBlock(header);
},
Expand Down Expand Up @@ -534,6 +538,9 @@ class DataStore {
* @returns promise for the block with the actual Substrate block, timestamp, events, extrinsics, etc.
*/
async getBlockByHash(hash: BlockHash): Promise<Block | undefined> {
if (!this.substrateClient) {
return undefined;
}
try {
const extendedHeader = await this.substrateClient.derive.chain.getHeader(hash);
const substrateBlock = (await this.substrateClient.rpc.chain.getBlock(hash)).block;
Expand Down Expand Up @@ -569,10 +576,26 @@ class DataStore {
* @returns promise for the block with the actual Substrate block, timestamp, events, extrinsics, etc.
*/
async getBlockByNumber(number: number): Promise<Block | undefined> {
if (!this.substrateClient) {
return undefined;
}
const hash = await this.substrateClient.rpc.chain.getBlockHash(number);
return this.getBlockByHash(hash);
}

/**
* Fetches a block hash from the Substrate RPC node by block number.
*
* @param hash block number
* @returns promise for the block hash
*/
async getBlockHash(number: number): Promise<BlockHash | undefined> {
if (!this.substrateClient) {
return undefined;
}
return await this.substrateClient.rpc.chain.getBlockHash(number);
}

/**
* Fetches the list of recent XCM transfers from the Polkaholic API,
* and populates the data array.
Expand Down Expand Up @@ -649,6 +672,7 @@ class DataStore {
if (this.substrateClient) {
try {
await this.substrateClient.disconnect();
this.substrateClient = undefined;
} catch (error) {
console.error('Error while disconnecting Substrate client:', error);
}
Expand Down
2 changes: 1 addition & 1 deletion src/client/util/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export abstract class Constants {
static readonly VALIDATOR_INSERT_DELAY_MS = 1000;
static readonly CONTENT_FADE_ANIM_DURATION_MS = 500;
static readonly UI_STATE_CHANGE_DELAY_MS = 300;
static readonly INITIAL_BLOCK_COUNT = 3;
static readonly INITIAL_FINALIZED_BLOCK_COUNT = 4;
static readonly MAX_BLOCK_COUNT = 15;
static readonly MAX_VALIDATORS_PER_ARC = 30;
static readonly MIN_ARC_COUNT = 10;
Expand Down
89 changes: 85 additions & 4 deletions tests/data-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Kusama } from '../src/client/model/substrate/network';
import { EventBus } from '../src/client/event/event-bus';
import { ChainvizEvent } from '../src/client/event/event';
import { ValidatorSummary } from '../src/client/model/subvt/validator-summary';
import { Constants } from '../src/client/util/constants';
import { Block } from '../src/client/model/chainviz/block';

// prettier-ignore
(BigInt.prototype as any).toJSON = function () { // eslint-disable-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -41,7 +43,7 @@ describe('data store', () => {
});
test('active validator list remove works and publishes event', async () => {
const dataStore = new DataStore();
dataStore.setNetwork(Kusama);
await dataStore.setNetwork(Kusama);
dataStore['processActiveValidatorListUpdate']({
finalizedBlockNumber: 0,
insert: validators,
Expand All @@ -50,8 +52,8 @@ describe('data store', () => {
});
const removeIds = [validators[0].accountId, validators[1].accountId];
const removeAddresses = [validators[0].address, validators[1].address];
const eventBus = EventBus.getInstance();
let eventReceived = false;
const eventBus = EventBus.getInstance();
eventBus.register(
ChainvizEvent.ACTIVE_VALIDATOR_LIST_REMOVED,
(removedStashAddresses: string[]) => {
Expand All @@ -74,7 +76,7 @@ describe('data store', () => {
});
test('active validator list update works and publishes event', async () => {
const dataStore = new DataStore();
dataStore.setNetwork(Kusama);
await dataStore.setNetwork(Kusama);
dataStore['processActiveValidatorListUpdate']({
finalizedBlockNumber: 0,
insert: validators,
Expand All @@ -86,8 +88,8 @@ describe('data store', () => {
],
removeIds: [],
});
const eventBus = EventBus.getInstance();
let eventReceived = false;
const eventBus = EventBus.getInstance();
eventBus.register(
ChainvizEvent.ACTIVE_VALIDATOR_LIST_UPDATED,
(updatedValidators: ValidatorSummary[]) => {
Expand Down Expand Up @@ -116,4 +118,83 @@ describe('data store', () => {
await new Promise((resolve) => setTimeout(resolve, 500));
expect(eventReceived).toBeTruthy();
});
test('gets correct number of initial blocks', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.connectSubstrateRPC();
await dataStore.getInitialBlocks();
expect(dataStore['blocks'].length > Constants.INITIAL_FINALIZED_BLOCK_COUNT).toBeTruthy();
expect(dataStore['blocks'].filter((block) => block.isFinalized).length).toBe(
Constants.INITIAL_FINALIZED_BLOCK_COUNT,
);
await dataStore.disconnectSubstrateClient();
});
test('gets paras', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.connectSubstrateRPC();
const paraIds = await dataStore['getParaIds']();
await dataStore.getParas();
expect(dataStore.paras.length).toBeGreaterThan(30);
expect(dataStore.paras.length).toBeLessThanOrEqual(paraIds.length);
await dataStore.disconnectSubstrateClient();
});
test('subscribes to new blocks', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.connectSubstrateRPC();
let eventReceived = false;
const eventBus = EventBus.getInstance();
eventBus.register(ChainvizEvent.NEW_BLOCK, (block: Block) => {
expect(block.events.length).toBeGreaterThan(0);
eventReceived = true;
});
dataStore.subscribeToNewBlocks();
await new Promise((resolve) => setTimeout(resolve, 10_000));
expect(eventReceived).toBeTruthy();
await dataStore.disconnectSubstrateClient();
});
test('subscribes to finalized blocks', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.connectSubstrateRPC();
let eventReceived = false;
const eventBus = EventBus.getInstance();
eventBus.register(ChainvizEvent.FINALIZED_BLOCK, (block: Block) => {
expect(block.isFinalized).toBeTruthy();
expect(block.events.length).toBeGreaterThan(0);
eventReceived = true;
});
dataStore.subscribeToFinalizedBlocks();
await new Promise((resolve) => setTimeout(resolve, 10_000));
expect(eventReceived).toBeTruthy();
await dataStore.disconnectSubstrateClient();
});
test('can get block by hash', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.connectSubstrateRPC();
const blockNumber = 19662640;
const blockHash = await dataStore.getBlockHash(blockNumber);
expect(blockHash).toBeDefined();
expect(blockHash!.toHex()).toBe(
'0x4c7b7509917a547a205adcc5a11fe86a8527707500b0d453b3fbc1d129109f35',
);
const block = await dataStore.getBlockByHash(blockHash!);
expect(block).toBeDefined();
expect(block!.block.header.number.toNumber()).toBe(blockNumber);
expect(block!.events.length).toBe(48);
await dataStore.disconnectSubstrateClient();
});
test('can get block by number', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.connectSubstrateRPC();
const blockNumber = 19662639;
const block = await dataStore.getBlockByNumber(blockNumber);
expect(block).toBeDefined();
expect(block!.block.header.number.toNumber()).toBe(blockNumber);
expect(block!.events.length).toBe(44);
await dataStore.disconnectSubstrateClient();
});
});

0 comments on commit a59ac75

Please sign in to comment.