@acala-network/chopsticks 0.3.11 → 0.3.12

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.
@@ -1,6 +1,8 @@
1
+ import { Header, TransactionValidityError } from '@polkadot/types/interfaces';
1
2
  import { Block, TaskCallResponse } from './block';
2
3
  import { HexString } from '@polkadot/util/types';
3
- export declare const buildBlock: (head: Block, inherents: HexString[], extrinsics: HexString[]) => Promise<[Block, HexString[]]>;
4
+ export declare const newHeader: (head: Block) => Promise<Header>;
5
+ export declare const buildBlock: (head: Block, inherents: HexString[], extrinsics: HexString[], onApplyExtrinsicError: (extrinsic: HexString, error: TransactionValidityError) => void) => Promise<[Block, HexString[]]>;
4
6
  export declare const dryRunExtrinsic: (head: Block, inherents: HexString[], extrinsic: HexString | {
5
7
  call: HexString;
6
8
  address: string;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dryRunInherents = exports.dryRunExtrinsic = exports.buildBlock = void 0;
3
+ exports.dryRunInherents = exports.dryRunExtrinsic = exports.buildBlock = exports.newHeader = void 0;
4
4
  const block_1 = require("./block");
5
+ const util_crypto_1 = require("@polkadot/util-crypto");
5
6
  const util_1 = require("@polkadot/util");
6
7
  const utils_1 = require("../utils");
7
8
  const logger_1 = require("../logger");
@@ -84,6 +85,7 @@ const newHeader = async (head) => {
84
85
  });
85
86
  return header;
86
87
  };
88
+ exports.newHeader = newHeader;
87
89
  const initNewBlock = async (head, header, inherents) => {
88
90
  const blockNumber = header.number.toNumber();
89
91
  const hash = `0x${Math.round(Math.random() * 100000000)
@@ -110,22 +112,34 @@ const initNewBlock = async (head, header, inherents) => {
110
112
  }
111
113
  return newBlock;
112
114
  };
113
- const buildBlock = async (head, inherents, extrinsics) => {
115
+ const buildBlock = async (head, inherents, extrinsics, onApplyExtrinsicError) => {
114
116
  const registry = await head.registry;
115
- const header = await newHeader(head);
117
+ const header = await (0, exports.newHeader)(head);
116
118
  const newBlock = await initNewBlock(head, header, inherents);
117
119
  logger.info({
118
120
  number: newBlock.number,
119
121
  extrinsicsCount: extrinsics.length,
120
122
  tempHash: newBlock.hash,
121
- }, `Building block #${newBlock.number.toLocaleString()}`);
123
+ }, `Try building block #${newBlock.number.toLocaleString()}`);
122
124
  const pendingExtrinsics = [];
125
+ const includedExtrinsic = [];
123
126
  // apply extrinsics
124
127
  for (const extrinsic of extrinsics) {
125
128
  try {
126
- const { storageDiff } = await newBlock.call('BlockBuilder_apply_extrinsic', [extrinsic]);
129
+ const { result, storageDiff } = await newBlock.call('BlockBuilder_apply_extrinsic', [extrinsic]);
130
+ const outcome = registry.createType('ApplyExtrinsicResult', result);
131
+ if (outcome.isErr) {
132
+ if (outcome.asErr.isInvalid && outcome.asErr.asInvalid.isFuture) {
133
+ pendingExtrinsics.push(extrinsic);
134
+ }
135
+ else {
136
+ onApplyExtrinsicError(extrinsic, outcome.asErr);
137
+ }
138
+ continue;
139
+ }
127
140
  newBlock.pushStorageLayer().setAll(storageDiff);
128
141
  logger.trace((0, logger_1.truncate)(storageDiff), 'Applied extrinsic');
142
+ includedExtrinsic.push(extrinsic);
129
143
  }
130
144
  catch (e) {
131
145
  logger.info('Failed to apply extrinsic %o %s', e, e);
@@ -140,23 +154,28 @@ const buildBlock = async (head, inherents, extrinsics) => {
140
154
  }
141
155
  const blockData = registry.createType('Block', {
142
156
  header,
143
- extrinsics,
157
+ extrinsics: includedExtrinsic,
144
158
  });
145
159
  const storageDiff = await newBlock.storageDiff();
146
160
  logger.trace(Object.entries(storageDiff).map(([key, value]) => [key, (0, logger_1.truncate)(value)]), 'Final block');
147
161
  const finalBlock = new block_1.Block(head.chain, newBlock.number, blockData.hash.toHex(), head, {
148
162
  header,
149
- extrinsics: [...inherents, ...extrinsics],
163
+ extrinsics: [...inherents, ...includedExtrinsic],
150
164
  storage: head.storage,
151
165
  storageDiff,
152
166
  });
153
- logger.info({ hash: finalBlock.hash, number: newBlock.number }, `Block built #${newBlock.number.toLocaleString()} hash ${finalBlock.hash}`);
167
+ logger.info({
168
+ hash: finalBlock.hash,
169
+ extrinsics: includedExtrinsic.map((x) => (0, util_crypto_1.blake2AsHex)(x, 256)),
170
+ pendingExtrinsics: pendingExtrinsics.length,
171
+ number: newBlock.number,
172
+ }, `Block built #${newBlock.number.toLocaleString()} hash ${finalBlock.hash}`);
154
173
  return [finalBlock, pendingExtrinsics];
155
174
  };
156
175
  exports.buildBlock = buildBlock;
157
176
  const dryRunExtrinsic = async (head, inherents, extrinsic) => {
158
177
  const registry = await head.registry;
159
- const header = await newHeader(head);
178
+ const header = await (0, exports.newHeader)(head);
160
179
  const newBlock = await initNewBlock(head, header, inherents);
161
180
  if (typeof extrinsic !== 'string') {
162
181
  if (!head.chain.mockSignatureHost) {
@@ -185,7 +204,7 @@ const dryRunExtrinsic = async (head, inherents, extrinsic) => {
185
204
  };
186
205
  exports.dryRunExtrinsic = dryRunExtrinsic;
187
206
  const dryRunInherents = async (head, inherents) => {
188
- const header = await newHeader(head);
207
+ const header = await (0, exports.newHeader)(head);
189
208
  const newBlock = await initNewBlock(head, header, inherents);
190
209
  return Object.entries(await newBlock.storageDiff());
191
210
  };
@@ -4,7 +4,7 @@ import { HexString } from '@polkadot/util/types';
4
4
  import { RegisteredTypes } from '@polkadot/types/types';
5
5
  import { Api } from '../api';
6
6
  import { Block } from './block';
7
- import { BuildBlockMode, BuildBlockParams, HorizontalMessage } from './txpool';
7
+ import { BuildBlockMode, BuildBlockParams, HorizontalMessage, TxPool } from './txpool';
8
8
  import { HeadState } from './head-state';
9
9
  import { InherentProvider } from './inherent';
10
10
  export interface Options {
@@ -31,7 +31,7 @@ export declare class Blockchain {
31
31
  readonly headState: HeadState;
32
32
  constructor({ api, buildBlockMode, inherentProvider, db, header, mockSignatureHost, allowUnresolvedImports, registeredTypes, }: Options);
33
33
  get head(): Block;
34
- get pendingExtrinsics(): HexString[];
34
+ get txPool(): TxPool;
35
35
  getBlockAt(number?: number): Promise<Block | undefined>;
36
36
  getBlock(hash?: HexString): Promise<Block | undefined>;
37
37
  unregisterBlock(block: Block): void;
@@ -47,4 +47,5 @@ export declare class Blockchain {
47
47
  storageDiff: [HexString, HexString | null][];
48
48
  }>;
49
49
  dryRunHrmp(hrmp: Record<number, HorizontalMessage[]>): Promise<[HexString, HexString | null][]>;
50
+ getInherents(): Promise<HexString[]>;
50
51
  }
@@ -42,8 +42,8 @@ class Blockchain {
42
42
  get head() {
43
43
  return this.#head;
44
44
  }
45
- get pendingExtrinsics() {
46
- return this.#txpool.pendingExtrinsics;
45
+ get txPool() {
46
+ return this.#txpool;
47
47
  }
48
48
  async getBlockAt(number) {
49
49
  if (number === undefined) {
@@ -115,7 +115,7 @@ class Blockchain {
115
115
  this.#txpool.submitExtrinsic(extrinsic);
116
116
  return (0, util_crypto_1.blake2AsHex)(extrinsic, 256);
117
117
  }
118
- throw new Error(`Extrinsic is invalid: ${validity.asErr.toString()}`);
118
+ throw validity.asErr;
119
119
  }
120
120
  async newBlock(params) {
121
121
  await this.#txpool.buildBlock(params);
@@ -142,5 +142,10 @@ class Blockchain {
142
142
  const inherents = await this.#inherentProvider.createInherents(head, { horizontalMessages: hrmp });
143
143
  return (0, block_builder_1.dryRunInherents)(head, inherents);
144
144
  }
145
+ async getInherents() {
146
+ await this.api.isReady;
147
+ const inherents = await this.#inherentProvider.createInherents(this.head);
148
+ return inherents;
149
+ }
145
150
  }
146
151
  exports.Blockchain = Blockchain;
@@ -1,7 +1,10 @@
1
+ /// <reference types="node" />
2
+ import { EventEmitter } from 'node:stream';
1
3
  import { HexString } from '@polkadot/util/types';
2
4
  import { Block } from './block';
3
5
  import { Blockchain } from '.';
4
6
  import { InherentProvider } from './inherent';
7
+ export declare const APPLY_EXTRINSIC_ERROR = "TxPool::ApplyExtrinsicError";
5
8
  export declare enum BuildBlockMode {
6
9
  Batch = 0,
7
10
  Instant = 1,
@@ -23,6 +26,7 @@ export interface BuildBlockParams {
23
26
  }
24
27
  export declare class TxPool {
25
28
  #private;
29
+ readonly event: EventEmitter;
26
30
  constructor(chain: Blockchain, inherentProvider: InherentProvider, mode?: BuildBlockMode);
27
31
  get pendingExtrinsics(): HexString[];
28
32
  submitExtrinsic(extrinsic: HexString): void;
@@ -3,11 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.TxPool = exports.BuildBlockMode = void 0;
6
+ exports.TxPool = exports.BuildBlockMode = exports.APPLY_EXTRINSIC_ERROR = void 0;
7
7
  const rxjs_1 = require("rxjs");
8
+ const node_stream_1 = require("node:stream");
8
9
  const operators_1 = require("rxjs/operators");
9
10
  const lodash_1 = __importDefault(require("lodash"));
10
11
  const block_builder_1 = require("./block-builder");
12
+ exports.APPLY_EXTRINSIC_ERROR = 'TxPool::ApplyExtrinsicError';
11
13
  var BuildBlockMode;
12
14
  (function (BuildBlockMode) {
13
15
  BuildBlockMode[BuildBlockMode["Batch"] = 0] = "Batch";
@@ -19,6 +21,7 @@ class TxPool {
19
21
  #pool = [];
20
22
  #mode;
21
23
  #inherentProvider;
24
+ event = new node_stream_1.EventEmitter();
22
25
  #last;
23
26
  #lastBuildBlockPromise = Promise.resolve();
24
27
  constructor(chain, inherentProvider, mode = BuildBlockMode.Batch) {
@@ -62,7 +65,9 @@ class TxPool {
62
65
  const head = this.#chain.head;
63
66
  const extrinsics = this.#pool.splice(0);
64
67
  const inherents = await this.#inherentProvider.createInherents(head, params?.inherent);
65
- const [newBlock, pendingExtrinsics] = await (0, block_builder_1.buildBlock)(head, inherents, extrinsics);
68
+ const [newBlock, pendingExtrinsics] = await (0, block_builder_1.buildBlock)(head, inherents, extrinsics, (extrinsic, error) => {
69
+ this.event.emit(exports.APPLY_EXTRINSIC_ERROR, [extrinsic, error]);
70
+ });
66
71
  this.#pool.push(...pendingExtrinsics);
67
72
  await this.#chain.setHead(newBlock);
68
73
  }
package/dist/cli.js CHANGED
@@ -12,6 +12,7 @@ const _1 = require(".");
12
12
  const schema_1 = require("./schema");
13
13
  const decoder_1 = require("./utils/decoder");
14
14
  const dry_run_1 = require("./dry-run");
15
+ const dry_run_preimage_1 = require("./dry-run-preimage");
15
16
  const utils_1 = require("./utils");
16
17
  const run_block_1 = require("./run-block");
17
18
  const processConfig = async (path) => {
@@ -78,12 +79,15 @@ const defaultOptions = {
78
79
  extrinsic: {
79
80
  desc: 'Extrinsic or call to dry run. If you pass call here then address is required to fake signature',
80
81
  string: true,
81
- required: true,
82
82
  },
83
83
  address: {
84
84
  desc: 'Address to fake sign extrinsic',
85
85
  string: true,
86
86
  },
87
+ preimage: {
88
+ desc: 'Preimage to dry run',
89
+ string: true,
90
+ },
87
91
  at: {
88
92
  desc: 'Block hash to dry run',
89
93
  string: true,
@@ -99,7 +103,13 @@ const defaultOptions = {
99
103
  desc: 'Open generated html',
100
104
  },
101
105
  }), async (argv) => {
102
- await (0, dry_run_1.dryRun)(await processArgv(argv));
106
+ const config = await processArgv(argv);
107
+ if (config.preimage) {
108
+ await (0, dry_run_preimage_1.dryRunPreimage)(config);
109
+ }
110
+ else {
111
+ await (0, dry_run_1.dryRun)(config);
112
+ }
103
113
  })
104
114
  .command('dev', 'Dev mode', (yargs) => yargs.options({
105
115
  ...defaultOptions,
@@ -0,0 +1,2 @@
1
+ import { Config } from './schema';
2
+ export declare const dryRunPreimage: (argv: Config) => Promise<never>;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dryRunPreimage = void 0;
4
+ const util_crypto_1 = require("@polkadot/util-crypto");
5
+ const util_1 = require("@polkadot/util");
6
+ const logger_1 = require("./logger");
7
+ const generate_html_diff_1 = require("./utils/generate-html-diff");
8
+ const block_builder_1 = require("./blockchain/block-builder");
9
+ const open_html_1 = require("./utils/open-html");
10
+ const executor_1 = require("./executor");
11
+ const set_storage_1 = require("./utils/set-storage");
12
+ const setup_1 = require("./setup");
13
+ const dryRunPreimage = async (argv) => {
14
+ const context = await (0, setup_1.setup)(argv);
15
+ const extrinsic = argv['preimage'];
16
+ const block = context.chain.head;
17
+ const registry = await block.registry;
18
+ const header = await (0, block_builder_1.newHeader)(block);
19
+ const data = (0, util_1.hexToU8a)(extrinsic);
20
+ const hash = (0, util_crypto_1.blake2AsHex)(data, 256);
21
+ await (0, set_storage_1.setStorage)(context.chain, {
22
+ Preimage: {
23
+ PreimageFor: [[[[hash, data.byteLength]], extrinsic]],
24
+ StatusFor: [
25
+ [
26
+ [hash],
27
+ {
28
+ Requested: {
29
+ count: 1,
30
+ len: data.byteLength,
31
+ },
32
+ },
33
+ ],
34
+ ],
35
+ },
36
+ Scheduler: {
37
+ Agenda: [
38
+ [
39
+ [block.number + 1],
40
+ [
41
+ {
42
+ maybeId: '0x64656d6f637261633a0000000000000000000000000000000000000000000000',
43
+ priority: 63,
44
+ call: {
45
+ Lookup: {
46
+ hash: hash,
47
+ len: data.byteLength,
48
+ },
49
+ },
50
+ origin: { system: { Root: null } },
51
+ },
52
+ ],
53
+ ],
54
+ ],
55
+ Lookup: [[['0x64656d6f637261633a0000000000000000000000000000000000000000000000'], [block.number + 1, 0]]],
56
+ },
57
+ });
58
+ const calls = [['Core_initialize_block', [header.toHex()]]];
59
+ for (const inherent of await block.chain.getInherents()) {
60
+ calls.push(['BlockBuilder_apply_extrinsic', [inherent]]);
61
+ }
62
+ calls.push(['BlockBuilder_finalize_block', []]);
63
+ logger_1.defaultLogger.info({ preimage: registry.createType('Call', data).toHuman() }, 'Dry run preimage');
64
+ const result = await (0, executor_1.runTask)({
65
+ wasm: await block.wasm,
66
+ calls,
67
+ storage: [],
68
+ mockSignatureHost: false,
69
+ allowUnresolvedImports: false,
70
+ }, (0, executor_1.taskHandler)(block));
71
+ if (result.Error) {
72
+ throw new Error(result.Error);
73
+ }
74
+ const filePath = await (0, generate_html_diff_1.generateHtmlDiffPreviewFile)(block, result.Call.storageDiff, hash);
75
+ console.log(`Generated preview ${filePath}`);
76
+ if (argv['open']) {
77
+ (0, open_html_1.openHtml)(filePath);
78
+ }
79
+ // if dry-run preimage has extrinsic arguments then dry-run extrinsic
80
+ // this is usefull to test something after preimage is applied
81
+ if (argv['extrinsic']) {
82
+ await context.chain.newBlock();
83
+ const input = argv['address'] ? { call: argv['extrinsic'], address: argv['address'] } : argv['extrinsic'];
84
+ const { outcome, storageDiff } = await context.chain.dryRunExtrinsic(input);
85
+ if (outcome.isErr) {
86
+ throw new Error(outcome.asErr.toString());
87
+ }
88
+ else {
89
+ logger_1.defaultLogger.info(outcome.toHuman(), 'dry_run_outcome');
90
+ }
91
+ const filePath = await (0, generate_html_diff_1.generateHtmlDiffPreviewFile)(context.chain.head, storageDiff, (0, util_crypto_1.blake2AsHex)(argv['extrinsic'], 256));
92
+ console.log(`Generated preview ${filePath}`);
93
+ if (argv['open']) {
94
+ (0, open_html_1.openHtml)(filePath);
95
+ }
96
+ }
97
+ process.exit(0);
98
+ };
99
+ exports.dryRunPreimage = dryRunPreimage;
package/dist/logger.js CHANGED
@@ -11,10 +11,13 @@ exports.defaultLogger = (0, pino_1.default)({
11
11
  target: 'pino-pretty',
12
12
  },
13
13
  });
14
- const truncate = (val) => {
14
+ const innerTruncate = (level = 0) => (val) => {
15
15
  if (val == null) {
16
16
  return val;
17
17
  }
18
+ if (level > 5) {
19
+ return '( Too Deep )';
20
+ }
18
21
  switch (typeof val) {
19
22
  case 'string':
20
23
  if (val.length > 66) {
@@ -25,11 +28,12 @@ const truncate = (val) => {
25
28
  }
26
29
  case 'object':
27
30
  if (Array.isArray(val)) {
28
- return val.map(exports.truncate);
31
+ return val.map(innerTruncate(level + 1));
29
32
  }
30
- return Object.fromEntries(Object.entries(val).map(([k, v]) => [k, (0, exports.truncate)(v)]));
33
+ return Object.fromEntries(Object.entries(val.toJSON ? val.toJSON() : val).map(([k, v]) => [k, innerTruncate(level + 1)(v)]));
31
34
  default:
32
35
  return val;
33
36
  }
34
37
  };
38
+ const truncate = (val) => innerTruncate(0)(val);
35
39
  exports.truncate = truncate;
@@ -1,19 +1,35 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const txpool_1 = require("../../blockchain/txpool");
3
4
  const shared_1 = require("../../rpc/shared");
4
5
  const logger_1 = require("../../logger");
5
6
  const logger = logger_1.defaultLogger.child({ name: 'rpc-author' });
6
7
  const handlers = {
7
8
  author_submitExtrinsic: async (context, [extrinsic]) => {
8
9
  return context.chain.submitExtrinsic(extrinsic).catch((error) => {
9
- throw new shared_1.ResponseError(1, error.toString());
10
+ const code = error.isInvalid ? 1010 : 1011;
11
+ throw new shared_1.ResponseError(code, error.toString());
10
12
  });
11
13
  },
12
14
  author_submitAndWatchExtrinsic: async (context, [extrinsic], { subscribe, unsubscribe }) => {
13
15
  let update = (_block) => { };
14
16
  const id = context.chain.headState.subscribeHead((block) => update(block));
15
17
  const callback = subscribe('author_extrinsicUpdate', id, () => context.chain.headState.unsubscribeHead(id));
18
+ const onExtrinsicFail = ([failedExtrinsic, error]) => {
19
+ if (failedExtrinsic === extrinsic) {
20
+ callback(error.toJSON());
21
+ done(id);
22
+ }
23
+ };
24
+ context.chain.txPool.event.on(txpool_1.APPLY_EXTRINSIC_ERROR, onExtrinsicFail);
25
+ const done = (id) => {
26
+ context.chain.txPool.event.removeListener(txpool_1.APPLY_EXTRINSIC_ERROR, onExtrinsicFail);
27
+ unsubscribe(id);
28
+ };
16
29
  update = async (block) => {
30
+ const extrisnics = await block.extrinsics;
31
+ if (!extrisnics.includes(extrinsic))
32
+ return;
17
33
  logger.debug({ block: block.hash }, 'author_extrinsicUpdate');
18
34
  // for now just assume tx is always included on next block
19
35
  callback({
@@ -22,7 +38,7 @@ const handlers = {
22
38
  callback({
23
39
  Finalized: block.hash,
24
40
  });
25
- unsubscribe(id);
41
+ done(id);
26
42
  };
27
43
  context.chain
28
44
  .submitExtrinsic(extrinsic)
@@ -33,8 +49,8 @@ const handlers = {
33
49
  })
34
50
  .catch((error) => {
35
51
  logger.error({ error }, 'ExtrinsicFailed');
36
- callback({ Invalid: null });
37
- unsubscribe(id);
52
+ callback(error.toJSON());
53
+ done(id);
38
54
  });
39
55
  return id;
40
56
  },
@@ -42,7 +58,7 @@ const handlers = {
42
58
  unsubscribe(subid);
43
59
  },
44
60
  author_pendingExtrinsics: async (context) => {
45
- return context.chain.pendingExtrinsics;
61
+ return context.chain.txPool.pendingExtrinsics;
46
62
  },
47
63
  };
48
64
  exports.default = handlers;
@@ -1,5 +1,6 @@
1
1
  declare const _default: {
2
2
  chain_subscribeNewHeads: import("../shared").Handler;
3
3
  chain_unsubscribeNewHeads: import("../shared").Handler;
4
+ chain_unsubscribeFinalizedHeads: import("../shared").Handler;
4
5
  };
5
6
  export default _default;
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const shared_1 = require("../shared");
4
+ const processHeader = (header) => {
5
+ const res = header.toJSON();
6
+ res.number = '0x' + res.number.toString(16); // number is hex format
7
+ return res;
8
+ };
4
9
  const handlers = {
5
10
  chain_getBlockHash: async (context, [blockNumber]) => {
6
11
  const block = await context.chain.getBlockAt(blockNumber);
@@ -14,7 +19,7 @@ const handlers = {
14
19
  if (!block) {
15
20
  throw new shared_1.ResponseError(1, `Block ${hash} not found`);
16
21
  }
17
- return await block.header;
22
+ return processHeader(await block.header);
18
23
  },
19
24
  chain_getBlock: async (context, [hash]) => {
20
25
  const block = await context.chain.getBlock(hash);
@@ -37,7 +42,7 @@ const handlers = {
37
42
  const id = context.chain.headState.subscribeHead(() => update());
38
43
  const callback = subscribe('chain_newHead', id, () => context.chain.headState.unsubscribeHead(id));
39
44
  update = async () => {
40
- callback(await context.chain.head.header);
45
+ callback(processHeader(await context.chain.head.header));
41
46
  };
42
47
  update();
43
48
  return id;
@@ -45,9 +50,9 @@ const handlers = {
45
50
  chain_subscribeFinalizedHeads: async (context, _params, { subscribe }) => {
46
51
  let update = () => { };
47
52
  const id = context.chain.headState.subscribeHead(() => update());
48
- const callback = subscribe('chain_newFinalizedHead', id, () => context.chain.headState.unsubscribeHead(id));
53
+ const callback = subscribe('chain_finalizedHead', id, () => context.chain.headState.unsubscribeHead(id));
49
54
  update = async () => {
50
- callback(await context.chain.head.header);
55
+ callback(processHeader(await context.chain.head.header));
51
56
  };
52
57
  update();
53
58
  return id;
@@ -59,6 +64,7 @@ const handlers = {
59
64
  const alias = {
60
65
  chain_subscribeNewHeads: handlers.chain_subscribeNewHead,
61
66
  chain_unsubscribeNewHeads: handlers.chain_unsubscribeNewHead,
67
+ chain_unsubscribeFinalizedHeads: handlers.chain_unsubscribeNewHead,
62
68
  };
63
69
  exports.default = {
64
70
  ...handlers,
package/dist/server.js CHANGED
@@ -76,6 +76,7 @@ const createServer = async (handler, port) => {
76
76
  subscriptions[subid] = onCancel;
77
77
  return (data) => {
78
78
  if (subscriptions[subid]) {
79
+ logger.trace({ method, subid, data: (0, logger_1.truncate)(data) }, 'Subscription notification');
79
80
  send({
80
81
  jsonrpc: '2.0',
81
82
  method,
@@ -128,7 +129,11 @@ const createServer = async (handler, port) => {
128
129
  }, 'Received message');
129
130
  try {
130
131
  const resp = await handler(req, subscriptionManager);
131
- logger.trace('Sending response for request %o %o', req.id, req.method);
132
+ logger.trace({
133
+ id: req.id,
134
+ method: req.method,
135
+ result: (0, logger_1.truncate)(resp),
136
+ }, 'Sending response for request');
132
137
  send({
133
138
  id: req.id,
134
139
  jsonrpc: '2.0',
@@ -100,6 +100,10 @@ const decodeStorageDiff = async (block, diff) => {
100
100
  lodash_1.default.merge(oldState, await (0, exports.decodeKeyValue)(block, key, (await block.get(key))));
101
101
  lodash_1.default.merge(newState, await (0, exports.decodeKeyValue)(block, key, value));
102
102
  }
103
- return [oldState, newState, diffPatcher.diff(oldState, newState)];
103
+ const oldStateWithoutEvents = lodash_1.default.cloneDeep(oldState);
104
+ if (oldStateWithoutEvents['system']?.['events']) {
105
+ oldStateWithoutEvents['system']['events'] = [];
106
+ }
107
+ return [oldState, newState, diffPatcher.diff(oldStateWithoutEvents, newState)];
104
108
  };
105
109
  exports.decodeStorageDiff = decodeStorageDiff;
@@ -28,12 +28,14 @@ function objectToStorageItems(meta, storage) {
28
28
  throw Error(`Cannot find storage ${storageName} in pallet ${sectionName}`);
29
29
  if (storageEntry.meta.type.isPlain) {
30
30
  const key = new types_1.StorageKey(meta.registry, [storageEntry]);
31
- storageItems.push([key.toHex(), storage ? meta.registry.createType(key.outputType, storage).toHex(true) : null]);
31
+ const type = storageEntry.meta.modifier.isOptional ? `Option<${key.outputType}>` : key.outputType;
32
+ storageItems.push([key.toHex(), storage ? meta.registry.createType(type, storage).toHex() : null]);
32
33
  }
33
34
  else {
34
35
  for (const [keys, value] of storage) {
35
36
  const key = new types_1.StorageKey(meta.registry, [storageEntry, keys]);
36
- storageItems.push([key.toHex(), value ? meta.registry.createType(key.outputType, value).toHex(true) : null]);
37
+ const type = storageEntry.meta.modifier.isOptional ? `Option<${key.outputType}>` : key.outputType;
38
+ storageItems.push([key.toHex(), value ? meta.registry.createType(type, value).toHex() : null]);
37
39
  }
38
40
  }
39
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acala-network/chopsticks",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "author": "Bryan Chen <xlchen1291@gmail.com>",
@@ -29,7 +29,7 @@
29
29
  "dev:moonbeam": "ts-node-dev --transpile-only --inspect --notify=false src/cli.ts -- dev --config=configs/moonbeam.yml"
30
30
  },
31
31
  "dependencies": {
32
- "@acala-network/chopsticks-executor": "0.3.10",
32
+ "@acala-network/chopsticks-executor": "0.3.12",
33
33
  "@polkadot/api": "^9.12.1",
34
34
  "@polkadot/rpc-provider": "^9.12.1",
35
35
  "@polkadot/types": "^9.12.1",
@@ -57,8 +57,8 @@
57
57
  "@types/node": "^18.11.18",
58
58
  "@types/ws": "^8.5.4",
59
59
  "@types/yargs": "^17.0.20",
60
- "@typescript-eslint/eslint-plugin": "^5.49.0",
61
- "@typescript-eslint/parser": "^5.49.0",
60
+ "@typescript-eslint/eslint-plugin": "^5.50.0",
61
+ "@typescript-eslint/parser": "^5.50.0",
62
62
  "eslint": "^8.32.0",
63
63
  "eslint-config-prettier": "^8.6.0",
64
64
  "eslint-plugin-import": "^2.27.5",
@@ -13,7 +13,7 @@
13
13
 
14
14
  div#app {
15
15
  margin: 0 !important;
16
- padding: 10px !important;
16
+ padding: 0 !important;
17
17
  }
18
18
 
19
19
  .diff {
@@ -24,6 +24,36 @@
24
24
  line-height: 150%;
25
25
  }
26
26
 
27
+ .diff > button {
28
+ position: absolute;
29
+ display: none;
30
+ left: 50%;
31
+ top: 50%;
32
+ translate: -50% -50%;
33
+ background: #fff;
34
+ border: none;
35
+ border-radius: 50%;
36
+ padding: 10px;
37
+ cursor: pointer;
38
+ cursor: pointer;
39
+ opacity: 80%;
40
+ width: 40px;
41
+ height: 40px;
42
+ }
43
+
44
+ .diff > button > img {
45
+ width: 100%;
46
+ height: 100%;
47
+ }
48
+
49
+ .diff > button:hover {
50
+ opacity: 100%;
51
+ }
52
+
53
+ .diff:hover > button {
54
+ display: block;
55
+ }
56
+
27
57
  .diffWrap {
28
58
  position: relative;
29
59
  z-index: 1;
@@ -37,30 +67,36 @@
37
67
 
38
68
  .diffAdd {
39
69
  color: darkseagreen;
70
+ display: inline-flex;
40
71
  }
41
72
 
42
73
  .diffRemove {
43
74
  text-decoration: line-through;
44
75
  text-decoration-thickness: 1px;
45
76
  color: red;
77
+ display: inline-flex;
46
78
  }
47
79
 
48
80
  .diffUpdateFrom {
49
81
  text-decoration: line-through;
50
82
  text-decoration-thickness: 1px;
51
83
  color: red;
84
+ display: inline-flex;
52
85
  }
53
86
 
54
87
  .diffUpdateTo {
55
88
  color: darkseagreen;
89
+ display: inline-flex;
56
90
  }
57
91
 
58
92
  .diffUpdateArrow {
59
93
  color: #ccc;
60
94
  }
95
+
61
96
  .unchanged {
62
97
  color: #666;
63
98
  }
99
+
64
100
  .delta {
65
101
  color: #ccc;
66
102
  font-size: 12px;
@@ -94,76 +130,82 @@
94
130
  return { className: className.join(' ') }
95
131
  }
96
132
 
97
- function valueRenderer(raw, value, ...keys) {
98
- const modifyPath = keys.reverse().join('.')
99
- const removePath = keys.map(x => Number.isInteger(parseInt(x)) ? '_' + x : x).join('.')
100
- const isDelta = _.has(delta, modifyPath) || _.has(delta, removePath)
133
+ function valueRenderer(viewPartial) {
134
+ return function (raw, value, ...keys) {
135
+ const modifyPath = keys.reverse().join('.')
136
+ const removePath = keys.map(x => Number.isInteger(parseInt(x)) ? '_' + x : x).join('.')
137
+ const isDelta = _.has(delta, modifyPath) || _.has(delta, removePath)
101
138
 
102
- function renderSpan(name, body) {
103
- return (
104
- <span key={name} {...styling(['diff', name])}>
105
- {body}
106
- </span>
107
- );
108
- }
139
+ function renderSpan(name, body, raw) {
140
+ return (
141
+ <span key={name} {...styling(['diff', name])}>
142
+ {body}
143
+ {_.isObjectLike(raw) ? <button onClick={() => viewPartial({ [modifyPath]: raw })}><img src='data:image/svg+xml;base64,PCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4KDTwhLS0gVXBsb2FkZWQgdG86IFNWRyBSZXBvLCB3d3cuc3ZncmVwby5jb20sIFRyYW5zZm9ybWVkIGJ5OiBTVkcgUmVwbyBNaXhlciBUb29scyAtLT4KPHN2ZyBmaWxsPSIjMDAwMDAwIiBoZWlnaHQ9IjY0cHgiIHdpZHRoPSI2NHB4IiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjQyLjEzMyAyNDIuMTMzIiB4bWw6c3BhY2U9InByZXNlcnZlIiBzdHJva2U9IiMwMDAwMDAiPgoNPGcgaWQ9IlNWR1JlcG9fYmdDYXJyaWVyIiBzdHJva2Utd2lkdGg9IjAiLz4KDTxnIGlkPSJTVkdSZXBvX3RyYWNlckNhcnJpZXIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgoNPGcgaWQ9IlNWR1JlcG9faWNvbkNhcnJpZXIiPiA8ZyBpZD0iWE1MSURfMjVfIj4gPHBhdGggaWQ9IlhNTElEXzI2XyIgZD0iTTg5LjI0NywxMzEuNjczbC00Ny43MzIsNDcuNzNsLTE1LjkwOS0xNS45MWMtNC4yOS00LjI5MS0xMC43NDItNS41NzItMTYuMzQ3LTMuMjUyIEMzLjY1NCwxNjIuNTYzLDAsMTY4LjAzMywwLDE3NC4xdjUzLjAzMmMwLDguMjg0LDYuNzE2LDE1LDE1LDE1bDUzLjAzMywwLjAwMWMwLjAwNy0wLjAwMSwwLjAxMi0wLjAwMSwwLjAxOSwwIGM4LjI4NSwwLDE1LTYuNzE2LDE1LTE1YzAtNC4zNzctMS44NzUtOC4zMTYtNC44NjUtMTEuMDU5bC0xNS40NTgtMTUuNDU4bDQ3LjczLTQ3LjcyOWM1Ljg1OC01Ljg1OCw1Ljg1OC0xNS4zNTUsMC0yMS4yMTMgQzEwNC42MDMsMTI1LjgxNSw5NS4xMDQsMTI1LjgxNiw4OS4yNDcsMTMxLjY3M3oiLz4gPHBhdGggaWQ9IlhNTElEXzI4XyIgZD0iTTIyNy4xMzMsMEgxNzQuMWMtNi4wNjcsMC0xMS41MzYsMy42NTUtMTMuODU4LDkuMjZjLTIuMzIxLDUuNjA1LTEuMDM4LDEyLjA1NywzLjI1MiwxNi4zNDdsMTUuOTExLDE1LjkxMSBsLTQ3LjcyOSw0Ny43M2MtNS44NTgsNS44NTgtNS44NTgsMTUuMzU1LDAsMjEuMjEzYzIuOTI5LDIuOTI5LDYuNzY4LDQuMzkzLDEwLjYwNiw0LjM5M2MzLjgzOSwwLDcuNjc4LTEuNDY0LDEwLjYwNi00LjM5NCBsNDcuNzMtNDcuNzNsMTUuOTA5LDE1LjkxYzIuODY5LDIuODcsNi43MDYsNC4zOTQsMTAuNjA5LDQuMzk0YzEuOTMzLDAsMy44ODItMC4zNzMsNS43MzctMS4xNDIgYzUuNjA1LTIuMzIyLDkuMjYtNy43OTIsOS4yNi0xMy44NThWMTVDMjQyLjEzMyw2LjcxNiwyMzUuNDE3LDAsMjI3LjEzMywweiIvPiA8L2c+IDwvZz4KDTwvc3ZnPg==' /></button> : null}
144
+ </span>
145
+ );
146
+ }
109
147
 
110
- function renderDelta(value) {
111
- if (/^\d+(,\d+)*$/.test(value[0]) && /^\d+(,\d+)*$/.test(value[1])) {
112
- const oldValue = parseInt(value[0].replace(/,/g, ''))
113
- const newValue = parseInt(value[1].replace(/,/g, ''))
114
- if (oldValue > 0 && newValue > 0) {
115
- const delta = Number(newValue - oldValue)
116
- return (<span className="delta" >{delta > 0 ? '+' : ''}{delta.toLocaleString()}</span>)
148
+ function renderDelta(value) {
149
+ if (/^\d+(,\d+)*$/.test(value[0]) && /^\d+(,\d+)*$/.test(value[1])) {
150
+ const oldValue = parseInt(value[0].replace(/,/g, ''))
151
+ const newValue = parseInt(value[1].replace(/,/g, ''))
152
+ if (oldValue > 0 && newValue > 0) {
153
+ const delta = Number(newValue - oldValue)
154
+ return (<span className="delta" >{delta > 0 ? '+' : ''}{delta.toLocaleString()}</span>)
155
+ }
117
156
  }
118
157
  }
119
- }
120
158
 
121
- if (isDelta && Array.isArray(value)) {
122
- switch (value.length) {
123
- case 0:
124
- return (
125
- <span {...styling('diffWrap')}>
126
- {renderSpan('diff', '[]')}
127
- </span>
128
- )
129
- case 1:
130
- return (
131
- <span {...styling('diffWrap')}>
132
- {renderSpan(
133
- 'diffAdd',
134
- stringifyAndShrink(value[0])
135
- )}
136
- </span>
137
- );
138
- case 2:
139
- return (
140
- <span {...styling('diffWrap')}>
141
- {renderSpan(
142
- 'diffUpdateFrom',
143
- stringifyAndShrink(value[0])
144
- )}
145
- {renderSpan('diffUpdateArrow', ' => ')}
146
- {renderSpan(
147
- 'diffUpdateTo',
148
- stringifyAndShrink(value[1])
149
- )}
150
- {renderDelta(value)}
151
- </span>
152
- );
153
- case 3:
154
- return (
155
- <span {...styling('diffWrap')}>
156
- {renderSpan('diffRemove', stringifyAndShrink(value[0]))}
157
- </span>
158
- );
159
+ if (isDelta && Array.isArray(value)) {
160
+ switch (value.length) {
161
+ case 0:
162
+ return (
163
+ <span {...styling('diffWrap')}>
164
+ {renderSpan('diff', '[]')}
165
+ </span>
166
+ )
167
+ case 1:
168
+ return (
169
+ <span {...styling('diffWrap')}>
170
+ {renderSpan(
171
+ 'diffAdd',
172
+ stringifyAndShrink(value[0]),
173
+ value[0]
174
+ )}
175
+ </span>
176
+ );
177
+ case 2:
178
+ return (
179
+ <span {...styling('diffWrap')}>
180
+ {renderSpan(
181
+ 'diffUpdateFrom',
182
+ stringifyAndShrink(value[0]),
183
+ value[0]
184
+ )}
185
+ {renderSpan('diffUpdateArrow', ' => ')}
186
+ {renderSpan(
187
+ 'diffUpdateTo',
188
+ stringifyAndShrink(value[1]),
189
+ value[1]
190
+ )}
191
+ {renderDelta(value)}
192
+ </span>
193
+ );
194
+ case 3:
195
+ return (
196
+ <span {...styling('diffWrap')}>
197
+ {renderSpan('diffRemove', stringifyAndShrink(value[0]), value[0])}
198
+ </span>
199
+ );
200
+ }
159
201
  }
160
- }
161
202
 
162
- return (
163
- <span {...styling('diffWrap')}>
164
- {renderSpan('unchanged', stringifyAndShrink(value))}
165
- </span>
166
- );
203
+ return (
204
+ <span {...styling('diffWrap')}>
205
+ {renderSpan('unchanged', stringifyAndShrink(value), value)}
206
+ </span>
207
+ );
208
+ }
167
209
  };
168
210
 
169
211
  function prepareDelta(value) {
@@ -208,30 +250,49 @@
208
250
  class App extends React.Component {
209
251
  constructor(props) {
210
252
  super(props);
211
- this.state = { showUnchanged: false };
253
+ this.state = { showUnchanged: false, partial: null };
212
254
  }
213
255
 
214
256
  toggle = (e) => {
215
257
  this.setState(state => {
216
- return { showUnchanged: !state.showUnchanged }
258
+ return { ...state, showUnchanged: !state.showUnchanged }
259
+ })
260
+ }
261
+
262
+ viewPartial = (value) => {
263
+ this.setState(state => {
264
+ return { ...state, partial: _.isEqual(state.partial, value) ? null : value }
217
265
  })
218
266
  }
219
267
 
220
268
  render() {
221
269
  return (
222
270
  <div>
223
- <input type="checkbox" onChange={this.toggle} id="show_unchanged" />
224
- <label for="show_unchanged" style={{fontSize: '12px', color: 'white'}}>Show Unchanged</label>
225
- <ReactJsonTree.JSONTree
226
- theme={theme}
227
- invertTheme={false}
228
- data={this.state.showUnchanged ? _.merge(_.cloneDeep(left), delta): delta}
229
- valueRenderer={valueRenderer}
230
- postprocessValue={prepareDelta}
231
- isCustomNode={Array.isArray}
232
- shouldExpandNodeInitially={expandFirstLevel}
233
- hideRoot
234
- />
271
+ <div style={{ display: 'flex', flexDirection: 'row' }}>
272
+ <div style={{ flex: 1, padding: '0 10px', overflow: 'hidden', overflowY: 'scroll', height: '100vh' }}>
273
+ <input type="checkbox" onChange={this.toggle} id="show_unchanged" />
274
+ <label for="show_unchanged" style={{ fontSize: '12px', color: 'white' }}>Show Unchanged</label>
275
+ <ReactJsonTree.JSONTree
276
+ theme={theme}
277
+ invertTheme={false}
278
+ data={this.state.showUnchanged ? _.merge(_.cloneDeep(left), delta) : delta}
279
+ valueRenderer={valueRenderer(this.viewPartial)}
280
+ postprocessValue={prepareDelta}
281
+ isCustomNode={Array.isArray}
282
+ shouldExpandNodeInitially={expandFirstLevel}
283
+ hideRoot
284
+ />
285
+ </div>
286
+ {this.state.partial ? <div style={{ flex: 1, padding: '0 10px', overflow: 'hidden', overflowY: 'scroll', height: '100vh' }}>
287
+ <ReactJsonTree.JSONTree
288
+ theme={theme}
289
+ invertTheme={false}
290
+ data={this.state.partial}
291
+ shouldExpandNodeInitially={() => true}
292
+ hideRoot
293
+ />
294
+ </div> : null}
295
+ </div>
235
296
  </div>
236
297
  );
237
298
  }