@acala-network/chopsticks 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,13 @@ Make sure you have setup Rust environment (>= 1.64).
26
26
  - Replay latest block
27
27
  - `yarn start run-block --endpoint=wss://acala-rpc-2.aca-api.network/ws`
28
28
  - This will replay the last block and print out the changed storages
29
+ - Use option `--block` to replay certain block hash
29
30
  - Use option `--output-path=<file_path>` to print out JSON file
31
+ - Use option `--html` to generate storage diff preview (add `--open` to automatically open file)
32
+
33
+ - Dry run extrinsic, same as `run-block`, example:
34
+ - `yarn start dry-run --config=configs/mandala.yml --html --open --extrinsic 0x51028400d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01a4143d076116f80a4074ed7acc90f9e1f9e2db54603900be53145a6ef5faa333f2614e687a06a9c886a909d77f3115b1a9d989afdc9fd73e5dca941bc690ae8a0000000a00008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a481b000000a1edccce1bc2d3`
35
+
30
36
  - Run a test node
31
37
  - `yarn start dev --endpoint=wss://acala-rpc-2.aca-api.network/ws`
32
38
  - You have a test node running at `ws://localhost:8000`
@@ -34,7 +40,6 @@ Make sure you have setup Rust environment (>= 1.64).
34
40
  - Submit any transaction to produce a new block in the in parallel reality
35
41
  - (Optional) Pre-define/override storage using option `--import-storage=storage.[json/yaml]`. See example storage below.
36
42
  ```json5
37
- // prettier-ignore
38
43
  {
39
44
  "Sudo": {
40
45
  "Key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
@@ -1,4 +1,5 @@
1
- import { Block } from './block';
2
- import { Header } from '@polkadot/types/interfaces';
1
+ import { Block, TaskCallResponse } from './block';
3
2
  import { HexString } from '@polkadot/util/types';
4
- export declare const buildBlock: (head: Block, header: Header, inherents: HexString[], extrinsics: HexString[]) => Promise<[Block, HexString[]]>;
3
+ export declare const buildBlock: (head: Block, inherents: HexString[], extrinsics: HexString[]) => Promise<[Block, HexString[]]>;
4
+ export declare const dryRunExtrinsic: (head: Block, inherents: HexString[], extrinsic: HexString) => Promise<TaskCallResponse>;
5
+ export declare const dryRunInherents: (head: Block, inherents: HexString[]) => Promise<[HexString, HexString | null][]>;
@@ -1,27 +1,100 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildBlock = void 0;
3
+ exports.dryRunInherents = exports.dryRunExtrinsic = exports.buildBlock = void 0;
4
4
  const block_1 = require("./block");
5
+ const util_1 = require("@polkadot/util");
6
+ const utils_1 = require("../utils");
5
7
  const logger_1 = require("../logger");
8
+ const time_travel_1 = require("../utils/time-travel");
6
9
  const logger = logger_1.defaultLogger.child({ name: 'block-builder' });
7
- const buildBlock = async (head, header, inherents, extrinsics) => {
8
- const registry = await head.registry;
9
- const pendingExtrinsics = [];
10
+ const getConsensus = (header) => {
11
+ if (header.digest.logs.length === 0)
12
+ return;
13
+ const preRuntime = header.digest.logs[0].asPreRuntime;
14
+ const [consensusEngine, slot] = preRuntime;
15
+ return { consensusEngine, slot, rest: header.digest.logs.slice(1) };
16
+ };
17
+ const getNewSlot = (digest, slotNumber) => {
18
+ if (digest.isPrimary) {
19
+ return {
20
+ primary: {
21
+ ...digest.asPrimary.toJSON(),
22
+ slotNumber,
23
+ },
24
+ };
25
+ }
26
+ if (digest.isSecondaryPlain) {
27
+ return {
28
+ secondaryPlain: {
29
+ ...digest.asSecondaryPlain.toJSON(),
30
+ slotNumber,
31
+ },
32
+ };
33
+ }
34
+ if (digest.isSecondaryVRF) {
35
+ return {
36
+ secondaryVRF: {
37
+ ...digest.asSecondaryVRF.toJSON(),
38
+ slotNumber,
39
+ },
40
+ };
41
+ }
42
+ return digest.toJSON();
43
+ };
44
+ const newHeader = async (head) => {
45
+ const meta = await head.meta;
46
+ const parentHeader = await head.header;
47
+ let newLogs = parentHeader.digest.logs;
48
+ const consensus = getConsensus(parentHeader);
49
+ if (consensus?.consensusEngine.isAura) {
50
+ const slot = await (0, time_travel_1.getCurrentSlot)(head.chain);
51
+ const newSlot = (0, util_1.compactAddLength)(meta.registry.createType('Slot', slot + 1).toU8a());
52
+ newLogs = [{ PreRuntime: [consensus.consensusEngine, newSlot] }, ...consensus.rest];
53
+ }
54
+ else if (consensus?.consensusEngine.isBabe) {
55
+ const slot = await (0, time_travel_1.getCurrentSlot)(head.chain);
56
+ const digest = meta.registry.createType('RawBabePreDigest', consensus.slot);
57
+ const newSlot = (0, util_1.compactAddLength)(meta.registry.createType('RawBabePreDigest', getNewSlot(digest, slot + 1)).toU8a());
58
+ newLogs = [{ PreRuntime: [consensus.consensusEngine, newSlot] }, ...consensus.rest];
59
+ }
60
+ else if (consensus?.consensusEngine?.toString() == 'nmbs') {
61
+ const nmbsKey = (0, util_1.stringToHex)('nmbs');
62
+ newLogs = [
63
+ {
64
+ // Using previous block author
65
+ PreRuntime: [
66
+ consensus.consensusEngine,
67
+ parentHeader.digest.logs
68
+ .find((log) => log.isPreRuntime && log.asPreRuntime[0].toHex() == nmbsKey)
69
+ ?.asPreRuntime[1].toHex(),
70
+ ],
71
+ },
72
+ ...consensus.rest,
73
+ head.pushStorageLayer().set((0, utils_1.compactHex)(meta.query.randomness.notFirstBlock()), "Deleted" /* StorageValueKind.Deleted */),
74
+ ];
75
+ }
76
+ const header = meta.registry.createType('Header', {
77
+ parentHash: head.hash,
78
+ number: head.number + 1,
79
+ stateRoot: '0x0000000000000000000000000000000000000000000000000000000000000000',
80
+ extrinsicsRoot: '0x0000000000000000000000000000000000000000000000000000000000000000',
81
+ digest: {
82
+ logs: newLogs,
83
+ },
84
+ });
85
+ return header;
86
+ };
87
+ const initNewBlock = async (head, header, inherents) => {
10
88
  const blockNumber = header.number.toNumber();
11
89
  const hash = `0x${Math.round(Math.random() * 100000000)
12
90
  .toString(16)
13
91
  .padEnd(64, '0')}`;
14
92
  const newBlock = new block_1.Block(head.chain, blockNumber, hash, head, { header, extrinsics: [], storage: head.storage });
15
- logger.info({
16
- number: blockNumber,
17
- extrinsicsCount: extrinsics.length,
18
- tempHash: newBlock.hash,
19
- }, `Building block #${blockNumber.toLocaleString()}`);
20
93
  {
21
94
  // initialize block
22
95
  const { storageDiff } = await newBlock.call('Core_initialize_block', header.toHex());
23
- logger.trace((0, logger_1.truncateStorageDiff)(storageDiff), 'Initialize block');
24
96
  newBlock.pushStorageLayer().setAll(storageDiff);
97
+ logger.trace((0, logger_1.truncateStorageDiff)(storageDiff), 'Initialize block');
25
98
  }
26
99
  // apply inherents
27
100
  for (const extrinsic of inherents) {
@@ -35,6 +108,18 @@ const buildBlock = async (head, header, inherents, extrinsics) => {
35
108
  throw new Error('Failed to apply inherents');
36
109
  }
37
110
  }
111
+ return newBlock;
112
+ };
113
+ const buildBlock = async (head, inherents, extrinsics) => {
114
+ const registry = await head.registry;
115
+ const header = await newHeader(head);
116
+ const newBlock = await initNewBlock(head, header, inherents);
117
+ logger.info({
118
+ number: newBlock.number,
119
+ extrinsicsCount: extrinsics.length,
120
+ tempHash: newBlock.hash,
121
+ }, `Building block #${newBlock.number.toLocaleString()}`);
122
+ const pendingExtrinsics = [];
38
123
  // apply extrinsics
39
124
  for (const extrinsic of extrinsics) {
40
125
  try {
@@ -59,13 +144,25 @@ const buildBlock = async (head, header, inherents, extrinsics) => {
59
144
  });
60
145
  const storageDiff = await newBlock.storageDiff();
61
146
  logger.trace(Object.entries(storageDiff).map(([key, value]) => [key, (0, logger_1.truncate)(value)]), 'Final block');
62
- const finalBlock = new block_1.Block(head.chain, blockNumber, blockData.hash.toHex(), head, {
147
+ const finalBlock = new block_1.Block(head.chain, newBlock.number, blockData.hash.toHex(), head, {
63
148
  header,
64
149
  extrinsics: [...inherents, ...extrinsics],
65
150
  storage: head.storage,
66
151
  storageDiff,
67
152
  });
68
- logger.info({ hash: finalBlock.hash, number: blockNumber }, `Block built #${blockNumber.toLocaleString()} hash ${finalBlock.hash}`);
153
+ logger.info({ hash: finalBlock.hash, number: newBlock.number }, `Block built #${newBlock.number.toLocaleString()} hash ${finalBlock.hash}`);
69
154
  return [finalBlock, pendingExtrinsics];
70
155
  };
71
156
  exports.buildBlock = buildBlock;
157
+ const dryRunExtrinsic = async (head, inherents, extrinsic) => {
158
+ const header = await newHeader(head);
159
+ const newBlock = await initNewBlock(head, header, inherents);
160
+ return newBlock.call('BlockBuilder_apply_extrinsic', extrinsic);
161
+ };
162
+ exports.dryRunExtrinsic = dryRunExtrinsic;
163
+ const dryRunInherents = async (head, inherents) => {
164
+ const header = await newHeader(head);
165
+ const newBlock = await initNewBlock(head, header, inherents);
166
+ return Object.entries(await newBlock.storageDiff());
167
+ };
168
+ exports.dryRunInherents = dryRunInherents;
@@ -32,7 +32,7 @@ export declare class Block {
32
32
  }): Promise<string[]>;
33
33
  pushStorageLayer(): StorageLayer;
34
34
  popStorageLayer(): void;
35
- storageDiff(): Promise<Record<string, string>>;
35
+ storageDiff(): Promise<Record<HexString, HexString | null>>;
36
36
  get wasm(): Promise<`0x${string}`>;
37
37
  setWasm(wasm: HexString): void;
38
38
  get registry(): Promise<TypeRegistry>;
@@ -1,8 +1,9 @@
1
+ import { ApplyExtrinsicResult } from '@polkadot/types/interfaces';
1
2
  import { DataSource } from 'typeorm';
2
3
  import { HexString } from '@polkadot/util/types';
3
4
  import { Api } from '../api';
4
5
  import { Block } from './block';
5
- import { BuildBlockMode, BuildBlockParams } from './txpool';
6
+ import { BuildBlockMode, BuildBlockParams, HorizontalMessage } from './txpool';
6
7
  import { HeadState } from './head-state';
7
8
  import { InherentProvider } from './inherent';
8
9
  export interface Options {
@@ -33,4 +34,9 @@ export declare class Blockchain {
33
34
  setHead(block: Block): Promise<void>;
34
35
  submitExtrinsic(extrinsic: HexString): Promise<HexString>;
35
36
  newBlock(params?: BuildBlockParams): Promise<Block>;
37
+ dryRunExtrinsic(extrinsic: HexString): Promise<{
38
+ outcome: ApplyExtrinsicResult;
39
+ storageDiff: [HexString, HexString | null][];
40
+ }>;
41
+ dryRunHrmp(hrmp: Record<number, HorizontalMessage[]>): Promise<[HexString, HexString | null][]>;
36
42
  }
@@ -7,6 +7,7 @@ const block_1 = require("./block");
7
7
  const txpool_1 = require("./txpool");
8
8
  const head_state_1 = require("./head-state");
9
9
  const logger_1 = require("../logger");
10
+ const block_builder_1 = require("./block-builder");
10
11
  const logger = logger_1.defaultLogger.child({ name: 'blockchain' });
11
12
  class Blockchain {
12
13
  api;
@@ -14,6 +15,7 @@ class Blockchain {
14
15
  mockSignatureHost;
15
16
  allowUnresolvedImports;
16
17
  #txpool;
18
+ #inherentProvider;
17
19
  #head;
18
20
  #blocksByNumber = [];
19
21
  #blocksByHash = {};
@@ -27,6 +29,7 @@ class Blockchain {
27
29
  this.#head = new block_1.Block(this, header.number, header.hash);
28
30
  this.#registerBlock(this.#head);
29
31
  this.#txpool = new txpool_1.TxPool(this, inherentProvider, buildBlockMode);
32
+ this.#inherentProvider = inherentProvider;
30
33
  this.headState = new head_state_1.HeadState(this.#head);
31
34
  }
32
35
  #registerBlock(block) {
@@ -115,5 +118,20 @@ class Blockchain {
115
118
  await this.#txpool.buildBlock(params);
116
119
  return this.#head;
117
120
  }
121
+ async dryRunExtrinsic(extrinsic) {
122
+ await this.api.isReady;
123
+ const head = this.head;
124
+ const registry = await head.registry;
125
+ const inherents = await this.#inherentProvider.createInherents(head);
126
+ const { result, storageDiff } = await (0, block_builder_1.dryRunExtrinsic)(head, inherents, extrinsic);
127
+ const outcome = registry.createType('ApplyExtrinsicResult', result);
128
+ return { outcome, storageDiff };
129
+ }
130
+ async dryRunHrmp(hrmp) {
131
+ await this.api.isReady;
132
+ const head = this.head;
133
+ const inherents = await this.#inherentProvider.createInherents(head, { horizontalMessages: hrmp });
134
+ return (0, block_builder_1.dryRunInherents)(head, inherents);
135
+ }
118
136
  }
119
137
  exports.Blockchain = Blockchain;
@@ -3,6 +3,8 @@ import { BuildBlockParams } from '../txpool';
3
3
  import { HexString } from '@polkadot/util/types';
4
4
  export { SetValidationData } from './parachain/validation-data';
5
5
  export { ParaInherentEnter } from './para-enter';
6
+ export { SetBabeRandomness } from './parachain/babe-randomness';
7
+ export { SetNimbusAuthorInherent } from './parachain/nimbus-author-inherent';
6
8
  export interface CreateInherents {
7
9
  createInherents(parent: Block, params?: BuildBlockParams['inherent']): Promise<HexString[]>;
8
10
  }
@@ -1,12 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InherentProviders = exports.SetTimestamp = exports.ParaInherentEnter = exports.SetValidationData = void 0;
3
+ exports.InherentProviders = exports.SetTimestamp = exports.SetNimbusAuthorInherent = exports.SetBabeRandomness = exports.ParaInherentEnter = exports.SetValidationData = void 0;
4
4
  const types_1 = require("@polkadot/types");
5
5
  const time_travel_1 = require("../../utils/time-travel");
6
6
  var validation_data_1 = require("./parachain/validation-data");
7
7
  Object.defineProperty(exports, "SetValidationData", { enumerable: true, get: function () { return validation_data_1.SetValidationData; } });
8
8
  var para_enter_1 = require("./para-enter");
9
9
  Object.defineProperty(exports, "ParaInherentEnter", { enumerable: true, get: function () { return para_enter_1.ParaInherentEnter; } });
10
+ var babe_randomness_1 = require("./parachain/babe-randomness");
11
+ Object.defineProperty(exports, "SetBabeRandomness", { enumerable: true, get: function () { return babe_randomness_1.SetBabeRandomness; } });
12
+ var nimbus_author_inherent_1 = require("./parachain/nimbus-author-inherent");
13
+ Object.defineProperty(exports, "SetNimbusAuthorInherent", { enumerable: true, get: function () { return nimbus_author_inherent_1.SetNimbusAuthorInherent; } });
10
14
  class SetTimestamp {
11
15
  async createInherents(parent) {
12
16
  const meta = await parent.meta;
@@ -0,0 +1,7 @@
1
+ import { HexString } from '@polkadot/util/types';
2
+ import { Block } from '../../../blockchain/block';
3
+ import { BuildBlockParams } from '../../txpool';
4
+ import { CreateInherents } from '..';
5
+ export declare class SetBabeRandomness implements CreateInherents {
6
+ createInherents(parent: Block, _params?: BuildBlockParams['inherent']): Promise<HexString[]>;
7
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SetBabeRandomness = void 0;
4
+ const types_1 = require("@polkadot/types");
5
+ // Support for Moonbeam pallet-randomness mandatory inherent
6
+ class SetBabeRandomness {
7
+ async createInherents(parent, _params) {
8
+ const meta = await parent.meta;
9
+ if (!meta.tx.randomness?.setBabeRandomnessResults) {
10
+ return [];
11
+ }
12
+ return [new types_1.GenericExtrinsic(meta.registry, meta.tx.randomness.setBabeRandomnessResults()).toHex()];
13
+ }
14
+ }
15
+ exports.SetBabeRandomness = SetBabeRandomness;
@@ -0,0 +1,7 @@
1
+ import { HexString } from '@polkadot/util/types';
2
+ import { Block } from '../../../blockchain/block';
3
+ import { BuildBlockParams } from '../../txpool';
4
+ import { CreateInherents } from '..';
5
+ export declare class SetNimbusAuthorInherent implements CreateInherents {
6
+ createInherents(parent: Block, _params?: BuildBlockParams['inherent']): Promise<HexString[]>;
7
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SetNimbusAuthorInherent = void 0;
4
+ const types_1 = require("@polkadot/types");
5
+ // Support for Nimbus Author Inherent
6
+ class SetNimbusAuthorInherent {
7
+ async createInherents(parent, _params) {
8
+ const meta = await parent.meta;
9
+ if (!meta.tx.authorInherent?.kickOffAuthorshipValidation) {
10
+ return [];
11
+ }
12
+ return [new types_1.GenericExtrinsic(meta.registry, meta.tx.authorInherent.kickOffAuthorshipValidation()).toHex()];
13
+ }
14
+ }
15
+ exports.SetNimbusAuthorInherent = SetNimbusAuthorInherent;
@@ -4,50 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.TxPool = exports.BuildBlockMode = void 0;
7
- const util_1 = require("@polkadot/util");
8
7
  const lodash_1 = __importDefault(require("lodash"));
9
8
  const block_builder_1 = require("./block-builder");
10
- const time_travel_1 = require("../utils/time-travel");
11
9
  var BuildBlockMode;
12
10
  (function (BuildBlockMode) {
13
11
  BuildBlockMode[BuildBlockMode["Batch"] = 0] = "Batch";
14
12
  BuildBlockMode[BuildBlockMode["Instant"] = 1] = "Instant";
15
13
  BuildBlockMode[BuildBlockMode["Manual"] = 2] = "Manual";
16
14
  })(BuildBlockMode = exports.BuildBlockMode || (exports.BuildBlockMode = {}));
17
- const getConsensus = (header) => {
18
- if (header.digest.logs.length === 0)
19
- return;
20
- const preRuntime = header.digest.logs[0].asPreRuntime;
21
- const [consensusEngine, slot] = preRuntime;
22
- return { consensusEngine, slot, rest: header.digest.logs.slice(1) };
23
- };
24
- const getNewSlot = (digest, slotNumber) => {
25
- if (digest.isPrimary) {
26
- return {
27
- primary: {
28
- ...digest.asPrimary.toJSON(),
29
- slotNumber,
30
- },
31
- };
32
- }
33
- if (digest.isSecondaryPlain) {
34
- return {
35
- secondaryPlain: {
36
- ...digest.asSecondaryPlain.toJSON(),
37
- slotNumber,
38
- },
39
- };
40
- }
41
- if (digest.isSecondaryVRF) {
42
- return {
43
- secondaryVRF: {
44
- ...digest.asSecondaryVRF.toJSON(),
45
- slotNumber,
46
- },
47
- };
48
- }
49
- return digest.toJSON();
50
- };
51
15
  class TxPool {
52
16
  #chain;
53
17
  #pool = [];
@@ -87,33 +51,8 @@ class TxPool {
87
51
  await wait.catch(() => { }); // ignore error
88
52
  const head = this.#chain.head;
89
53
  const extrinsics = this.#pool.splice(0);
90
- const meta = await head.meta;
91
- const parentHeader = await head.header;
92
- let newLogs = parentHeader.digest.logs;
93
- const consensus = getConsensus(parentHeader);
94
- if (consensus?.consensusEngine.isAura) {
95
- const slot = await (0, time_travel_1.getCurrentSlot)(this.#chain);
96
- const newSlot = (0, util_1.compactAddLength)(meta.registry.createType('Slot', slot + 1).toU8a());
97
- newLogs = [{ PreRuntime: [consensus.consensusEngine, newSlot] }, ...consensus.rest];
98
- }
99
- else if (consensus?.consensusEngine.isBabe) {
100
- const slot = await (0, time_travel_1.getCurrentSlot)(this.#chain);
101
- const digest = meta.registry.createType('RawBabePreDigest', consensus.slot);
102
- const newSlot = (0, util_1.compactAddLength)(meta.registry.createType('RawBabePreDigest', getNewSlot(digest, slot + 1)).toU8a());
103
- newLogs = [{ PreRuntime: [consensus.consensusEngine, newSlot] }, ...consensus.rest];
104
- }
105
- const registry = await head.registry;
106
- const header = registry.createType('Header', {
107
- parentHash: head.hash,
108
- number: head.number + 1,
109
- stateRoot: '0x0000000000000000000000000000000000000000000000000000000000000000',
110
- extrinsicsRoot: '0x0000000000000000000000000000000000000000000000000000000000000000',
111
- digest: {
112
- logs: newLogs,
113
- },
114
- });
115
54
  const inherents = await this.#inherentProvider.createInherents(head, params?.inherent);
116
- const [newBlock, pendingExtrinsics] = await (0, block_builder_1.buildBlock)(head, header, inherents, extrinsics);
55
+ const [newBlock, pendingExtrinsics] = await (0, block_builder_1.buildBlock)(head, inherents, extrinsics);
117
56
  this.#pool.push(...pendingExtrinsics);
118
57
  await this.#chain.setHead(newBlock);
119
58
  }
@@ -0,0 +1,2 @@
1
+ import { Config } from './schema';
2
+ export declare const dryRun: (argv: Config) => Promise<never>;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dryRun = void 0;
4
+ const util_crypto_1 = require("@polkadot/util-crypto");
5
+ const node_fs_1 = require("node:fs");
6
+ const generate_html_diff_1 = require("./utils/generate-html-diff");
7
+ const open_html_1 = require("./utils/open-html");
8
+ const setup_1 = require("./setup");
9
+ const dryRun = async (argv) => {
10
+ const context = await (0, setup_1.setup)(argv);
11
+ const { outcome, storageDiff } = await context.chain.dryRunExtrinsic(argv['extrinsic']);
12
+ if (outcome.isErr) {
13
+ throw new Error(outcome.asErr.toString());
14
+ }
15
+ if (argv['html']) {
16
+ const filePath = await (0, generate_html_diff_1.generateHtmlDiffPreviewFile)(context.chain.head, storageDiff, (0, util_crypto_1.blake2AsHex)(argv['extrinsic'], 256));
17
+ console.log(`Generated preview ${filePath}`);
18
+ if (argv['open']) {
19
+ (0, open_html_1.openHtml)(filePath);
20
+ }
21
+ }
22
+ else if (argv['output-path']) {
23
+ (0, node_fs_1.writeFileSync)(argv['output-path'], JSON.stringify({ outcome: outcome.toHuman(), storageDiff }, null, 2));
24
+ }
25
+ else {
26
+ console.dir({ outcome: outcome.toHuman(), storageDiff }, { depth: null, colors: false });
27
+ }
28
+ process.exit(0);
29
+ };
30
+ exports.dryRun = dryRun;
package/dist/index.js CHANGED
@@ -10,8 +10,10 @@ const yargs_1 = __importDefault(require("yargs"));
10
10
  const txpool_1 = require("./blockchain/txpool");
11
11
  const schema_1 = require("./schema");
12
12
  const xcm_1 = require("./xcm");
13
- const decode_key_1 = require("./decode-key");
13
+ const decoder_1 = require("./utils/decoder");
14
+ const dry_run_1 = require("./dry-run");
14
15
  const run_block_1 = require("./run-block");
16
+ const setup_1 = require("./setup");
15
17
  const setup_with_server_1 = require("./setup-with-server");
16
18
  const processConfig = (path) => {
17
19
  const configFile = (0, node_fs_1.readFileSync)(path, 'utf8');
@@ -31,7 +33,6 @@ const defaultOptions = {
31
33
  },
32
34
  block: {
33
35
  desc: 'Block hash or block number. Default to latest block',
34
- string: true,
35
36
  },
36
37
  'wasm-override': {
37
38
  desc: 'Path to wasm override',
@@ -58,8 +59,34 @@ const defaultOptions = {
58
59
  desc: 'File path to print output',
59
60
  string: true,
60
61
  },
62
+ html: {
63
+ desc: 'Generate html with storage diff',
64
+ },
65
+ open: {
66
+ desc: 'Open generated html',
67
+ },
61
68
  }), async (argv) => {
62
69
  await (0, run_block_1.runBlock)(processArgv(argv));
70
+ })
71
+ .command('dry-run', 'Dry run an extrinsic', (yargs) => yargs.options({
72
+ ...defaultOptions,
73
+ extrinsic: {
74
+ desc: 'Extrinsic to dry run',
75
+ string: true,
76
+ required: true,
77
+ },
78
+ 'output-path': {
79
+ desc: 'File path to print output',
80
+ string: true,
81
+ },
82
+ html: {
83
+ desc: 'Generate html with storage diff',
84
+ },
85
+ open: {
86
+ desc: 'Open generated html',
87
+ },
88
+ }), async (argv) => {
89
+ await (0, dry_run_1.dryRun)(processArgv(argv));
63
90
  })
64
91
  .command('dev', 'Dev mode', (yargs) => yargs.options({
65
92
  ...defaultOptions,
@@ -90,7 +117,15 @@ const defaultOptions = {
90
117
  .options({
91
118
  ...defaultOptions,
92
119
  }), async (argv) => {
93
- await (0, decode_key_1.decodeKey)(processArgv(argv));
120
+ const context = await (0, setup_1.setup)(processArgv(argv));
121
+ const { storage, decodedKey } = await (0, decoder_1.decodeKey)(context.chain.head, argv.key);
122
+ if (storage && decodedKey) {
123
+ console.log(`${storage.section}.${storage.method}`, decodedKey.args.map((x) => JSON.stringify(x.toHuman())).join(', '));
124
+ }
125
+ else {
126
+ console.log('Unknown');
127
+ }
128
+ process.exit(0);
94
129
  })
95
130
  .command('xcm', 'XCM setup with relaychain and parachains', (yargs) => yargs.options({
96
131
  relaychain: {
package/dist/rpc/dev.js CHANGED
@@ -2,18 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const shared_1 = require("./shared");
4
4
  const set_storage_1 = require("../utils/set-storage");
5
+ const decoder_1 = require("../utils/decoder");
5
6
  const logger_1 = require("../logger");
7
+ const generate_html_diff_1 = require("../utils/generate-html-diff");
6
8
  const time_travel_1 = require("../utils/time-travel");
7
9
  const logger = logger_1.defaultLogger.child({ name: 'rpc-dev' });
8
10
  const handlers = {
9
11
  dev_newBlock: async (context, [param]) => {
10
- const { count, to } = param || {};
12
+ const { count, to, hrmp } = param || {};
11
13
  const now = context.chain.head.number;
12
14
  const diff = to ? to - now : count;
13
15
  const finalCount = diff > 0 ? diff : 1;
14
16
  let finalHash;
15
17
  for (let i = 0; i < finalCount; i++) {
16
- const block = await context.chain.newBlock().catch((error) => {
18
+ const block = await context.chain.newBlock({ inherent: { horizontalMessages: hrmp } }).catch((error) => {
17
19
  throw new shared_1.ResponseError(1, error.toString());
18
20
  });
19
21
  logger.debug({ hash: block.hash }, 'dev_newBlock');
@@ -39,5 +41,30 @@ const handlers = {
39
41
  await (0, time_travel_1.timeTravel)(context.chain, timestamp);
40
42
  return timestamp;
41
43
  },
44
+ dev_dryRun: async (context, [{ html, extrinsic, hrmp, raw }]) => {
45
+ const dryRun = async () => {
46
+ if (extrinsic) {
47
+ const { outcome, storageDiff } = await context.chain.dryRunExtrinsic(extrinsic);
48
+ if (outcome.isErr) {
49
+ throw new Error(outcome.asErr.toString());
50
+ }
51
+ return storageDiff;
52
+ }
53
+ return context.chain.dryRunHrmp(hrmp);
54
+ };
55
+ const storageDiff = await dryRun();
56
+ if (html) {
57
+ return (0, generate_html_diff_1.generateHtmlDiff)(context.chain.head, storageDiff);
58
+ }
59
+ if (raw) {
60
+ return storageDiff;
61
+ }
62
+ const [oldData, newData, delta] = await (0, decoder_1.decodeStorageDiff)(context.chain.head, storageDiff);
63
+ return {
64
+ old: oldData,
65
+ new: newData,
66
+ delta,
67
+ };
68
+ },
42
69
  };
43
70
  exports.default = handlers;
@@ -1,2 +1,2 @@
1
1
  import { Config } from './schema';
2
- export declare const runBlock: (argv: Config) => Promise<void>;
2
+ export declare const runBlock: (argv: Config) => Promise<never>;
package/dist/run-block.js CHANGED
@@ -2,10 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runBlock = void 0;
4
4
  const node_fs_1 = require("node:fs");
5
+ const generate_html_diff_1 = require("./utils/generate-html-diff");
6
+ const open_html_1 = require("./utils/open-html");
5
7
  const executor_1 = require("./executor");
6
- const setup_with_server_1 = require("./setup-with-server");
8
+ const setup_1 = require("./setup");
7
9
  const runBlock = async (argv) => {
8
- const context = await (0, setup_with_server_1.setupWithServer)(argv);
10
+ const context = await (0, setup_1.setup)(argv);
9
11
  const header = await context.chain.head.header;
10
12
  const wasm = await context.chain.head.wasm;
11
13
  const block = context.chain.head;
@@ -24,13 +26,22 @@ const runBlock = async (argv) => {
24
26
  mockSignatureHost: false,
25
27
  allowUnresolvedImports: false,
26
28
  }, (0, executor_1.taskHandler)(parent));
27
- if (argv['output-path']) {
29
+ if (result.Error) {
30
+ throw new Error(result.Error);
31
+ }
32
+ if (argv['html']) {
33
+ const filePath = await (0, generate_html_diff_1.generateHtmlDiffPreviewFile)(block, result.Call.storageDiff, block.hash);
34
+ console.log(`Generated preview ${filePath}`);
35
+ if (argv['open']) {
36
+ (0, open_html_1.openHtml)(filePath);
37
+ }
38
+ }
39
+ else if (argv['output-path']) {
28
40
  (0, node_fs_1.writeFileSync)(argv['output-path'], JSON.stringify(result, null, 2));
29
41
  }
30
42
  else {
31
43
  console.dir(result, { depth: null, colors: false });
32
44
  }
33
- await context.close();
34
- setTimeout(() => process.exit(0), 50);
45
+ process.exit(0);
35
46
  };
36
47
  exports.runBlock = runBlock;
package/dist/setup.js CHANGED
@@ -30,7 +30,7 @@ const setup = async (argv) => {
30
30
  if (argv.block == null) {
31
31
  blockHash = await api.getBlockHash();
32
32
  }
33
- else if (Number.isInteger(argv.block)) {
33
+ else if (Number.isInteger(+argv.block)) {
34
34
  blockHash = await api.getBlockHash(Number(argv.block));
35
35
  }
36
36
  else {
@@ -42,7 +42,12 @@ const setup = async (argv) => {
42
42
  db = await (0, db_1.openDb)(argv.db);
43
43
  }
44
44
  const header = await api.getHeader(blockHash);
45
- const inherents = new inherent_1.InherentProviders(new inherent_1.SetTimestamp(), [new inherent_1.SetValidationData(), new inherent_1.ParaInherentEnter()]);
45
+ const inherents = new inherent_1.InherentProviders(new inherent_1.SetTimestamp(), [
46
+ new inherent_1.SetValidationData(),
47
+ new inherent_1.ParaInherentEnter(),
48
+ new inherent_1.SetNimbusAuthorInherent(),
49
+ new inherent_1.SetBabeRandomness(),
50
+ ]);
46
51
  const chain = new blockchain_1.Blockchain({
47
52
  api,
48
53
  buildBlockMode: argv['build-block-mode'],
@@ -0,0 +1,17 @@
1
+ import '@polkadot/types-codec';
2
+ import { Block } from '../blockchain/block';
3
+ import { HexString } from '@polkadot/util/types';
4
+ import { StorageEntry } from '@polkadot/types/primitive/types';
5
+ import { StorageKey } from '@polkadot/types';
6
+ export declare const decodeKey: (block: Block, key: HexString) => Promise<{
7
+ storage?: StorageEntry | undefined;
8
+ decodedKey?: StorageKey<import("@polkadot/types-codec/types").AnyTuple> | undefined;
9
+ }>;
10
+ export declare const decodeKeyValue: (block: Block, key: HexString, value?: HexString | null) => Promise<{
11
+ [x: string]: `0x${string}` | null | undefined;
12
+ } | {
13
+ [x: string]: {
14
+ [x: string]: import("@polkadot/types-codec/types").AnyJson;
15
+ };
16
+ }>;
17
+ export declare const decodeStorageDiff: (block: Block, diff: [HexString, HexString | null][]) => Promise<({} | undefined)[]>;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.decodeStorageDiff = exports.decodeKeyValue = exports.decodeKey = void 0;
7
+ require("@polkadot/types-codec");
8
+ const util_crypto_1 = require("@polkadot/util-crypto");
9
+ const jsondiffpatch_1 = require("jsondiffpatch");
10
+ const util_1 = require("@polkadot/util");
11
+ const lodash_1 = __importDefault(require("lodash"));
12
+ const diffPatcher = (0, jsondiffpatch_1.create)({
13
+ array: { detectMove: false },
14
+ textDiff: { minLength: Number.MAX_VALUE }, // skip text diff
15
+ });
16
+ const cache = {};
17
+ const getStorageEntry = async (block, key) => {
18
+ for (const [prefix, storageEntry] of Object.entries(cache)) {
19
+ if (key.startsWith(prefix))
20
+ return storageEntry;
21
+ }
22
+ const meta = await block.meta;
23
+ for (const module of Object.values(meta.query)) {
24
+ for (const storage of Object.values(module)) {
25
+ const keyPrefix = (0, util_1.u8aToHex)(storage.keyPrefix());
26
+ if (key.startsWith(keyPrefix)) {
27
+ cache[keyPrefix] = storage;
28
+ return storage;
29
+ }
30
+ }
31
+ }
32
+ throw new Error(`Cannot find key ${key}`);
33
+ };
34
+ const decodeKey = async (block, key) => {
35
+ const meta = await block.meta;
36
+ const storage = await getStorageEntry(block, key).catch(() => undefined);
37
+ const decodedKey = meta.registry.createType('StorageKey', key);
38
+ if (storage) {
39
+ decodedKey.setMeta(storage.meta);
40
+ return { storage, decodedKey };
41
+ }
42
+ return {};
43
+ };
44
+ exports.decodeKey = decodeKey;
45
+ const decodeKeyValue = async (block, key, value) => {
46
+ const meta = await block.meta;
47
+ const { storage, decodedKey } = await (0, exports.decodeKey)(block, key);
48
+ if (!storage || !decodedKey) {
49
+ return { [key]: value };
50
+ }
51
+ const decodeValue = () => {
52
+ if (!value)
53
+ return null;
54
+ if (storage.section === 'substrate' && storage.method === 'code') {
55
+ return `:code blake2_256 ${(0, util_crypto_1.blake2AsHex)(value, 256)} (${(0, util_1.hexToU8a)(value).length} bytes)`;
56
+ }
57
+ return meta.registry.createType(decodedKey.outputType, (0, util_1.hexToU8a)(value)).toHuman();
58
+ };
59
+ switch (decodedKey.args.length) {
60
+ case 2: {
61
+ return {
62
+ [storage.section]: {
63
+ [storage.method]: {
64
+ [decodedKey.args[0].toString()]: {
65
+ [decodedKey.args[1].toString()]: decodeValue(),
66
+ },
67
+ },
68
+ },
69
+ };
70
+ }
71
+ case 1: {
72
+ return {
73
+ [storage.section]: {
74
+ [storage.method]: {
75
+ [decodedKey.args[0].toString()]: decodeValue(),
76
+ },
77
+ },
78
+ };
79
+ }
80
+ default:
81
+ return {
82
+ [storage.section]: {
83
+ [storage.method]: decodeValue(),
84
+ },
85
+ };
86
+ }
87
+ };
88
+ exports.decodeKeyValue = decodeKeyValue;
89
+ const decodeStorageDiff = async (block, diff) => {
90
+ const parent = await block.parentBlock;
91
+ if (!parent)
92
+ throw new Error('Cannot find parent block');
93
+ const oldState = {};
94
+ const newState = {};
95
+ for (const [key, value] of diff) {
96
+ lodash_1.default.merge(oldState, await (0, exports.decodeKeyValue)(parent, key, (await parent.get(key))));
97
+ lodash_1.default.merge(newState, await (0, exports.decodeKeyValue)(block, key, value));
98
+ }
99
+ return [oldState, newState, diffPatcher.diff(oldState, newState)];
100
+ };
101
+ exports.decodeStorageDiff = decodeStorageDiff;
@@ -0,0 +1,4 @@
1
+ import { Block } from '../blockchain/block';
2
+ import { HexString } from '@polkadot/util/types';
3
+ export declare const generateHtmlDiff: (block: Block, diff: [HexString, HexString | null][]) => Promise<string>;
4
+ export declare const generateHtmlDiffPreviewFile: (block: Block, diff: [HexString, HexString | null][], filename: string) => Promise<string>;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateHtmlDiffPreviewFile = exports.generateHtmlDiff = void 0;
4
+ const decoder_1 = require("./decoder");
5
+ const node_fs_1 = require("node:fs");
6
+ const lodash_1 = require("lodash");
7
+ const generateHtmlDiff = async (block, diff) => {
8
+ const [left, _right, delta] = await (0, decoder_1.decodeStorageDiff)(block, diff);
9
+ const htmlTemplate = (0, node_fs_1.readFileSync)('./template/diff.html', 'utf-8');
10
+ return (0, lodash_1.template)(htmlTemplate)({ left: JSON.stringify(left), delta: JSON.stringify(delta) });
11
+ };
12
+ exports.generateHtmlDiff = generateHtmlDiff;
13
+ const generateHtmlDiffPreviewFile = async (block, diff, filename) => {
14
+ const html = await (0, exports.generateHtmlDiff)(block, diff);
15
+ (0, node_fs_1.mkdirSync)('./preview', { recursive: true });
16
+ const filePath = `./preview/${filename}.html`;
17
+ (0, node_fs_1.writeFileSync)(filePath, html);
18
+ return filePath;
19
+ };
20
+ exports.generateHtmlDiffPreviewFile = generateHtmlDiffPreviewFile;
@@ -0,0 +1 @@
1
+ export declare const openHtml: (filePath: string) => void;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.openHtml = void 0;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const openHtml = (filePath) => {
6
+ const start = process.platform == 'darwin' ? 'open' : process.platform == 'win32' ? 'start' : 'xdg-open';
7
+ (0, node_child_process_1.execSync)(start + ' ' + filePath);
8
+ };
9
+ exports.openHtml = openHtml;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acala-network/chopsticks",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "author": "Bryan Chen <xlchen1291@gmail.com>",
@@ -24,10 +24,12 @@
24
24
  "test:dev": "LOG_LEVEL=trace vitest --inspect",
25
25
  "dev": "LOG_LEVEL=trace ts-node-dev --transpile-only --inspect --notify=false src/index.ts -- dev --config=configs/dev.yml",
26
26
  "dev:karura": "ts-node-dev --transpile-only --inspect --notify=false src/index.ts -- dev --config=configs/karura.yml",
27
- "dev:acala": "ts-node-dev --transpile-only --inspect --notify=false src/index.ts -- dev --config=configs/acala.yml"
27
+ "dev:acala": "ts-node-dev --transpile-only --inspect --notify=false src/index.ts -- dev --config=configs/acala.yml",
28
+ "dev:moonriver": "ts-node-dev --transpile-only --inspect --notify=false src/index.ts -- dev --config=configs/moonriver.yml",
29
+ "dev:moonbeam": "ts-node-dev --transpile-only --inspect --notify=false src/index.ts -- dev --config=configs/moonbeam.yml"
28
30
  },
29
31
  "dependencies": {
30
- "@acala-network/chopsticks-executor": "0.2.3",
32
+ "@acala-network/chopsticks-executor": "0.3.0",
31
33
  "@polkadot/api": "^9.10.1",
32
34
  "@polkadot/rpc-provider": "^9.10.1",
33
35
  "@polkadot/types": "^9.10.1",
@@ -37,6 +39,7 @@
37
39
  "@polkadot/util-crypto": "^10.2.1",
38
40
  "axios": "^1.2.2",
39
41
  "js-yaml": "^4.1.0",
42
+ "jsondiffpatch": "^0.4.1",
40
43
  "lodash": "^4.17.21",
41
44
  "pino": "^8.7.0",
42
45
  "pino-pretty": "^9.1.1",
@@ -1,2 +0,0 @@
1
- import '@polkadot/types-codec';
2
- export declare const decodeKey: (argv: any) => Promise<void>;
@@ -1,24 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.decodeKey = void 0;
4
- require("@polkadot/types-codec");
5
- const util_1 = require("@polkadot/util");
6
- const setup_1 = require("./setup");
7
- const decodeKey = async (argv) => {
8
- const context = await (0, setup_1.setup)(argv);
9
- const key = argv.key;
10
- const meta = await context.chain.head.meta;
11
- outer: for (const module of Object.values(meta.query)) {
12
- for (const storage of Object.values(module)) {
13
- const keyPrefix = (0, util_1.u8aToHex)(storage.keyPrefix());
14
- if (key.startsWith(keyPrefix)) {
15
- const decodedKey = meta.registry.createType('StorageKey', key);
16
- decodedKey.setMeta(storage.meta);
17
- console.log(`${storage.section}.${storage.method}`, decodedKey.args.map((x) => JSON.stringify(x.toHuman())).join(', '));
18
- break outer;
19
- }
20
- }
21
- }
22
- setTimeout(() => process.exit(0), 50);
23
- };
24
- exports.decodeKey = decodeKey;