@acala-network/chopsticks 0.10.2 → 0.12.0

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.
Files changed (47) hide show
  1. package/dist/cjs/cli.js +4 -1
  2. package/dist/cjs/context.d.ts +11 -0
  3. package/dist/cjs/context.js +14 -2
  4. package/dist/cjs/logger.d.ts +3 -0
  5. package/dist/cjs/logger.js +45 -0
  6. package/dist/cjs/plugins/dry-run/index.d.ts +3 -0
  7. package/dist/cjs/plugins/dry-run/rpc.d.ts +4 -4
  8. package/dist/cjs/plugins/fetch-storage/cli.d.ts +2 -0
  9. package/dist/cjs/plugins/fetch-storage/cli.js +49 -0
  10. package/dist/cjs/plugins/fetch-storage/index.d.ts +1 -0
  11. package/dist/cjs/plugins/fetch-storage/index.js +18 -0
  12. package/dist/cjs/plugins/index.d.ts +3 -1
  13. package/dist/cjs/plugins/index.js +30 -2
  14. package/dist/cjs/rpc/index.js +3 -3
  15. package/dist/cjs/schema/index.d.ts +3 -0
  16. package/dist/cjs/schema/index.js +4 -1
  17. package/dist/cjs/setup-with-server.d.ts +11 -0
  18. package/dist/cjs/setup-with-server.js +1 -0
  19. package/dist/cjs/utils/fetch-storages-worker.js +62 -0
  20. package/dist/cjs/utils/fetch-storages.d.ts +26 -0
  21. package/dist/cjs/utils/fetch-storages.js +213 -0
  22. package/dist/cjs/utils/index.d.ts +1 -0
  23. package/dist/cjs/utils/index.js +1 -0
  24. package/dist/esm/cli.js +5 -2
  25. package/dist/esm/context.d.ts +11 -0
  26. package/dist/esm/context.js +14 -2
  27. package/dist/esm/logger.d.ts +3 -0
  28. package/dist/esm/logger.js +34 -0
  29. package/dist/esm/plugins/dry-run/index.d.ts +3 -0
  30. package/dist/esm/plugins/dry-run/rpc.d.ts +4 -4
  31. package/dist/esm/plugins/fetch-storage/cli.d.ts +2 -0
  32. package/dist/esm/plugins/fetch-storage/cli.js +34 -0
  33. package/dist/esm/plugins/fetch-storage/index.d.ts +1 -0
  34. package/dist/esm/plugins/fetch-storage/index.js +1 -0
  35. package/dist/esm/plugins/index.d.ts +3 -1
  36. package/dist/esm/plugins/index.js +25 -2
  37. package/dist/esm/rpc/index.js +4 -4
  38. package/dist/esm/schema/index.d.ts +3 -0
  39. package/dist/esm/schema/index.js +4 -1
  40. package/dist/esm/setup-with-server.d.ts +11 -0
  41. package/dist/esm/setup-with-server.js +1 -0
  42. package/dist/esm/utils/fetch-storages-worker.js +12 -0
  43. package/dist/esm/utils/fetch-storages.d.ts +26 -0
  44. package/dist/esm/utils/fetch-storages.js +188 -0
  45. package/dist/esm/utils/index.d.ts +1 -0
  46. package/dist/esm/utils/index.js +1 -0
  47. package/package.json +3 -3
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: all[name]
9
+ });
10
+ }
11
+ _export(exports, {
12
+ fetchStorages: function() {
13
+ return fetchStorages;
14
+ },
15
+ getPrefixesFromConfig: function() {
16
+ return getPrefixesFromConfig;
17
+ },
18
+ logger: function() {
19
+ return logger;
20
+ },
21
+ startFetchStorageWorker: function() {
22
+ return startFetchStorageWorker;
23
+ }
24
+ });
25
+ const _chopstickscore = require("@acala-network/chopsticks-core");
26
+ const _api = require("@polkadot/api");
27
+ const _chopsticksdb = require("@acala-network/chopsticks-db");
28
+ const _rpcprovider = require("@polkadot/rpc-provider");
29
+ const _util = require("@polkadot/util");
30
+ const _types = require("@polkadot/types");
31
+ const _comlink = require("comlink");
32
+ const _utilcrypto = require("@polkadot/util-crypto");
33
+ const _lodash = /*#__PURE__*/ _interop_require_default(require("lodash"));
34
+ const _nodeadapter = /*#__PURE__*/ _interop_require_default(require("comlink/dist/umd/node-adapter.js"));
35
+ const _nodeworker_threads = /*#__PURE__*/ _interop_require_default(require("node:worker_threads"));
36
+ function _interop_require_default(obj) {
37
+ return obj && obj.__esModule ? obj : {
38
+ default: obj
39
+ };
40
+ }
41
+ const BATCH_SIZE = 1000;
42
+ const logger = _chopstickscore.defaultLogger.child({
43
+ name: 'fetch-storages'
44
+ });
45
+ const getHexKeyWithArgs = (meta, storage, args)=>{
46
+ const isPartialKey = args.length !== (meta.type.isPlain ? 0 : meta.type.asMap.hashers.length);
47
+ const hexKey = isPartialKey && storage.creator.iterKey ? storage.creator.iterKey(...args).toHex() : (0, _util.u8aToHex)((0, _util.compactStripLength)(storage.creator(...args))[1]);
48
+ return hexKey;
49
+ };
50
+ const checkPalletStorageByName = (meta, palletName, storageName)=>{
51
+ const pallet = meta.query[(0, _util.stringCamelCase)(palletName)];
52
+ if (!pallet) throw Error(`Cannot find pallet ${palletName}`);
53
+ let storage;
54
+ if (storageName) {
55
+ storage = pallet[(0, _util.stringCamelCase)(storageName)];
56
+ if (!storage) throw Error(`Cannot find storage ${storageName} in pallet ${palletName}`);
57
+ }
58
+ return {
59
+ pallet,
60
+ storage
61
+ };
62
+ };
63
+ const getPrefixesFromConfig = async (config, api)=>{
64
+ logger.debug({
65
+ config
66
+ }, 'received fetch-storage config');
67
+ const prefixes = [];
68
+ const metadata = await api.rpc.state.getMetadata();
69
+ const expandMeta = (0, _types.expandMetadata)(metadata.registry, metadata);
70
+ for (const item of config){
71
+ if (typeof item === 'string' && item.startsWith('0x')) {
72
+ // hex
73
+ prefixes.push(item);
74
+ } else if (typeof item === 'string' && !item.includes('.')) {
75
+ // pallet
76
+ checkPalletStorageByName(expandMeta, item);
77
+ prefixes.push((0, _utilcrypto.xxhashAsHex)(item, 128));
78
+ } else if (typeof item === 'string' && item.includes('.')) {
79
+ // pallet.storage
80
+ const [palletName, storageName] = item.split('.');
81
+ const { storage } = checkPalletStorageByName(expandMeta, palletName, storageName);
82
+ prefixes.push((0, _util.u8aToHex)(storage.keyPrefix()));
83
+ } else if (typeof item === 'object') {
84
+ // object cases
85
+ const [objectKey, objectVal] = Object.entries(item)[0];
86
+ if (typeof objectVal === 'string') {
87
+ // - System: Account
88
+ const { storage } = checkPalletStorageByName(expandMeta, objectKey, objectVal);
89
+ prefixes.push((0, _util.u8aToHex)(storage.keyPrefix()));
90
+ } else if (objectKey.includes('.') && Array.isArray(objectVal)) {
91
+ // - Pallet.Storage: [xxx, ...]
92
+ const [pallet, storage] = objectKey.split('.').map((x)=>(0, _util.stringCamelCase)(x));
93
+ checkPalletStorageByName(expandMeta, pallet, storage);
94
+ const storageEntry = api.query[pallet][storage];
95
+ const meta = storageEntry.creator.meta;
96
+ const args = objectVal;
97
+ const hexKey = getHexKeyWithArgs(meta, storageEntry, args);
98
+ prefixes.push(hexKey);
99
+ } else if (!Array.isArray(objectVal)) {
100
+ // - Tokens:
101
+ // Accounts: [xxx, ...]
102
+ const pallet = (0, _util.stringCamelCase)(objectKey);
103
+ const [storage, args] = Object.entries(objectVal)[0];
104
+ checkPalletStorageByName(expandMeta, pallet, storage);
105
+ const storageEntry = api.query[pallet][(0, _util.stringCamelCase)(storage)];
106
+ const meta = storageEntry.creator.meta;
107
+ const hexKey = getHexKeyWithArgs(meta, storageEntry, args);
108
+ prefixes.push(hexKey);
109
+ } else {
110
+ throw new Error(`Unsupported fetch-storage config: ${objectKey}.${objectVal}`);
111
+ }
112
+ }
113
+ }
114
+ logger.debug({
115
+ prefixes
116
+ }, 'prefixes from config');
117
+ return prefixes;
118
+ };
119
+ const fetchStorages = async ({ block, endpoint, dbPath, config })=>{
120
+ if (!endpoint) throw new Error('endpoint is required');
121
+ if (!block) throw new Error('block is required');
122
+ const provider = new _rpcprovider.WsProvider(endpoint, 3_000);
123
+ const apiPromise = new _api.ApiPromise({
124
+ provider
125
+ });
126
+ await apiPromise.isReady;
127
+ let blockHash;
128
+ if (block == null) {
129
+ const lastHdr = await apiPromise.rpc.chain.getHeader();
130
+ blockHash = lastHdr.hash.toString();
131
+ } else if (typeof block === 'string' && block.startsWith('0x')) {
132
+ blockHash = block;
133
+ } else if (Number.isInteger(+block)) {
134
+ blockHash = await apiPromise.rpc.chain.getBlockHash(Number(block)).then((h)=>h.toString());
135
+ } else {
136
+ throw new Error(`Invalid block number or hash: ${block}`);
137
+ }
138
+ const prefixesFromConfig = await getPrefixesFromConfig(config, apiPromise);
139
+ const uniqPrefixes = _lodash.default.uniq(prefixesFromConfig);
140
+ const processPrefixes = (prefixes)=>{
141
+ prefixes.sort();
142
+ const result = [];
143
+ for (const prefix of prefixes){
144
+ // check if the current prefix is not a prefix of any added prefix
145
+ if (!result.some((prev)=>prefix.startsWith(prev))) {
146
+ result.push(prefix);
147
+ }
148
+ }
149
+ return result;
150
+ };
151
+ const prefixes = processPrefixes(uniqPrefixes);
152
+ if (!prefixes.length) throw new Error('No prefixes to fetch');
153
+ const signedBlock = await apiPromise.rpc.chain.getBlock(blockHash);
154
+ const blockNumber = signedBlock.block.header.number.toNumber();
155
+ const chainName = (await apiPromise.rpc.system.chain()).toString();
156
+ const finalDbPath = dbPath ?? `db-${chainName}-${blockNumber}.sqlite`;
157
+ const api = new _chopstickscore.Api(provider);
158
+ const db = new _chopsticksdb.SqliteDatabase(finalDbPath);
159
+ logger.info(`Storages will be saved at ${finalDbPath}, use '--db=${finalDbPath} --block=${blockNumber}' to apply it later on`);
160
+ for (const prefix of prefixes){
161
+ let startKey = '0x';
162
+ let hasMorePages = true;
163
+ while(hasMorePages){
164
+ logger.debug({
165
+ prefix,
166
+ startKey
167
+ }, 'fetching keys');
168
+ const keysPage = await api.getKeysPaged(prefix, BATCH_SIZE, startKey, blockHash);
169
+ logger.debug({
170
+ prefix,
171
+ startKey
172
+ }, `fetched ${keysPage.length} keys`);
173
+ if (!keysPage.length) break;
174
+ startKey = keysPage[keysPage.length - 1];
175
+ if (!keysPage || keysPage.length < BATCH_SIZE) {
176
+ hasMorePages = false;
177
+ }
178
+ logger.debug({
179
+ prefix
180
+ }, 'fetching storages');
181
+ const storages = await api.getStorageBatch(prefix, keysPage, blockHash);
182
+ logger.debug({
183
+ prefix
184
+ }, `fetched ${storages.length} storages`);
185
+ const keyValueEntries = storages.map(([key, value])=>({
186
+ blockHash,
187
+ key,
188
+ value
189
+ }));
190
+ await db.saveStorageBatch(keyValueEntries);
191
+ logger.debug({
192
+ prefix
193
+ }, `saved ${storages.length} storages ✅`);
194
+ }
195
+ }
196
+ logger.info(`Storages are saved at ${finalDbPath}, use '--db=${finalDbPath} --block=${blockNumber}' to apply it`);
197
+ };
198
+ const startFetchStorageWorker = async (options)=>{
199
+ if (!options.config) return null;
200
+ const worker = new _nodeworker_threads.default.Worker(new URL('./fetch-storages-worker.js', require("url").pathToFileURL(__filename).toString()), {
201
+ name: 'fetch-storages-worker'
202
+ });
203
+ const workerApi = (0, _comlink.wrap)((0, _nodeadapter.default)(worker));
204
+ workerApi.startFetch(options);
205
+ const terminate = async ()=>{
206
+ workerApi[_comlink.releaseProxy]();
207
+ await worker.terminate();
208
+ };
209
+ return {
210
+ worker: workerApi,
211
+ terminate
212
+ };
213
+ };
@@ -2,3 +2,4 @@ export * from './decoder.js';
2
2
  export * from './generate-html-diff.js';
3
3
  export * from './open-html.js';
4
4
  export * from './override.js';
5
+ export * from './fetch-storages.js';
@@ -6,6 +6,7 @@ _export_star(require("./decoder.js"), exports);
6
6
  _export_star(require("./generate-html-diff.js"), exports);
7
7
  _export_star(require("./open-html.js"), exports);
8
8
  _export_star(require("./override.js"), exports);
9
+ _export_star(require("./fetch-storages.js"), exports);
9
10
  function _export_star(from, to) {
10
11
  Object.keys(from).forEach(function(k) {
11
12
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
package/dist/esm/cli.js CHANGED
@@ -4,10 +4,13 @@ import _ from 'lodash';
4
4
  import yargs from 'yargs';
5
5
  import { connectParachains, connectVertical, environment } from '@acala-network/chopsticks-core';
6
6
  import { configSchema, fetchConfig, getYargsOptions } from './schema/index.js';
7
- import { pluginExtendCli } from './plugins/index.js';
7
+ import { loadRpcMethodsByScripts, pluginExtendCli } from './plugins/index.js';
8
8
  import { setupWithServer } from './index.js';
9
9
  dotenvConfig();
10
10
  const processArgv = async (argv)=>{
11
+ if (argv.unsafeRpcMethods) {
12
+ await loadRpcMethodsByScripts(argv.unsafeRpcMethods);
13
+ }
11
14
  if (argv.config) {
12
15
  Object.assign(argv, _.defaults(argv, await fetchConfig(argv.config)));
13
16
  }
@@ -43,7 +46,7 @@ const commands = yargs(hideBin(process.argv)).scriptName('chopsticks').middlewar
43
46
  await connectVertical(relaychain, parachain);
44
47
  }
45
48
  }
46
- }).strict().help().alias('help', 'h').alias('version', 'v').alias('config', 'c').alias('endpoint', 'e').alias('port', 'p').alias('block', 'b').alias('import-storage', 's').alias('wasm-override', 'w').usage('Usage: $0 <command> [options]').example('$0', '-c acala');
49
+ }).strict().help().alias('help', 'h').alias('version', 'v').alias('config', 'c').alias('endpoint', 'e').alias('port', 'p').alias('block', 'b').alias('unsafe-rpc-methods', 'ur').alias('import-storage', 's').alias('wasm-override', 'w').usage('Usage: $0 <command> [options]').example('$0', '-c acala');
47
50
  if (!environment.DISABLE_PLUGINS) {
48
51
  pluginExtendCli(commands.config('config', 'Path to config file with default options', ()=>({}))).then(()=>commands.parse());
49
52
  } else {
@@ -4,4 +4,15 @@ import { Config } from './schema/index.js';
4
4
  export declare const genesisFromUrl: (url: string) => Promise<GenesisProvider>;
5
5
  export declare const setupContext: (argv: Config, overrideParent?: boolean) => Promise<{
6
6
  chain: import("@acala-network/chopsticks-core").Blockchain;
7
+ fetchStorageWorker: {
8
+ worker: import("comlink").Remote<{
9
+ startFetch: (options: {
10
+ block?: string | number | null | undefined;
11
+ endpoint?: string | string[] | undefined;
12
+ dbPath?: string | undefined;
13
+ config: import("./utils/fetch-storages.js").FetchStorageConfig;
14
+ }) => Promise<void>;
15
+ }>;
16
+ terminate: () => Promise<void>;
17
+ } | null;
7
18
  }>;
@@ -1,7 +1,9 @@
1
1
  import './utils/tunnel.js';
2
2
  import { GenesisProvider, defaultLogger, isUrl, setup, timeTravel } from '@acala-network/chopsticks-core';
3
3
  import { SqliteDatabase } from '@acala-network/chopsticks-db';
4
+ import { apiFetching } from './logger.js';
4
5
  import { overrideStorage, overrideWasm } from './utils/override.js';
6
+ import { startFetchStorageWorker } from './utils/fetch-storages.js';
5
7
  import axios from 'axios';
6
8
  const logger = defaultLogger.child({
7
9
  name: 'setup-context'
@@ -41,7 +43,10 @@ export const setupContext = async (argv, overrideParent = false)=>{
41
43
  registeredTypes: argv['registered-types'],
42
44
  offchainWorker: argv['offchain-worker'],
43
45
  maxMemoryBlockCount: argv['max-memory-block-count'],
44
- processQueuedMessages: argv['process-queued-messages']
46
+ processQueuedMessages: argv['process-queued-messages'],
47
+ hooks: {
48
+ apiFetching
49
+ }
45
50
  });
46
51
  // load block from db
47
52
  if (chain.db) {
@@ -77,7 +82,14 @@ export const setupContext = async (argv, overrideParent = false)=>{
77
82
  // added that have storage imports
78
83
  await overrideWasm(chain, argv['wasm-override'], at);
79
84
  await overrideStorage(chain, argv['import-storage'], at);
85
+ const fetchStorageWorker = await startFetchStorageWorker({
86
+ config: argv['prefetch-storages'],
87
+ dbPath: argv.db,
88
+ block: argv.block,
89
+ endpoint: argv.endpoint
90
+ });
80
91
  return {
81
- chain
92
+ chain,
93
+ fetchStorageWorker
82
94
  };
83
95
  };
@@ -1 +1,4 @@
1
+ import _ from 'lodash';
1
2
  export { defaultLogger, truncate } from '@acala-network/chopsticks-core';
3
+ export declare const spinnerFrames: string[];
4
+ export declare const apiFetching: _.DebouncedFunc<() => void>;
@@ -1 +1,35 @@
1
+ import _ from 'lodash';
1
2
  export { defaultLogger, truncate } from '@acala-network/chopsticks-core';
3
+ const showProgress = process.stdout.isTTY && !process.env['CI'] && !process.env['TEST'];
4
+ export const spinnerFrames = process.platform === 'win32' ? [
5
+ '-',
6
+ '\\',
7
+ '|',
8
+ '/'
9
+ ] : [
10
+ '⠋',
11
+ '⠙',
12
+ '⠹',
13
+ '⠸',
14
+ '⠼',
15
+ '⠴',
16
+ '⠦',
17
+ '⠧',
18
+ '⠇',
19
+ '⠏'
20
+ ];
21
+ let index = 0;
22
+ // clear to the right from cursor
23
+ const clearStatus = _.debounce(()=>process.stdout.clearLine(1), 500, {
24
+ trailing: true
25
+ });
26
+ export const apiFetching = _.throttle(()=>{
27
+ if (!showProgress) return;
28
+ // print ` ⠋ Fetching|` and move cursor at position 0 of the line `| ⠋ Fetching`
29
+ process.stdout.write(` ${spinnerFrames[index++]} Fetching`);
30
+ process.stdout.cursorTo(0);
31
+ index = ++index % spinnerFrames.length;
32
+ clearStatus();
33
+ }, 50, {
34
+ leading: true
35
+ });
@@ -83,6 +83,7 @@ export declare const dryRunSchema: z.ZodObject<{
83
83
  'offchain-worker': z.ZodOptional<z.ZodBoolean>;
84
84
  resume: z.ZodOptional<z.ZodUnion<[z.ZodIntersection<z.ZodString, z.ZodType<`0x${string}`, z.ZodTypeDef, `0x${string}`>>, z.ZodNumber, z.ZodBoolean]>>;
85
85
  'process-queued-messages': z.ZodOptional<z.ZodBoolean>;
86
+ 'prefetch-storages': z.ZodOptional<z.ZodAny>;
86
87
  }, "strip", z.ZodTypeAny, {
87
88
  port: number;
88
89
  'build-block-mode': import("@acala-network/chopsticks-core").BuildBlockMode;
@@ -121,6 +122,7 @@ export declare const dryRunSchema: z.ZodObject<{
121
122
  'offchain-worker'?: boolean | undefined;
122
123
  resume?: number | boolean | `0x${string}` | undefined;
123
124
  'process-queued-messages'?: boolean | undefined;
125
+ 'prefetch-storages'?: any;
124
126
  }, {
125
127
  extrinsic?: string | undefined;
126
128
  address?: string | undefined;
@@ -159,6 +161,7 @@ export declare const dryRunSchema: z.ZodObject<{
159
161
  'offchain-worker'?: boolean | undefined;
160
162
  resume?: number | boolean | `0x${string}` | undefined;
161
163
  'process-queued-messages'?: boolean | undefined;
164
+ 'prefetch-storages'?: any;
162
165
  }>;
163
166
  export type DryRunSchemaType = z.infer<typeof dryRunSchema>;
164
167
  export * from './cli.js';
@@ -7,11 +7,11 @@ declare const schema: z.ZodObject<{
7
7
  call: z.ZodType<`0x${string}`, z.ZodTypeDef, `0x${string}`>;
8
8
  address: z.ZodType<`0x${string}`, z.ZodTypeDef, `0x${string}`>;
9
9
  }, "strip", z.ZodTypeAny, {
10
- address: `0x${string}`;
11
10
  call: `0x${string}`;
12
- }, {
13
11
  address: `0x${string}`;
12
+ }, {
14
13
  call: `0x${string}`;
14
+ address: `0x${string}`;
15
15
  }>]>>;
16
16
  hrmp: z.ZodOptional<z.ZodRecord<z.ZodEffects<z.ZodString, number, string>, z.ZodArray<z.ZodObject<{
17
17
  sentAt: z.ZodNumber;
@@ -39,8 +39,8 @@ declare const schema: z.ZodObject<{
39
39
  raw?: boolean | undefined;
40
40
  html?: boolean | undefined;
41
41
  extrinsic?: `0x${string}` | {
42
- address: `0x${string}`;
43
42
  call: `0x${string}`;
43
+ address: `0x${string}`;
44
44
  } | undefined;
45
45
  hrmp?: Record<number, {
46
46
  data: `0x${string}`;
@@ -56,8 +56,8 @@ declare const schema: z.ZodObject<{
56
56
  raw?: boolean | undefined;
57
57
  html?: boolean | undefined;
58
58
  extrinsic?: `0x${string}` | {
59
- address: `0x${string}`;
60
59
  call: `0x${string}`;
60
+ address: `0x${string}`;
61
61
  } | undefined;
62
62
  hrmp?: Record<string, {
63
63
  data: `0x${string}`;
@@ -0,0 +1,2 @@
1
+ import type { Argv } from 'yargs';
2
+ export declare const cli: (y: Argv) => void;
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod';
2
+ import _ from 'lodash';
3
+ import { configSchema, getYargsOptions } from '../../schema/index.js';
4
+ import { fetchStorages } from '../../utils/fetch-storages.js';
5
+ const schema = z.object(_.pick(configSchema.shape, [
6
+ 'endpoint',
7
+ 'block',
8
+ 'db'
9
+ ]));
10
+ export const cli = (y)=>{
11
+ y.command({
12
+ command: 'fetch-storages [items..]',
13
+ aliases: [
14
+ 'fetch-storage'
15
+ ],
16
+ describe: 'Fetch and save storages',
17
+ builder: (yargs)=>yargs.options(getYargsOptions(schema.shape)),
18
+ handler: async (argv)=>{
19
+ const config = schema.parse(argv);
20
+ if (!argv.items) throw new Error('fetch-storages items are required');
21
+ try {
22
+ await fetchStorages({
23
+ block: config.block,
24
+ endpoint: config.endpoint,
25
+ dbPath: config.db,
26
+ config: argv.items
27
+ });
28
+ process.exit(0);
29
+ } catch (e) {
30
+ process.exit(1);
31
+ }
32
+ }
33
+ });
34
+ };
@@ -0,0 +1 @@
1
+ export * from './cli.js';
@@ -0,0 +1 @@
1
+ export * from './cli.js';
@@ -2,5 +2,7 @@ import { Handlers } from '@acala-network/chopsticks-core';
2
2
  import type { Argv } from 'yargs';
3
3
  export declare const rpcPluginHandlers: Handlers;
4
4
  export declare const rpcPluginMethods: string[];
5
- export declare const loadRpcPlugin: (method: string) => Promise<any>;
5
+ export declare const loadRpcMethodsByScripts: (path: string) => Promise<void>;
6
+ export declare const getRpcExtensionMethods: () => string[];
7
+ export declare const loadRpcExtensionMethod: (method: string) => Promise<any>;
6
8
  export declare const pluginExtendCli: (y: Argv) => Promise<void>;
@@ -1,7 +1,8 @@
1
1
  import { environment } from '@acala-network/chopsticks-core';
2
- import { lstatSync, readdirSync } from 'fs';
2
+ import { lstatSync, readFileSync, readdirSync } from 'fs';
3
3
  import _ from 'lodash';
4
4
  import { defaultLogger } from '../logger.js';
5
+ import { resolve } from 'path';
5
6
  const logger = defaultLogger.child({
6
7
  name: 'plugin'
7
8
  });
@@ -10,7 +11,7 @@ export const rpcPluginHandlers = {};
10
11
  const plugins = readdirSync(new URL('.', import.meta.url)).filter((file)=>lstatSync(new URL(file, import.meta.url)).isDirectory());
11
12
  // find all rpc methods
12
13
  export const rpcPluginMethods = plugins.filter((name)=>readdirSync(new URL(name, import.meta.url)).some((file)=>file.startsWith('rpc'))).map((name)=>`dev_${_.camelCase(name)}`);
13
- export const loadRpcPlugin = async (method)=>{
14
+ const loadRpcPlugin = async (method)=>{
14
15
  if (environment.DISABLE_PLUGINS) {
15
16
  return undefined;
16
17
  }
@@ -24,6 +25,28 @@ export const loadRpcPlugin = async (method)=>{
24
25
  logger.debug(`Registered plugin ${plugin} RPC`);
25
26
  return rpc;
26
27
  };
28
+ // store the loaded methods by cli
29
+ let rpcScriptMethods = {};
30
+ // use cli to load rpc methods of external scripts
31
+ export const loadRpcMethodsByScripts = async (path)=>{
32
+ try {
33
+ const scriptContent = readFileSync(resolve(path), 'utf8');
34
+ rpcScriptMethods = new Function(scriptContent)();
35
+ logger.info(`${Object.keys(rpcScriptMethods).length} extension rpc methods loaded from ${path}`);
36
+ } catch (error) {
37
+ console.log('Failed to load rpc extension methods');
38
+ }
39
+ };
40
+ export const getRpcExtensionMethods = ()=>{
41
+ return [
42
+ ...Object.keys(rpcScriptMethods),
43
+ ...rpcPluginMethods
44
+ ];
45
+ };
46
+ export const loadRpcExtensionMethod = async (method)=>{
47
+ if (rpcScriptMethods[method]) return rpcScriptMethods[method];
48
+ return loadRpcPlugin(method);
49
+ };
27
50
  export const pluginExtendCli = async (y)=>{
28
51
  for (const plugin of plugins){
29
52
  const location = new URL(`${plugin}/index.js`, import.meta.url);
@@ -1,5 +1,5 @@
1
1
  import { ResponseError, allHandlers as coreHandlers, defaultLogger } from '@acala-network/chopsticks-core';
2
- import { loadRpcPlugin, rpcPluginMethods } from '../plugins/index.js';
2
+ import { getRpcExtensionMethods, loadRpcExtensionMethod } from '../plugins/index.js';
3
3
  const rpcLogger = defaultLogger.child({
4
4
  name: 'rpc'
5
5
  });
@@ -9,15 +9,15 @@ const allHandlers = {
9
9
  version: 1,
10
10
  methods: [
11
11
  ...Object.keys(allHandlers),
12
- ...rpcPluginMethods
12
+ ...getRpcExtensionMethods()
13
13
  ].sort()
14
14
  })
15
15
  };
16
16
  const getHandler = async (method)=>{
17
17
  const handler = allHandlers[method];
18
18
  if (!handler) {
19
- // no handler for this method, check if it's a plugin
20
- return loadRpcPlugin(method);
19
+ // no handler for this method, check if it's a plugin or a script loaded
20
+ return loadRpcExtensionMethod(method);
21
21
  }
22
22
  return handler;
23
23
  };
@@ -79,6 +79,7 @@ export declare const configSchema: z.ZodObject<{
79
79
  'offchain-worker': z.ZodOptional<z.ZodBoolean>;
80
80
  resume: z.ZodOptional<z.ZodUnion<[z.ZodIntersection<z.ZodString, z.ZodType<`0x${string}`, z.ZodTypeDef, `0x${string}`>>, z.ZodNumber, z.ZodBoolean]>>;
81
81
  'process-queued-messages': z.ZodOptional<z.ZodBoolean>;
82
+ 'prefetch-storages': z.ZodOptional<z.ZodAny>;
82
83
  }, "strip", ZodTypeAny, {
83
84
  port: number;
84
85
  'build-block-mode': BuildBlockMode;
@@ -110,6 +111,7 @@ export declare const configSchema: z.ZodObject<{
110
111
  'offchain-worker'?: boolean | undefined;
111
112
  resume?: number | boolean | `0x${string}` | undefined;
112
113
  'process-queued-messages'?: boolean | undefined;
114
+ 'prefetch-storages'?: any;
113
115
  }, {
114
116
  port?: number | undefined;
115
117
  endpoint?: string | string[] | undefined;
@@ -141,6 +143,7 @@ export declare const configSchema: z.ZodObject<{
141
143
  'offchain-worker'?: boolean | undefined;
142
144
  resume?: number | boolean | `0x${string}` | undefined;
143
145
  'process-queued-messages'?: boolean | undefined;
146
+ 'prefetch-storages'?: any;
144
147
  }>;
145
148
  export type Config = z.infer<typeof configSchema>;
146
149
  export declare const getYargsOptions: (zodShape: ZodRawShape) => {
@@ -19,7 +19,7 @@ export const configSchema = z.object({
19
19
  }).optional(),
20
20
  block: z.union([
21
21
  z.string(),
22
- z.number().max(Number.MAX_SAFE_INTEGER, 'Number is too big, please make it a string if you are uing a hex string'),
22
+ z.number().max(Number.MAX_SAFE_INTEGER, 'Number is too big, please make it a string if you are using a hex string'),
23
23
  z.null()
24
24
  ], {
25
25
  description: 'Block hash or block number. Default to latest block'
@@ -62,6 +62,9 @@ export const configSchema = z.object({
62
62
  }).optional(),
63
63
  'process-queued-messages': z.boolean({
64
64
  description: 'Produce extra block when queued messages are detected. Default to true. Set to false to disable it.'
65
+ }).optional(),
66
+ 'prefetch-storages': z.any({
67
+ description: 'Storage key prefixes config for fetching storage, useful for testing big migrations, see README for examples'
65
68
  }).optional()
66
69
  });
67
70
  const getZodType = (option)=>{
@@ -3,4 +3,15 @@ export declare const setupWithServer: (argv: Config) => Promise<{
3
3
  listenPort: number;
4
4
  close(): Promise<void>;
5
5
  chain: import("@acala-network/chopsticks-core").Blockchain;
6
+ fetchStorageWorker: {
7
+ worker: import("comlink").Remote<{
8
+ startFetch: (options: {
9
+ block?: string | number | null | undefined;
10
+ endpoint?: string | string[] | undefined;
11
+ dbPath?: string | undefined;
12
+ config: import("./utils/fetch-storages.js").FetchStorageConfig;
13
+ }) => Promise<void>;
14
+ }>;
15
+ terminate: () => Promise<void>;
16
+ } | null;
6
17
  }>;
@@ -11,6 +11,7 @@ export const setupWithServer = async (argv)=>{
11
11
  listenPort,
12
12
  async close () {
13
13
  await context.chain.close();
14
+ await context.fetchStorageWorker?.terminate();
14
15
  await close();
15
16
  }
16
17
  };
@@ -0,0 +1,12 @@
1
+ import * as Comlink from 'comlink';
2
+ import { parentPort } from 'node:worker_threads';
3
+ import nodeEndpoint from 'comlink/dist/umd/node-adapter.js';
4
+ import { fetchStorages } from './fetch-storages.js';
5
+ const api = {
6
+ startFetch: async ({ ...options })=>{
7
+ await fetchStorages({
8
+ ...options
9
+ });
10
+ }
11
+ };
12
+ Comlink.expose(api, nodeEndpoint(parentPort));
@@ -0,0 +1,26 @@
1
+ import { ApiPromise } from '@polkadot/api';
2
+ import { HexString } from '@polkadot/util/types';
3
+ export declare const logger: import("pino").default.Logger<never>;
4
+ type FetchStorageConfigItem = HexString | string | Record<string, string | Record<string, any[]> | Record<string, any>[] | (string | any)[]>;
5
+ export type FetchStorageConfig = FetchStorageConfigItem[];
6
+ /**
7
+ * Convert fetch-storage configs to prefixes for fetching.
8
+ */
9
+ export declare const getPrefixesFromConfig: (config: FetchStorageConfig, api: ApiPromise) => Promise<string[]>;
10
+ type FetchStoragesParams = {
11
+ block?: number | string | null;
12
+ endpoint?: string | string[];
13
+ dbPath?: string;
14
+ config: FetchStorageConfig;
15
+ };
16
+ /**
17
+ * Fetch storages and save in a local db
18
+ */
19
+ export declare const fetchStorages: ({ block, endpoint, dbPath, config }: FetchStoragesParams) => Promise<void>;
20
+ export declare const startFetchStorageWorker: (options: FetchStoragesParams) => Promise<{
21
+ worker: import("comlink").Remote<{
22
+ startFetch: (options: FetchStoragesParams) => Promise<void>;
23
+ }>;
24
+ terminate: () => Promise<void>;
25
+ } | null>;
26
+ export {};