Newer
Older
import { ApiPromise } from '@polkadot/api';
import { AugmentedConst } from '@polkadot/api/types/consts';
import { RpcPromiseResult } from '@polkadot/api/types/rpc';
import { GenericExtrinsic } from '@polkadot/types';
import { GenericCall } from '@polkadot/types/generic';
import {
BalanceOf,
BlockHash,
Hash,
SignedBlock,
} from '@polkadot/types/interfaces';
import { BadRequest } from 'http-errors';
import { sanitizeNumbers } from '../../sanitize/sanitizeNumbers';
import { createCall } from '../../test-helpers/createCall';
Tarik Gul
committed
import {
polkadotMetadata,
polkadotMetadataV29,
} from '../../test-helpers/metadata/metadata';
import {
kusamaRegistry,
polkadotRegistry,
Tarik Gul
committed
polkadotRegistryV29,
Tarik Gul
committed
import { ExtBaseWeightValue, PerClassValue } from '../../types/chains-config';
import { IExtrinsic } from '../../types/responses/';
import {
blockHash789629,
mockApi,
mockBlock789629,
import block789629 from '../test-helpers/mock/data/block789629.json';
import { parseNumberOrThrow } from '../test-helpers/mock/parseNumberOrThrow';
import block789629Extrinsic from '../test-helpers/responses/blocks/block789629Extrinsic.json';
import blocks789629Response from '../test-helpers/responses/blocks/blocks789629.json';
import { BlocksService } from './BlocksService';
/**
* For type casting mock getBlock functions so tsc does not complain
*/
type GetBlock = RpcPromiseResult<
(hash?: string | BlockHash | Uint8Array | undefined) => Promise<SignedBlock>
>;
/**
* Interface for the reponse in `fetchBlock` test suite
*/
interface ResponseObj {
extrinsics: IExtrinsic[];
}
Tarik Gul
committed
const blocksService = new BlocksService(mockApi, 0);
describe('BlocksService', () => {
describe('fetchBlock', () => {
it('works when ApiPromise works (block 789629)', async () => {
// fetchBlock options
const options = {
eventDocs: true,
extrinsicDocs: true,
checkFinalized: false,
queryFinalizedHead: false,
omitFinalizedTag: false,
};
sanitizeNumbers(
await blocksService.fetchBlock(blockHash789629, options)
).toMatchObject(blocks789629Response);
});
it('throws when an extrinsic is undefined', async () => {
// Create a block with undefined as the first extrinisic and the last extrinsic removed
const mockBlock789629BadExt = polkadotRegistry.createType(
'Block',
block789629
);
(undefined as unknown) as GenericExtrinsic
// fetchBlock Options
const options = {
eventDocs: false,
extrinsicDocs: false,
checkFinalized: false,
queryFinalizedHead: false,
omitFinalizedTag: false,
};
Zeke Mostov
committed
const tempGetBlock = mockApi.derive.chain.getBlock;
mockApi.derive.chain.getBlock = (() =>
Promise.resolve().then(() => {
return {
block: mockBlock789629BadExt,
};
}) as unknown) as GetBlock;
await expect(
blocksService.fetchBlock(blockHash789629, options)
).rejects.toThrow(
new Error(
`Cannot destructure property 'method' of 'extrinsic' as it is undefined.`
)
);
Zeke Mostov
committed
mockApi.derive.chain.getBlock = (tempGetBlock as unknown) as GetBlock;
it('Returns the finalized tag as undefined when omitFinalizedTag equals true', async () => {
// fetchBlock options
const options = {
eventDocs: true,
extrinsicDocs: true,
checkFinalized: false,
queryFinalizedHead: false,
omitFinalizedTag: true,
};
const block = await blocksService.fetchBlock(blockHash789629, options);
expect(block.finalized).toEqual(undefined);
});
it('Return an error with a null calcFee when perByte is undefined', async () => {
mockApi.consts.transactionPayment.transactionByteFee = (undefined as unknown) as BalanceOf &
AugmentedConst<'promise'>;
Tarik Gul
committed
const configuredBlocksService = new BlocksService(mockApi, 0);
// fetchBlock options
const options = {
eventDocs: true,
extrinsicDocs: true,
checkFinalized: false,
queryFinalizedHead: false,
omitFinalizedTag: false,
};
const response = sanitizeNumbers(
await configuredBlocksService.fetchBlock(blockHash789629, options)
);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const responseObj: ResponseObj = JSON.parse(JSON.stringify(response));
// Revert mockApi back to its original setting that was changed above.
mockApi.consts.transactionPayment.transactionByteFee = polkadotRegistry.createType(
'Balance',
1000000
) as BalanceOf & AugmentedConst<'promise'>;
expect(responseObj.extrinsics[3].info).toEqual({
error: 'Fee calculation not supported for 16#polkadot',
});
});
describe('createCalcFee & calc_fee', () => {
it('calculates partialFee for proxy.proxy in polkadot block 789629', async () => {
// tx hash: 0x6d6c0e955650e689b14fb472daf14d2bdced258c748ded1d6cb0da3bfcc5854f
const { calcFee } = await blocksService['createCalcFee'](
mockApi,
('0xParentHash' as unknown) as Hash,
mockBlock789629
);
expect(calcFee?.calc_fee(BigInt(399480000), 534, BigInt(125000000))).toBe(
'544000000'
);
it('calculates partialFee for utility.batch in polkadot block 789629', async () => {
// tx hash: 0xc96b4d442014fae60c932ea50cba30bf7dea3233f59d1fe98c6f6f85bfd51045
const { calcFee } = await blocksService['createCalcFee'](
mockApi,
('0xParentHash' as unknown) as Hash,
mockBlock789629
);
expect(
calcFee?.calc_fee(BigInt(941325000000), 1247, BigInt(125000000))
).toBe('1257000075');
Tarik Gul
committed
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
it('Should store a new runtime specific extrinsicBaseWeight when it doesnt exist', async () => {
// Instantiate a blocks service where we explicitly know the block store is empty.
const blocksServiceEmptyBlockStore = new BlocksService(mockApi, 0);
(mockApi.runtimeVersion
.specVersion as unknown) = polkadotRegistry.createType('u32', 20);
(mockApi.runtimeVersion
.specName as unknown) = polkadotRegistry.createType('Text', 'westend');
await blocksServiceEmptyBlockStore['createCalcFee'](
mockApi,
('0xParentHash' as unknown) as Hash,
mockBlock789629
);
expect(blocksServiceEmptyBlockStore['blockWeightStore'][20]).toBeTruthy();
(mockApi.runtimeVersion
.specVersion as unknown) = polkadotRegistry.createType('u32', 16);
(mockApi.runtimeVersion
.specName as unknown) = polkadotRegistry.createType('Text', 'polkadot');
});
});
describe('BlocksService.getWeight', () => {
const blockHash = polkadotRegistry.createType(
'BlockHash',
'0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3'
);
it('Should return correct `extrinsicBaseWeight`', async () => {
const weightValue = await blocksService['getWeight'](mockApi, blockHash);
expect(
((weightValue as unknown) as ExtBaseWeightValue).extrinsicBaseWeight
).toBe(BigInt(125000000));
});
it('Should return correct `blockWeights`', async () => {
const changeMetadataToV29 = () =>
Promise.resolve().then(() => polkadotMetadataV29);
const revertedMetadata = () =>
Promise.resolve().then(() => polkadotMetadata);
(mockApi.registry as unknown) = polkadotRegistryV29;
(mockApi.rpc.state.getMetadata as unknown) = changeMetadataToV29;
const weightValue = await blocksService['getWeight'](mockApi, blockHash);
expect(
((weightValue as unknown) as PerClassValue).perClass.normal
.baseExtrinsic
).toBe(BigInt(125000000));
expect(
((weightValue as unknown) as PerClassValue).perClass.operational
.baseExtrinsic
).toBe(BigInt(1));
expect(
((weightValue as unknown) as PerClassValue).perClass.mandatory
.baseExtrinsic
).toBe(BigInt(512000000000001));
(mockApi.registry as unknown) = polkadotRegistry;
(mockApi.rpc.state.getMetadata as unknown) = revertedMetadata;
});
describe('BlocksService.parseGenericCall', () => {
const transfer = createCall('balances', 'transfer', {
value: 12,
dest: kusamaRegistry.createType(
'AccountId',
'14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3'
), // Bob
});
const transferOutput = {
method: {
pallet: 'balances',
},
args: {
dest: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
value: 12,
},
};
it('does not handle an empty object', () =>
expect(() =>
blocksService['parseGenericCall'](
({} as unknown) as GenericCall,
mockBlock789629.registry
)
).toThrow());
it('parses a simple balances.transfer', () => {
expect(
JSON.stringify(
blocksService['parseGenericCall'](transfer, mockBlock789629.registry)
)
).toBe(JSON.stringify(transferOutput));
it('parses utility.batch nested 4 deep', () => {
const batch1 = createCall('utility', 'batch', {
calls: [transfer],
});
const batch2 = createCall('utility', 'batch', {
calls: [batch1, transfer],
});
const batch3 = createCall('utility', 'batch', {
calls: [batch2, transfer],
});
const batch4 = createCall('utility', 'batch', {
calls: [batch3, transfer],
});
const baseBatch = {
method: {
pallet: 'utility',
},
calls: [],
},
};
expect(
JSON.stringify(
blocksService['parseGenericCall'](batch4, mockBlock789629.registry)
)
).toBe(
JSON.stringify({
...baseBatch,
args: {
calls: [
{
...baseBatch,
args: {
calls: [
{
...baseBatch,
args: {
calls: [
{
...baseBatch,
args: {
calls: [transferOutput],
transferOutput,
],
},
transferOutput,
],
},
transferOutput,
],
},
})
);
it('handles a batch sudo proxy transfer', () => {
const proxy = createCall('proxy', 'proxy', {
forceProxyType: 'Any',
call: transfer,
});
const sudo = createCall('sudo', 'sudo', {
call: proxy,
});
const batch = createCall('utility', 'batch', {
calls: [sudo, sudo, sudo],
});
method: {
pallet: 'sudo',
},
method: {
pallet: 'proxy',
},
real: '5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM',
force_proxy_type: 'Any',
call: transferOutput,
},
},
JSON.stringify(
blocksService['parseGenericCall'](batch, mockBlock789629.registry)
)
).toEqual(
JSON.stringify({
method: {
pallet: 'utility',
},
args: {
calls: [sudoOutput, sudoOutput, sudoOutput],
},
})
);
});
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
describe('BlockService.isFinalizedBlock', () => {
const finalizedHead = polkadotRegistry.createType(
'BlockHash',
'0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3'
);
const blockNumber = polkadotRegistry.createType(
'Compact<BlockNumber>',
789629
);
it('Returns false when queried blockId is not canonical', async () => {
const getHeader = (_hash: Hash) =>
Promise.resolve().then(() => mockForkedBlock789629.header);
const getBlockHash = (_zero: number) =>
Promise.resolve().then(() => finalizedHead);
const forkMockApi = {
rpc: {
chain: {
getHeader,
getBlockHash,
},
},
} as ApiPromise;
const queriedHash = polkadotRegistry.createType(
'BlockHash',
'0x7b713de604a99857f6c25eacc115a4f28d2611a23d9ddff99ab0e4f1c17a8578'
);
expect(
await blocksService['isFinalizedBlock'](
forkMockApi,
blockNumber,
queriedHash,
finalizedHead,
true
)
).toEqual(false);
});
it('Returns true when queried blockId is canonical', async () => {
expect(
await blocksService['isFinalizedBlock'](
mockApi,
blockNumber,
finalizedHead,
finalizedHead,
true
)
).toEqual(true);
});
});
describe('fetchExrinsicByIndex', () => {
// fetchBlock options
const options = {
eventDocs: false,
extrinsicDocs: false,
checkFinalized: false,
queryFinalizedHead: false,
omitFinalizedTag: false,
};
it('Returns the correct extrinisics object for block 789629', async () => {
const block = await blocksService.fetchBlock(blockHash789629, options);
/**
* The `extrinsicIndex` (second param) is being tested for a non-zero
* index here.
*/
const extrinsic = blocksService['fetchExtrinsicByIndex'](block, 2);
expect(JSON.stringify(sanitizeNumbers(extrinsic))).toEqual(
JSON.stringify(block789629Extrinsic)
);
});
it("Throw an error when `extrinsicIndex` doesn't exist", async () => {
const block = await blocksService.fetchBlock(blockHash789629, options);
expect(() => {
blocksService['fetchExtrinsicByIndex'](block, 5);
}).toThrow(new BadRequest('Requested `extrinsicIndex` does not exist'));
});
it('Throw an error when param `extrinsicIndex` is less than 0', () => {
expect(() => {
parseNumberOrThrow(
'-5',
'`exstrinsicIndex` path param is not a number'
);
}).toThrow(
new BadRequest('`exstrinsicIndex` path param is not a number')
);
});
});