@acala-network/chopsticks 0.11.0 → 0.12.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/dist/cjs/context.d.ts +11 -0
- package/dist/cjs/context.js +14 -2
- package/dist/cjs/logger.d.ts +3 -0
- package/dist/cjs/logger.js +45 -0
- package/dist/cjs/plugins/dry-run/index.d.ts +3 -0
- package/dist/cjs/plugins/dry-run/rpc.d.ts +4 -4
- package/dist/cjs/plugins/fetch-storage/cli.d.ts +2 -0
- package/dist/cjs/plugins/fetch-storage/cli.js +49 -0
- package/dist/cjs/plugins/fetch-storage/index.d.ts +1 -0
- package/dist/cjs/plugins/fetch-storage/index.js +18 -0
- package/dist/cjs/schema/index.d.ts +3 -0
- package/dist/cjs/schema/index.js +4 -1
- package/dist/cjs/setup-with-server.d.ts +11 -0
- package/dist/cjs/setup-with-server.js +1 -0
- package/dist/cjs/utils/fetch-storages-worker.js +62 -0
- package/dist/cjs/utils/fetch-storages.d.ts +26 -0
- package/dist/cjs/utils/fetch-storages.js +213 -0
- package/dist/cjs/utils/index.d.ts +1 -0
- package/dist/cjs/utils/index.js +1 -0
- package/dist/esm/context.d.ts +11 -0
- package/dist/esm/context.js +14 -2
- package/dist/esm/logger.d.ts +3 -0
- package/dist/esm/logger.js +34 -0
- package/dist/esm/plugins/dry-run/index.d.ts +3 -0
- package/dist/esm/plugins/dry-run/rpc.d.ts +4 -4
- package/dist/esm/plugins/fetch-storage/cli.d.ts +2 -0
- package/dist/esm/plugins/fetch-storage/cli.js +34 -0
- package/dist/esm/plugins/fetch-storage/index.d.ts +1 -0
- package/dist/esm/plugins/fetch-storage/index.js +1 -0
- package/dist/esm/schema/index.d.ts +3 -0
- package/dist/esm/schema/index.js +4 -1
- package/dist/esm/setup-with-server.d.ts +11 -0
- package/dist/esm/setup-with-server.js +1 -0
- package/dist/esm/utils/fetch-storages-worker.js +12 -0
- package/dist/esm/utils/fetch-storages.d.ts +26 -0
- package/dist/esm/utils/fetch-storages.js +188 -0
- package/dist/esm/utils/index.d.ts +1 -0
- package/dist/esm/utils/index.js +1 -0
- package/package.json +4 -3
package/dist/cjs/context.d.ts
CHANGED
|
@@ -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
|
}>;
|
package/dist/cjs/context.js
CHANGED
|
@@ -19,7 +19,9 @@ _export(exports, {
|
|
|
19
19
|
require("./utils/tunnel.js");
|
|
20
20
|
const _chopstickscore = require("@acala-network/chopsticks-core");
|
|
21
21
|
const _chopsticksdb = require("@acala-network/chopsticks-db");
|
|
22
|
+
const _logger = require("./logger.js");
|
|
22
23
|
const _override = require("./utils/override.js");
|
|
24
|
+
const _fetchstorages = require("./utils/fetch-storages.js");
|
|
23
25
|
const _axios = /*#__PURE__*/ _interop_require_default(require("axios"));
|
|
24
26
|
function _interop_require_default(obj) {
|
|
25
27
|
return obj && obj.__esModule ? obj : {
|
|
@@ -105,7 +107,10 @@ const setupContext = async (argv, overrideParent = false)=>{
|
|
|
105
107
|
registeredTypes: argv['registered-types'],
|
|
106
108
|
offchainWorker: argv['offchain-worker'],
|
|
107
109
|
maxMemoryBlockCount: argv['max-memory-block-count'],
|
|
108
|
-
processQueuedMessages: argv['process-queued-messages']
|
|
110
|
+
processQueuedMessages: argv['process-queued-messages'],
|
|
111
|
+
hooks: {
|
|
112
|
+
apiFetching: _logger.apiFetching
|
|
113
|
+
}
|
|
109
114
|
});
|
|
110
115
|
// load block from db
|
|
111
116
|
if (chain.db) {
|
|
@@ -141,7 +146,14 @@ const setupContext = async (argv, overrideParent = false)=>{
|
|
|
141
146
|
// added that have storage imports
|
|
142
147
|
await (0, _override.overrideWasm)(chain, argv['wasm-override'], at);
|
|
143
148
|
await (0, _override.overrideStorage)(chain, argv['import-storage'], at);
|
|
149
|
+
const fetchStorageWorker = await (0, _fetchstorages.startFetchStorageWorker)({
|
|
150
|
+
config: argv['prefetch-storages'],
|
|
151
|
+
dbPath: argv.db,
|
|
152
|
+
block: argv.block,
|
|
153
|
+
endpoint: argv.endpoint
|
|
154
|
+
});
|
|
144
155
|
return {
|
|
145
|
-
chain
|
|
156
|
+
chain,
|
|
157
|
+
fetchStorageWorker
|
|
146
158
|
};
|
|
147
159
|
};
|
package/dist/cjs/logger.d.ts
CHANGED
package/dist/cjs/logger.js
CHANGED
|
@@ -9,11 +9,56 @@ function _export(target, all) {
|
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
11
|
_export(exports, {
|
|
12
|
+
apiFetching: function() {
|
|
13
|
+
return apiFetching;
|
|
14
|
+
},
|
|
12
15
|
defaultLogger: function() {
|
|
13
16
|
return _chopstickscore.defaultLogger;
|
|
14
17
|
},
|
|
18
|
+
spinnerFrames: function() {
|
|
19
|
+
return spinnerFrames;
|
|
20
|
+
},
|
|
15
21
|
truncate: function() {
|
|
16
22
|
return _chopstickscore.truncate;
|
|
17
23
|
}
|
|
18
24
|
});
|
|
25
|
+
const _lodash = /*#__PURE__*/ _interop_require_default(require("lodash"));
|
|
19
26
|
const _chopstickscore = require("@acala-network/chopsticks-core");
|
|
27
|
+
function _interop_require_default(obj) {
|
|
28
|
+
return obj && obj.__esModule ? obj : {
|
|
29
|
+
default: obj
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const showProgress = process.stdout.isTTY && !process.env['CI'] && !process.env['TEST'];
|
|
33
|
+
const spinnerFrames = process.platform === 'win32' ? [
|
|
34
|
+
'-',
|
|
35
|
+
'\\',
|
|
36
|
+
'|',
|
|
37
|
+
'/'
|
|
38
|
+
] : [
|
|
39
|
+
'⠋',
|
|
40
|
+
'⠙',
|
|
41
|
+
'⠹',
|
|
42
|
+
'⠸',
|
|
43
|
+
'⠼',
|
|
44
|
+
'⠴',
|
|
45
|
+
'⠦',
|
|
46
|
+
'⠧',
|
|
47
|
+
'⠇',
|
|
48
|
+
'⠏'
|
|
49
|
+
];
|
|
50
|
+
let index = 0;
|
|
51
|
+
// clear to the right from cursor
|
|
52
|
+
const clearStatus = _lodash.default.debounce(()=>process.stdout.clearLine(1), 500, {
|
|
53
|
+
trailing: true
|
|
54
|
+
});
|
|
55
|
+
const apiFetching = _lodash.default.throttle(()=>{
|
|
56
|
+
if (!showProgress) return;
|
|
57
|
+
// print ` ⠋ Fetching|` and move cursor at position 0 of the line `| ⠋ Fetching`
|
|
58
|
+
process.stdout.write(` ${spinnerFrames[index++]} Fetching`);
|
|
59
|
+
process.stdout.cursorTo(0);
|
|
60
|
+
index = ++index % spinnerFrames.length;
|
|
61
|
+
clearStatus();
|
|
62
|
+
}, 50, {
|
|
63
|
+
leading: true
|
|
64
|
+
});
|
|
@@ -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,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "cli", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return cli;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _zod = require("zod");
|
|
12
|
+
const _lodash = /*#__PURE__*/ _interop_require_default(require("lodash"));
|
|
13
|
+
const _index = require("../../schema/index.js");
|
|
14
|
+
const _fetchstorages = require("../../utils/fetch-storages.js");
|
|
15
|
+
function _interop_require_default(obj) {
|
|
16
|
+
return obj && obj.__esModule ? obj : {
|
|
17
|
+
default: obj
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const schema = _zod.z.object(_lodash.default.pick(_index.configSchema.shape, [
|
|
21
|
+
'endpoint',
|
|
22
|
+
'block',
|
|
23
|
+
'db'
|
|
24
|
+
]));
|
|
25
|
+
const cli = (y)=>{
|
|
26
|
+
y.command({
|
|
27
|
+
command: 'fetch-storages [items..]',
|
|
28
|
+
aliases: [
|
|
29
|
+
'fetch-storage'
|
|
30
|
+
],
|
|
31
|
+
describe: 'Fetch and save storages',
|
|
32
|
+
builder: (yargs)=>yargs.options((0, _index.getYargsOptions)(schema.shape)),
|
|
33
|
+
handler: async (argv)=>{
|
|
34
|
+
const config = schema.parse(argv);
|
|
35
|
+
if (!argv.items) throw new Error('fetch-storages items are required');
|
|
36
|
+
try {
|
|
37
|
+
await (0, _fetchstorages.fetchStorages)({
|
|
38
|
+
block: config.block,
|
|
39
|
+
endpoint: config.endpoint,
|
|
40
|
+
dbPath: config.db,
|
|
41
|
+
config: argv.items
|
|
42
|
+
});
|
|
43
|
+
process.exit(0);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './cli.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
_export_star(require("./cli.js"), exports);
|
|
6
|
+
function _export_star(from, to) {
|
|
7
|
+
Object.keys(from).forEach(function(k) {
|
|
8
|
+
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
|
|
9
|
+
Object.defineProperty(to, k, {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: function() {
|
|
12
|
+
return from[k];
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
return from;
|
|
18
|
+
}
|
|
@@ -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) => {
|
package/dist/cjs/schema/index.js
CHANGED
|
@@ -51,7 +51,7 @@ const configSchema = _zod.z.object({
|
|
|
51
51
|
}).optional(),
|
|
52
52
|
block: _zod.z.union([
|
|
53
53
|
_zod.z.string(),
|
|
54
|
-
_zod.z.number().max(Number.MAX_SAFE_INTEGER, 'Number is too big, please make it a string if you are
|
|
54
|
+
_zod.z.number().max(Number.MAX_SAFE_INTEGER, 'Number is too big, please make it a string if you are using a hex string'),
|
|
55
55
|
_zod.z.null()
|
|
56
56
|
], {
|
|
57
57
|
description: 'Block hash or block number. Default to latest block'
|
|
@@ -94,6 +94,9 @@ const configSchema = _zod.z.object({
|
|
|
94
94
|
}).optional(),
|
|
95
95
|
'process-queued-messages': _zod.z.boolean({
|
|
96
96
|
description: 'Produce extra block when queued messages are detected. Default to true. Set to false to disable it.'
|
|
97
|
+
}).optional(),
|
|
98
|
+
'prefetch-storages': _zod.z.any({
|
|
99
|
+
description: 'Storage key prefixes config for fetching storage, useful for testing big migrations, see README for examples'
|
|
97
100
|
}).optional()
|
|
98
101
|
});
|
|
99
102
|
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
|
}>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _comlink = /*#__PURE__*/ _interop_require_wildcard(require("comlink"));
|
|
6
|
+
const _nodeworker_threads = require("node:worker_threads");
|
|
7
|
+
const _nodeadapter = /*#__PURE__*/ _interop_require_default(require("comlink/dist/umd/node-adapter.js"));
|
|
8
|
+
const _fetchstorages = require("./fetch-storages.js");
|
|
9
|
+
function _interop_require_default(obj) {
|
|
10
|
+
return obj && obj.__esModule ? obj : {
|
|
11
|
+
default: obj
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function _getRequireWildcardCache(nodeInterop) {
|
|
15
|
+
if (typeof WeakMap !== "function") return null;
|
|
16
|
+
var cacheBabelInterop = new WeakMap();
|
|
17
|
+
var cacheNodeInterop = new WeakMap();
|
|
18
|
+
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
19
|
+
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
20
|
+
})(nodeInterop);
|
|
21
|
+
}
|
|
22
|
+
function _interop_require_wildcard(obj, nodeInterop) {
|
|
23
|
+
if (!nodeInterop && obj && obj.__esModule) {
|
|
24
|
+
return obj;
|
|
25
|
+
}
|
|
26
|
+
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
27
|
+
return {
|
|
28
|
+
default: obj
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
var cache = _getRequireWildcardCache(nodeInterop);
|
|
32
|
+
if (cache && cache.has(obj)) {
|
|
33
|
+
return cache.get(obj);
|
|
34
|
+
}
|
|
35
|
+
var newObj = {
|
|
36
|
+
__proto__: null
|
|
37
|
+
};
|
|
38
|
+
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
39
|
+
for(var key in obj){
|
|
40
|
+
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
41
|
+
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
42
|
+
if (desc && (desc.get || desc.set)) {
|
|
43
|
+
Object.defineProperty(newObj, key, desc);
|
|
44
|
+
} else {
|
|
45
|
+
newObj[key] = obj[key];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
newObj.default = obj;
|
|
50
|
+
if (cache) {
|
|
51
|
+
cache.set(obj, newObj);
|
|
52
|
+
}
|
|
53
|
+
return newObj;
|
|
54
|
+
}
|
|
55
|
+
const api = {
|
|
56
|
+
startFetch: async ({ ...options })=>{
|
|
57
|
+
await (0, _fetchstorages.fetchStorages)({
|
|
58
|
+
...options
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
_comlink.expose(api, (0, _nodeadapter.default)(_nodeworker_threads.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 {};
|
|
@@ -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
|
+
};
|
package/dist/cjs/utils/index.js
CHANGED
|
@@ -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/context.d.ts
CHANGED
|
@@ -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
|
}>;
|
package/dist/esm/context.js
CHANGED
|
@@ -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
|
};
|
package/dist/esm/logger.d.ts
CHANGED
package/dist/esm/logger.js
CHANGED
|
@@ -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,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';
|
|
@@ -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) => {
|
package/dist/esm/schema/index.js
CHANGED
|
@@ -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
|
|
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
|
}>;
|
|
@@ -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 {};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Api, defaultLogger } from '@acala-network/chopsticks-core';
|
|
2
|
+
import { ApiPromise } from '@polkadot/api';
|
|
3
|
+
import { SqliteDatabase } from '@acala-network/chopsticks-db';
|
|
4
|
+
import { WsProvider } from '@polkadot/rpc-provider';
|
|
5
|
+
import { compactStripLength, stringCamelCase, u8aToHex } from '@polkadot/util';
|
|
6
|
+
import { expandMetadata } from '@polkadot/types';
|
|
7
|
+
import { releaseProxy, wrap } from 'comlink';
|
|
8
|
+
import { xxhashAsHex } from '@polkadot/util-crypto';
|
|
9
|
+
import _ from 'lodash';
|
|
10
|
+
import nodeEndpoint from 'comlink/dist/umd/node-adapter.js';
|
|
11
|
+
import threads from 'node:worker_threads';
|
|
12
|
+
const BATCH_SIZE = 1000;
|
|
13
|
+
export const logger = defaultLogger.child({
|
|
14
|
+
name: 'fetch-storages'
|
|
15
|
+
});
|
|
16
|
+
const getHexKeyWithArgs = (meta, storage, args)=>{
|
|
17
|
+
const isPartialKey = args.length !== (meta.type.isPlain ? 0 : meta.type.asMap.hashers.length);
|
|
18
|
+
const hexKey = isPartialKey && storage.creator.iterKey ? storage.creator.iterKey(...args).toHex() : u8aToHex(compactStripLength(storage.creator(...args))[1]);
|
|
19
|
+
return hexKey;
|
|
20
|
+
};
|
|
21
|
+
const checkPalletStorageByName = (meta, palletName, storageName)=>{
|
|
22
|
+
const pallet = meta.query[stringCamelCase(palletName)];
|
|
23
|
+
if (!pallet) throw Error(`Cannot find pallet ${palletName}`);
|
|
24
|
+
let storage;
|
|
25
|
+
if (storageName) {
|
|
26
|
+
storage = pallet[stringCamelCase(storageName)];
|
|
27
|
+
if (!storage) throw Error(`Cannot find storage ${storageName} in pallet ${palletName}`);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
pallet,
|
|
31
|
+
storage
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Convert fetch-storage configs to prefixes for fetching.
|
|
36
|
+
*/ export const getPrefixesFromConfig = async (config, api)=>{
|
|
37
|
+
logger.debug({
|
|
38
|
+
config
|
|
39
|
+
}, 'received fetch-storage config');
|
|
40
|
+
const prefixes = [];
|
|
41
|
+
const metadata = await api.rpc.state.getMetadata();
|
|
42
|
+
const expandMeta = expandMetadata(metadata.registry, metadata);
|
|
43
|
+
for (const item of config){
|
|
44
|
+
if (typeof item === 'string' && item.startsWith('0x')) {
|
|
45
|
+
// hex
|
|
46
|
+
prefixes.push(item);
|
|
47
|
+
} else if (typeof item === 'string' && !item.includes('.')) {
|
|
48
|
+
// pallet
|
|
49
|
+
checkPalletStorageByName(expandMeta, item);
|
|
50
|
+
prefixes.push(xxhashAsHex(item, 128));
|
|
51
|
+
} else if (typeof item === 'string' && item.includes('.')) {
|
|
52
|
+
// pallet.storage
|
|
53
|
+
const [palletName, storageName] = item.split('.');
|
|
54
|
+
const { storage } = checkPalletStorageByName(expandMeta, palletName, storageName);
|
|
55
|
+
prefixes.push(u8aToHex(storage.keyPrefix()));
|
|
56
|
+
} else if (typeof item === 'object') {
|
|
57
|
+
// object cases
|
|
58
|
+
const [objectKey, objectVal] = Object.entries(item)[0];
|
|
59
|
+
if (typeof objectVal === 'string') {
|
|
60
|
+
// - System: Account
|
|
61
|
+
const { storage } = checkPalletStorageByName(expandMeta, objectKey, objectVal);
|
|
62
|
+
prefixes.push(u8aToHex(storage.keyPrefix()));
|
|
63
|
+
} else if (objectKey.includes('.') && Array.isArray(objectVal)) {
|
|
64
|
+
// - Pallet.Storage: [xxx, ...]
|
|
65
|
+
const [pallet, storage] = objectKey.split('.').map((x)=>stringCamelCase(x));
|
|
66
|
+
checkPalletStorageByName(expandMeta, pallet, storage);
|
|
67
|
+
const storageEntry = api.query[pallet][storage];
|
|
68
|
+
const meta = storageEntry.creator.meta;
|
|
69
|
+
const args = objectVal;
|
|
70
|
+
const hexKey = getHexKeyWithArgs(meta, storageEntry, args);
|
|
71
|
+
prefixes.push(hexKey);
|
|
72
|
+
} else if (!Array.isArray(objectVal)) {
|
|
73
|
+
// - Tokens:
|
|
74
|
+
// Accounts: [xxx, ...]
|
|
75
|
+
const pallet = stringCamelCase(objectKey);
|
|
76
|
+
const [storage, args] = Object.entries(objectVal)[0];
|
|
77
|
+
checkPalletStorageByName(expandMeta, pallet, storage);
|
|
78
|
+
const storageEntry = api.query[pallet][stringCamelCase(storage)];
|
|
79
|
+
const meta = storageEntry.creator.meta;
|
|
80
|
+
const hexKey = getHexKeyWithArgs(meta, storageEntry, args);
|
|
81
|
+
prefixes.push(hexKey);
|
|
82
|
+
} else {
|
|
83
|
+
throw new Error(`Unsupported fetch-storage config: ${objectKey}.${objectVal}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
logger.debug({
|
|
88
|
+
prefixes
|
|
89
|
+
}, 'prefixes from config');
|
|
90
|
+
return prefixes;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Fetch storages and save in a local db
|
|
94
|
+
*/ export const fetchStorages = async ({ block, endpoint, dbPath, config })=>{
|
|
95
|
+
if (!endpoint) throw new Error('endpoint is required');
|
|
96
|
+
if (!block) throw new Error('block is required');
|
|
97
|
+
const provider = new WsProvider(endpoint, 3_000);
|
|
98
|
+
const apiPromise = new ApiPromise({
|
|
99
|
+
provider
|
|
100
|
+
});
|
|
101
|
+
await apiPromise.isReady;
|
|
102
|
+
let blockHash;
|
|
103
|
+
if (block == null) {
|
|
104
|
+
const lastHdr = await apiPromise.rpc.chain.getHeader();
|
|
105
|
+
blockHash = lastHdr.hash.toString();
|
|
106
|
+
} else if (typeof block === 'string' && block.startsWith('0x')) {
|
|
107
|
+
blockHash = block;
|
|
108
|
+
} else if (Number.isInteger(+block)) {
|
|
109
|
+
blockHash = await apiPromise.rpc.chain.getBlockHash(Number(block)).then((h)=>h.toString());
|
|
110
|
+
} else {
|
|
111
|
+
throw new Error(`Invalid block number or hash: ${block}`);
|
|
112
|
+
}
|
|
113
|
+
const prefixesFromConfig = await getPrefixesFromConfig(config, apiPromise);
|
|
114
|
+
const uniqPrefixes = _.uniq(prefixesFromConfig);
|
|
115
|
+
const processPrefixes = (prefixes)=>{
|
|
116
|
+
prefixes.sort();
|
|
117
|
+
const result = [];
|
|
118
|
+
for (const prefix of prefixes){
|
|
119
|
+
// check if the current prefix is not a prefix of any added prefix
|
|
120
|
+
if (!result.some((prev)=>prefix.startsWith(prev))) {
|
|
121
|
+
result.push(prefix);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
};
|
|
126
|
+
const prefixes = processPrefixes(uniqPrefixes);
|
|
127
|
+
if (!prefixes.length) throw new Error('No prefixes to fetch');
|
|
128
|
+
const signedBlock = await apiPromise.rpc.chain.getBlock(blockHash);
|
|
129
|
+
const blockNumber = signedBlock.block.header.number.toNumber();
|
|
130
|
+
const chainName = (await apiPromise.rpc.system.chain()).toString();
|
|
131
|
+
const finalDbPath = dbPath ?? `db-${chainName}-${blockNumber}.sqlite`;
|
|
132
|
+
const api = new Api(provider);
|
|
133
|
+
const db = new SqliteDatabase(finalDbPath);
|
|
134
|
+
logger.info(`Storages will be saved at ${finalDbPath}, use '--db=${finalDbPath} --block=${blockNumber}' to apply it later on`);
|
|
135
|
+
for (const prefix of prefixes){
|
|
136
|
+
let startKey = '0x';
|
|
137
|
+
let hasMorePages = true;
|
|
138
|
+
while(hasMorePages){
|
|
139
|
+
logger.debug({
|
|
140
|
+
prefix,
|
|
141
|
+
startKey
|
|
142
|
+
}, 'fetching keys');
|
|
143
|
+
const keysPage = await api.getKeysPaged(prefix, BATCH_SIZE, startKey, blockHash);
|
|
144
|
+
logger.debug({
|
|
145
|
+
prefix,
|
|
146
|
+
startKey
|
|
147
|
+
}, `fetched ${keysPage.length} keys`);
|
|
148
|
+
if (!keysPage.length) break;
|
|
149
|
+
startKey = keysPage[keysPage.length - 1];
|
|
150
|
+
if (!keysPage || keysPage.length < BATCH_SIZE) {
|
|
151
|
+
hasMorePages = false;
|
|
152
|
+
}
|
|
153
|
+
logger.debug({
|
|
154
|
+
prefix
|
|
155
|
+
}, 'fetching storages');
|
|
156
|
+
const storages = await api.getStorageBatch(prefix, keysPage, blockHash);
|
|
157
|
+
logger.debug({
|
|
158
|
+
prefix
|
|
159
|
+
}, `fetched ${storages.length} storages`);
|
|
160
|
+
const keyValueEntries = storages.map(([key, value])=>({
|
|
161
|
+
blockHash,
|
|
162
|
+
key,
|
|
163
|
+
value
|
|
164
|
+
}));
|
|
165
|
+
await db.saveStorageBatch(keyValueEntries);
|
|
166
|
+
logger.debug({
|
|
167
|
+
prefix
|
|
168
|
+
}, `saved ${storages.length} storages ✅`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
logger.info(`Storages are saved at ${finalDbPath}, use '--db=${finalDbPath} --block=${blockNumber}' to apply it`);
|
|
172
|
+
};
|
|
173
|
+
export const startFetchStorageWorker = async (options)=>{
|
|
174
|
+
if (!options.config) return null;
|
|
175
|
+
const worker = new threads.Worker(new URL('./fetch-storages-worker.js', import.meta.url), {
|
|
176
|
+
name: 'fetch-storages-worker'
|
|
177
|
+
});
|
|
178
|
+
const workerApi = wrap(nodeEndpoint(worker));
|
|
179
|
+
workerApi.startFetch(options);
|
|
180
|
+
const terminate = async ()=>{
|
|
181
|
+
workerApi[releaseProxy]();
|
|
182
|
+
await worker.terminate();
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
worker: workerApi,
|
|
186
|
+
terminate
|
|
187
|
+
};
|
|
188
|
+
};
|
package/dist/esm/utils/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acala-network/chopsticks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"author": "Acala Developers <hello@acala.network>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": "./chopsticks.cjs",
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
"docs:prep": "typedoc"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@acala-network/chopsticks-core": "0.
|
|
17
|
-
"@acala-network/chopsticks-db": "0.
|
|
16
|
+
"@acala-network/chopsticks-core": "0.12.1",
|
|
17
|
+
"@acala-network/chopsticks-db": "0.12.1",
|
|
18
18
|
"@pnpm/npm-conf": "^2.2.2",
|
|
19
|
+
"@polkadot/api": "^10.11.2",
|
|
19
20
|
"@polkadot/api-augment": "^10.11.2",
|
|
20
21
|
"@polkadot/types": "^10.11.2",
|
|
21
22
|
"@polkadot/util": "^12.6.2",
|