@acala-network/chopsticks 0.8.5-5 → 0.9.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.
- package/chopsticks.js +1 -1
- package/{lib → dist/cjs}/plugins/dry-run/dry-run-preimage.js +1 -3
- package/{lib → dist/cjs}/plugins/run-block/index.js +15 -21
- package/{lib → dist/cjs}/utils/generate-html-diff.js +1 -1
- package/dist/esm/cli-options.js +40 -0
- package/dist/esm/cli.js +90 -0
- package/dist/esm/context.js +86 -0
- package/dist/esm/plugins/decode-key/index.js +23 -0
- package/dist/esm/plugins/dry-run/cli.js +42 -0
- package/dist/esm/plugins/dry-run/dry-run-extrinsic.js +29 -0
- package/dist/esm/plugins/dry-run/dry-run-preimage.js +92 -0
- package/dist/esm/plugins/dry-run/rpc.js +101 -0
- package/dist/esm/plugins/index.js +25 -0
- package/dist/esm/plugins/new-block/index.js +67 -0
- package/dist/esm/plugins/run-block/index.js +176 -0
- package/dist/esm/plugins/set-block-build-mode/index.js +25 -0
- package/dist/esm/plugins/set-head/index.js +31 -0
- package/dist/esm/plugins/set-runtime-log-level/index.js +24 -0
- package/dist/esm/plugins/set-storage/index.js +38 -0
- package/dist/esm/plugins/time-travel/index.js +23 -0
- package/dist/esm/plugins/try-runtime/index.js +54 -0
- package/{lib/plugins/types.d.ts → dist/esm/plugins/types.js} +3 -3
- package/dist/esm/rpc/index.js +26 -0
- package/dist/esm/schema/index.js +53 -0
- package/dist/esm/server.js +164 -0
- package/dist/esm/setup-with-server.js +22 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/utils/decoder.js +15 -0
- package/dist/esm/utils/generate-html-diff.js +16 -0
- package/dist/esm/utils/open-html.js +5 -0
- package/dist/esm/utils/override.js +43 -0
- package/dist/esm/utils/tunnel.js +11 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/logger.d.ts +1 -0
- package/dist/types/plugins/dry-run/index.d.ts +2 -0
- package/{lib → dist/types}/plugins/run-block/index.d.ts +14 -6
- package/dist/types/plugins/types.d.ts +11 -0
- package/dist/types/utils/index.d.ts +4 -0
- package/package.json +23 -14
- /package/{lib → dist/cjs}/cli-options.js +0 -0
- /package/{lib → dist/cjs}/cli.js +0 -0
- /package/{lib → dist/cjs}/context.js +0 -0
- /package/{lib → dist/cjs}/index.js +0 -0
- /package/{lib → dist/cjs}/logger.js +0 -0
- /package/{lib → dist/cjs}/plugins/decode-key/index.js +0 -0
- /package/{lib → dist/cjs}/plugins/dry-run/cli.js +0 -0
- /package/{lib → dist/cjs}/plugins/dry-run/dry-run-extrinsic.js +0 -0
- /package/{lib → dist/cjs}/plugins/dry-run/index.js +0 -0
- /package/{lib → dist/cjs}/plugins/dry-run/rpc.js +0 -0
- /package/{lib → dist/cjs}/plugins/index.js +0 -0
- /package/{lib → dist/cjs}/plugins/new-block/index.js +0 -0
- /package/{lib → dist/cjs}/plugins/set-block-build-mode/index.js +0 -0
- /package/{lib → dist/cjs}/plugins/set-head/index.js +0 -0
- /package/{lib → dist/cjs}/plugins/set-runtime-log-level/index.js +0 -0
- /package/{lib → dist/cjs}/plugins/set-storage/index.js +0 -0
- /package/{lib → dist/cjs}/plugins/time-travel/index.js +0 -0
- /package/{lib → dist/cjs}/plugins/try-runtime/index.js +0 -0
- /package/{lib → dist/cjs}/plugins/types.js +0 -0
- /package/{lib → dist/cjs}/rpc/index.js +0 -0
- /package/{lib → dist/cjs}/schema/index.js +0 -0
- /package/{lib → dist/cjs}/server.js +0 -0
- /package/{lib → dist/cjs}/setup-with-server.js +0 -0
- /package/{lib → dist/cjs}/types.js +0 -0
- /package/{lib → dist/cjs}/utils/decoder.js +0 -0
- /package/{lib → dist/cjs}/utils/index.js +0 -0
- /package/{lib → dist/cjs}/utils/open-html.js +0 -0
- /package/{lib → dist/cjs}/utils/override.js +0 -0
- /package/{lib → dist/cjs}/utils/tunnel.js +0 -0
- /package/{lib/index.d.ts → dist/esm/index.js} +0 -0
- /package/{lib/logger.d.ts → dist/esm/logger.js} +0 -0
- /package/{lib/plugins/dry-run/index.d.ts → dist/esm/plugins/dry-run/index.js} +0 -0
- /package/{lib/utils/index.d.ts → dist/esm/utils/index.js} +0 -0
- /package/{lib → dist/types}/cli-options.d.ts +0 -0
- /package/{lib → dist/types}/cli.d.ts +0 -0
- /package/{lib → dist/types}/context.d.ts +0 -0
- /package/{lib → dist/types}/plugins/decode-key/index.d.ts +0 -0
- /package/{lib → dist/types}/plugins/dry-run/cli.d.ts +0 -0
- /package/{lib → dist/types}/plugins/dry-run/dry-run-extrinsic.d.ts +0 -0
- /package/{lib → dist/types}/plugins/dry-run/dry-run-preimage.d.ts +0 -0
- /package/{lib → dist/types}/plugins/dry-run/rpc.d.ts +0 -0
- /package/{lib → dist/types}/plugins/index.d.ts +0 -0
- /package/{lib → dist/types}/plugins/new-block/index.d.ts +0 -0
- /package/{lib → dist/types}/plugins/set-block-build-mode/index.d.ts +0 -0
- /package/{lib → dist/types}/plugins/set-head/index.d.ts +0 -0
- /package/{lib → dist/types}/plugins/set-runtime-log-level/index.d.ts +0 -0
- /package/{lib → dist/types}/plugins/set-storage/index.d.ts +0 -0
- /package/{lib → dist/types}/plugins/time-travel/index.d.ts +0 -0
- /package/{lib → dist/types}/plugins/try-runtime/index.d.ts +0 -0
- /package/{lib → dist/types}/rpc/index.d.ts +0 -0
- /package/{lib → dist/types}/schema/index.d.ts +0 -0
- /package/{lib → dist/types}/server.d.ts +0 -0
- /package/{lib → dist/types}/setup-with-server.d.ts +0 -0
- /package/{lib → dist/types}/types.d.ts +0 -0
- /package/{lib → dist/types}/utils/decoder.d.ts +0 -0
- /package/{lib → dist/types}/utils/generate-html-diff.d.ts +0 -0
- /package/{lib → dist/types}/utils/open-html.d.ts +0 -0
- /package/{lib → dist/types}/utils/override.d.ts +0 -0
- /package/{lib → dist/types}/utils/tunnel.d.ts +0 -0
package/chopsticks.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
require('./
|
|
2
|
+
require('./dist/cjs/cli.js')
|
|
@@ -69,9 +69,7 @@ const dryRunPreimage = async (argv) => {
|
|
|
69
69
|
if ('Error' in result) {
|
|
70
70
|
throw new Error(result.Error);
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
logger_1.defaultLogger.info(`RuntimeLogs:\n${logs}`);
|
|
74
|
-
}
|
|
72
|
+
(0, chopsticks_core_1.printRuntimeLogs)(result.Call.runtimeLogs);
|
|
75
73
|
const filePath = await (0, generate_html_diff_1.generateHtmlDiffPreviewFile)(block, result.Call.storageDiff, hash);
|
|
76
74
|
console.log(`Generated preview ${filePath}`);
|
|
77
75
|
if (argv['open']) {
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.rpc = exports.name = exports.cli = void 0;
|
|
7
4
|
const node_fs_1 = require("node:fs");
|
|
8
5
|
const zod_1 = require("zod");
|
|
9
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
10
6
|
const chopsticks_core_1 = require("@acala-network/chopsticks-core");
|
|
11
|
-
const logger_1 = require("../../logger");
|
|
12
7
|
const cli_options_1 = require("../../cli-options");
|
|
13
8
|
const generate_html_diff_1 = require("../../utils/generate-html-diff");
|
|
14
9
|
const open_html_1 = require("../../utils/open-html");
|
|
@@ -50,9 +45,7 @@ const cli = (y) => {
|
|
|
50
45
|
if ('Error' in result) {
|
|
51
46
|
throw new Error(result.Error);
|
|
52
47
|
}
|
|
53
|
-
|
|
54
|
-
logger_1.defaultLogger.info(`RuntimeLogs:\n${logs}`);
|
|
55
|
-
}
|
|
48
|
+
(0, chopsticks_core_1.printRuntimeLogs)(result.Call.runtimeLogs);
|
|
56
49
|
if (argv.html) {
|
|
57
50
|
const filePath = await (0, generate_html_diff_1.generateHtmlDiffPreviewFile)(parent, result.Call.storageDiff, block.hash);
|
|
58
51
|
console.log(`Generated preview ${filePath}`);
|
|
@@ -100,6 +93,7 @@ const rpc = async ({ chain }, [params]) => {
|
|
|
100
93
|
const registry = await parentBlock.registry;
|
|
101
94
|
const header = registry.createType('Header', block.header);
|
|
102
95
|
const wasm = await parentBlock.wasm;
|
|
96
|
+
const meta = await parentBlock.meta;
|
|
103
97
|
const blockNumber = parentBlock.number + 1;
|
|
104
98
|
const hash = `0x${Math.round(Math.random() * 100000000)
|
|
105
99
|
.toString(16)
|
|
@@ -112,6 +106,8 @@ const rpc = async ({ chain }, [params]) => {
|
|
|
112
106
|
const resp = {
|
|
113
107
|
phases: [],
|
|
114
108
|
};
|
|
109
|
+
// exclude system events because it can be stupidly large and redudant
|
|
110
|
+
const systemEventsKey = (0, chopsticks_core_1.compactHex)(meta.query.system.events());
|
|
115
111
|
const run = async (fn, args) => {
|
|
116
112
|
const result = await (0, chopsticks_core_1.runTask)({
|
|
117
113
|
wasm,
|
|
@@ -123,23 +119,21 @@ const rpc = async ({ chain }, [params]) => {
|
|
|
123
119
|
if ('Error' in result) {
|
|
124
120
|
throw new Error(result.Error);
|
|
125
121
|
}
|
|
126
|
-
const resp = {};
|
|
122
|
+
const resp = { storageDiff: [] };
|
|
127
123
|
const raw = result.Call.storageDiff;
|
|
128
124
|
newBlock.pushStorageLayer().setAll(raw);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
lodash_1.default.merge(parsed, (0, chopsticks_core_1.decodeKeyValue)(meta, newBlock, key, value, false));
|
|
125
|
+
for (const [key, value] of raw) {
|
|
126
|
+
if (key === systemEventsKey) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const obj = {};
|
|
130
|
+
if (includeRawStorage) {
|
|
131
|
+
obj.raw = { key, value };
|
|
137
132
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
delete parsed['system']['events'];
|
|
133
|
+
if (includeParsed) {
|
|
134
|
+
obj.parsed = (0, chopsticks_core_1.decodeKeyValue)(await newBlock.meta, newBlock, key, value, false);
|
|
141
135
|
}
|
|
142
|
-
resp.
|
|
136
|
+
resp.storageDiff.push(obj);
|
|
143
137
|
}
|
|
144
138
|
resp.logs = result.Call.runtimeLogs;
|
|
145
139
|
return resp;
|
|
@@ -10,7 +10,7 @@ const lodash_1 = require("lodash");
|
|
|
10
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
11
|
const generateHtmlDiff = async (block, diff) => {
|
|
12
12
|
const { oldState, delta } = await (0, decoder_1.decodeStorageDiff)(block, diff);
|
|
13
|
-
const htmlTemplate = (0, node_fs_1.readFileSync)(node_path_1.default.join(__dirname, '
|
|
13
|
+
const htmlTemplate = (0, node_fs_1.readFileSync)(node_path_1.default.join(__dirname, '../../../template/diff.html'), 'utf-8');
|
|
14
14
|
return (0, lodash_1.template)(htmlTemplate)({ left: JSON.stringify(oldState), delta: JSON.stringify(delta) });
|
|
15
15
|
};
|
|
16
16
|
exports.generateHtmlDiff = generateHtmlDiff;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const defaultOptions = {
|
|
2
|
+
endpoint: {
|
|
3
|
+
desc: 'Endpoint to connect to',
|
|
4
|
+
string: true,
|
|
5
|
+
},
|
|
6
|
+
block: {
|
|
7
|
+
desc: 'Block hash or block number. Default to latest block',
|
|
8
|
+
string: true,
|
|
9
|
+
},
|
|
10
|
+
'wasm-override': {
|
|
11
|
+
desc: 'Path to wasm override',
|
|
12
|
+
string: true,
|
|
13
|
+
},
|
|
14
|
+
db: {
|
|
15
|
+
desc: 'Path to database',
|
|
16
|
+
string: true,
|
|
17
|
+
},
|
|
18
|
+
config: {
|
|
19
|
+
desc: 'Path to config file with default options',
|
|
20
|
+
string: true,
|
|
21
|
+
},
|
|
22
|
+
'runtime-log-level': {
|
|
23
|
+
desc: 'Runtime maximum log level [off = 0; error = 1; warn = 2; info = 3; debug = 4; trace = 5]',
|
|
24
|
+
number: true,
|
|
25
|
+
},
|
|
26
|
+
'offchain-worker': {
|
|
27
|
+
desc: 'Enable offchain worker',
|
|
28
|
+
boolean: true,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export const mockOptions = {
|
|
32
|
+
'import-storage': {
|
|
33
|
+
desc: 'Pre-defined JSON/YAML storage file path',
|
|
34
|
+
string: true,
|
|
35
|
+
},
|
|
36
|
+
'mock-signature-host': {
|
|
37
|
+
desc: 'Mock signature host so any signature starts with 0xdeadbeef and filled by 0xcd is considered valid',
|
|
38
|
+
boolean: true,
|
|
39
|
+
},
|
|
40
|
+
};
|
package/dist/esm/cli.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
2
|
+
import { hideBin } from 'yargs/helpers';
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
import yargs from 'yargs';
|
|
5
|
+
import { BuildBlockMode, connectParachains, connectVertical } from '@acala-network/chopsticks-core';
|
|
6
|
+
import { fetchConfig } from './schema';
|
|
7
|
+
import { defaultOptions, mockOptions } from './cli-options';
|
|
8
|
+
import { pluginExtendCli } from './plugins';
|
|
9
|
+
import { setupWithServer } from '.';
|
|
10
|
+
dotenvConfig();
|
|
11
|
+
const processArgv = async (argv) => {
|
|
12
|
+
if (argv.config) {
|
|
13
|
+
Object.assign(argv, _.defaults(argv, await fetchConfig(argv.config)));
|
|
14
|
+
}
|
|
15
|
+
argv.port = argv.port ?? (process.env.PORT ? Number(process.env.PORT) : 8000);
|
|
16
|
+
};
|
|
17
|
+
const commands = yargs(hideBin(process.argv))
|
|
18
|
+
.scriptName('chopsticks')
|
|
19
|
+
.middleware(processArgv, false)
|
|
20
|
+
.command('*', 'Dev mode, fork off a chain', (yargs) => yargs.options({
|
|
21
|
+
...defaultOptions,
|
|
22
|
+
...mockOptions,
|
|
23
|
+
port: {
|
|
24
|
+
desc: 'Port to listen on',
|
|
25
|
+
number: true,
|
|
26
|
+
},
|
|
27
|
+
'build-block-mode': {
|
|
28
|
+
desc: 'Build block mode. Default to Batch',
|
|
29
|
+
enum: [BuildBlockMode.Batch, BuildBlockMode.Manual, BuildBlockMode.Instant],
|
|
30
|
+
},
|
|
31
|
+
'allow-unresolved-imports': {
|
|
32
|
+
desc: 'Allow wasm unresolved imports',
|
|
33
|
+
boolean: true,
|
|
34
|
+
},
|
|
35
|
+
'max-memory-block-count': {
|
|
36
|
+
desc: 'Max memory block count',
|
|
37
|
+
number: true,
|
|
38
|
+
},
|
|
39
|
+
resume: {
|
|
40
|
+
desc: `Resume from the specified block hash or block number in db.
|
|
41
|
+
If true, it will resume from the latest block in db.
|
|
42
|
+
Note this will override the block option`,
|
|
43
|
+
string: true,
|
|
44
|
+
},
|
|
45
|
+
}), async (argv) => {
|
|
46
|
+
await setupWithServer(argv);
|
|
47
|
+
})
|
|
48
|
+
.command('xcm', 'XCM setup with relaychain and parachains', (yargs) => yargs
|
|
49
|
+
.options({
|
|
50
|
+
relaychain: {
|
|
51
|
+
desc: 'Relaychain config file path',
|
|
52
|
+
string: true,
|
|
53
|
+
},
|
|
54
|
+
parachain: {
|
|
55
|
+
desc: 'Parachain config file path',
|
|
56
|
+
type: 'array',
|
|
57
|
+
string: true,
|
|
58
|
+
required: true,
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
.alias('relaychain', 'r')
|
|
62
|
+
.alias('parachain', 'p'), async (argv) => {
|
|
63
|
+
const parachains = [];
|
|
64
|
+
for (const config of argv.parachain) {
|
|
65
|
+
const { chain } = await setupWithServer(await fetchConfig(config));
|
|
66
|
+
parachains.push(chain);
|
|
67
|
+
}
|
|
68
|
+
if (parachains.length > 1) {
|
|
69
|
+
await connectParachains(parachains);
|
|
70
|
+
}
|
|
71
|
+
if (argv.relaychain) {
|
|
72
|
+
const { chain: relaychain } = await setupWithServer(await fetchConfig(argv.relaychain));
|
|
73
|
+
for (const parachain of parachains) {
|
|
74
|
+
await connectVertical(relaychain, parachain);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
.strict()
|
|
79
|
+
.help()
|
|
80
|
+
.alias('help', 'h')
|
|
81
|
+
.alias('version', 'v')
|
|
82
|
+
.alias('config', 'c')
|
|
83
|
+
.alias('endpoint', 'e')
|
|
84
|
+
.alias('port', 'p')
|
|
85
|
+
.alias('block', 'b')
|
|
86
|
+
.alias('import-storage', 's')
|
|
87
|
+
.alias('wasm-override', 'w')
|
|
88
|
+
.usage('Usage: $0 <command> [options]')
|
|
89
|
+
.example('$0', '-c acala');
|
|
90
|
+
pluginExtendCli(commands).then(() => commands.parse());
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import './utils/tunnel';
|
|
2
|
+
import { GenesisProvider, defaultLogger, isUrl, setup, timeTravel } from '@acala-network/chopsticks-core';
|
|
3
|
+
import { SqliteDatabase } from '@acala-network/chopsticks-db';
|
|
4
|
+
import { overrideStorage, overrideWasm } from './utils/override';
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
const logger = defaultLogger.child({ name: 'setup-context' });
|
|
7
|
+
export const genesisFromUrl = async (url) => {
|
|
8
|
+
const getFile = async (url) => {
|
|
9
|
+
if (isUrl(url)) {
|
|
10
|
+
return axios.get(url).then((x) => x.data);
|
|
11
|
+
}
|
|
12
|
+
else if (typeof process === 'object') {
|
|
13
|
+
const { lstatSync, readFileSync } = await import('node:fs');
|
|
14
|
+
if (lstatSync(url).isFile()) {
|
|
15
|
+
return JSON.parse(String(readFileSync(url)));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
throw Error(`invalid genesis path or url ${url}`);
|
|
19
|
+
};
|
|
20
|
+
return new GenesisProvider(await getFile(url));
|
|
21
|
+
};
|
|
22
|
+
export const setupContext = async (argv, overrideParent = false) => {
|
|
23
|
+
let genesis;
|
|
24
|
+
if (argv.genesis) {
|
|
25
|
+
if (typeof argv.genesis === 'string') {
|
|
26
|
+
genesis = await genesisFromUrl(argv.genesis);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
genesis = new GenesisProvider(argv.genesis);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const chain = await setup({
|
|
33
|
+
endpoint: argv.endpoint,
|
|
34
|
+
block: argv.block,
|
|
35
|
+
genesis,
|
|
36
|
+
buildBlockMode: argv['build-block-mode'],
|
|
37
|
+
db: argv.db ? new SqliteDatabase(argv.db) : undefined,
|
|
38
|
+
mockSignatureHost: argv['mock-signature-host'],
|
|
39
|
+
allowUnresolvedImports: argv['allow-unresolved-imports'],
|
|
40
|
+
runtimeLogLevel: argv['runtime-log-level'],
|
|
41
|
+
registeredTypes: argv['registered-types'],
|
|
42
|
+
offchainWorker: argv['offchain-worker'],
|
|
43
|
+
maxMemoryBlockCount: argv['max-memory-block-count'],
|
|
44
|
+
});
|
|
45
|
+
// load block from db
|
|
46
|
+
if (chain.db) {
|
|
47
|
+
if (argv.resume) {
|
|
48
|
+
let blockData = null;
|
|
49
|
+
if (typeof argv.resume === 'string' && argv.resume.startsWith('0x')) {
|
|
50
|
+
blockData = await chain.db.queryBlock(argv.resume);
|
|
51
|
+
}
|
|
52
|
+
else if (typeof argv.resume === 'boolean' || argv.resume === 'true') {
|
|
53
|
+
blockData = await chain.db.queryHighestBlock();
|
|
54
|
+
}
|
|
55
|
+
else if (Number.isInteger(+argv.resume)) {
|
|
56
|
+
blockData = await chain.db.queryBlockByNumber(+argv.resume);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
throw new Error(`Resume failed. Invalid resume option ${argv.resume}`);
|
|
60
|
+
}
|
|
61
|
+
if (blockData) {
|
|
62
|
+
const block = await chain.loadBlockFromDB(blockData.number);
|
|
63
|
+
block && (await chain.setHead(block));
|
|
64
|
+
logger.info(`Resume from block ${blockData.number}, hash: ${blockData.hash}`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
throw new Error(`Resume failed. Cannot find block ${argv.resume}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (argv.timestamp)
|
|
72
|
+
await timeTravel(chain, argv.timestamp);
|
|
73
|
+
let at;
|
|
74
|
+
if (overrideParent) {
|
|
75
|
+
// in case of run block we need to apply wasm-override and import-storage to parent block
|
|
76
|
+
const block = await chain.head.parentBlock;
|
|
77
|
+
if (!block)
|
|
78
|
+
throw new Error('Cannot find parent block');
|
|
79
|
+
at = block.hash;
|
|
80
|
+
}
|
|
81
|
+
// override wasm before importing storage, in case new pallets have been
|
|
82
|
+
// added that have storage imports
|
|
83
|
+
await overrideStorage(chain, argv['import-storage'], at);
|
|
84
|
+
await overrideWasm(chain, argv['wasm-override'], at);
|
|
85
|
+
return { chain };
|
|
86
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { decodeKey } from '@acala-network/chopsticks-core';
|
|
2
|
+
import { defaultOptions } from '../../cli-options';
|
|
3
|
+
import { setupContext } from '../../context';
|
|
4
|
+
export const cli = (y) => {
|
|
5
|
+
y.command('decode-key <key>', 'Deocde a key', (yargs) => yargs
|
|
6
|
+
.positional('key', {
|
|
7
|
+
desc: 'Key to decode',
|
|
8
|
+
type: 'string',
|
|
9
|
+
})
|
|
10
|
+
.options({
|
|
11
|
+
...defaultOptions,
|
|
12
|
+
}), async (argv) => {
|
|
13
|
+
const context = await setupContext(argv);
|
|
14
|
+
const { storage, decodedKey } = decodeKey(await context.chain.head.meta, context.chain.head, argv.key);
|
|
15
|
+
if (storage && decodedKey) {
|
|
16
|
+
console.log(`${storage.section}.${storage.method}`, decodedKey.args.map((x) => JSON.stringify(x.toHuman())).join(', '));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
console.log('Unknown');
|
|
20
|
+
}
|
|
21
|
+
process.exit(0);
|
|
22
|
+
});
|
|
23
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { defaultOptions, mockOptions } from '../../cli-options';
|
|
2
|
+
import { dryRunExtrinsic } from './dry-run-extrinsic';
|
|
3
|
+
import { dryRunPreimage } from './dry-run-preimage';
|
|
4
|
+
export const cli = (y) => {
|
|
5
|
+
y.command('dry-run', 'Dry run an extrinsic', (yargs) => yargs.options({
|
|
6
|
+
...defaultOptions,
|
|
7
|
+
...mockOptions,
|
|
8
|
+
extrinsic: {
|
|
9
|
+
desc: 'Extrinsic or call to dry run. If you pass call here then address is required to fake signature',
|
|
10
|
+
string: true,
|
|
11
|
+
},
|
|
12
|
+
address: {
|
|
13
|
+
desc: 'Address to fake sign extrinsic',
|
|
14
|
+
string: true,
|
|
15
|
+
},
|
|
16
|
+
preimage: {
|
|
17
|
+
desc: 'Preimage to dry run',
|
|
18
|
+
string: true,
|
|
19
|
+
},
|
|
20
|
+
at: {
|
|
21
|
+
desc: 'Block hash to dry run',
|
|
22
|
+
string: true,
|
|
23
|
+
},
|
|
24
|
+
'output-path': {
|
|
25
|
+
desc: 'File path to print output',
|
|
26
|
+
string: true,
|
|
27
|
+
},
|
|
28
|
+
html: {
|
|
29
|
+
desc: 'Generate html with storage diff',
|
|
30
|
+
},
|
|
31
|
+
open: {
|
|
32
|
+
desc: 'Open generated html',
|
|
33
|
+
},
|
|
34
|
+
}), async (argv) => {
|
|
35
|
+
if (argv.preimage) {
|
|
36
|
+
await dryRunPreimage(argv);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
await dryRunExtrinsic(argv);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { blake2AsHex } from '@polkadot/util-crypto';
|
|
2
|
+
import { writeFileSync } from 'node:fs';
|
|
3
|
+
import { defaultLogger } from '../../logger';
|
|
4
|
+
import { generateHtmlDiffPreviewFile } from '../../utils/generate-html-diff';
|
|
5
|
+
import { openHtml } from '../../utils/open-html';
|
|
6
|
+
import { setupContext } from '../../context';
|
|
7
|
+
export const dryRunExtrinsic = async (argv) => {
|
|
8
|
+
const context = await setupContext(argv);
|
|
9
|
+
const input = argv['address'] ? { call: argv['extrinsic'], address: argv['address'] } : argv['extrinsic'];
|
|
10
|
+
const { outcome, storageDiff } = await context.chain.dryRunExtrinsic(input, argv['at']);
|
|
11
|
+
if (outcome.isErr) {
|
|
12
|
+
throw new Error(outcome.asErr.toString());
|
|
13
|
+
}
|
|
14
|
+
defaultLogger.info(outcome.toHuman(), 'dry_run_outcome');
|
|
15
|
+
if (argv['html']) {
|
|
16
|
+
const filePath = await generateHtmlDiffPreviewFile(context.chain.head, storageDiff, blake2AsHex(argv['extrinsic'], 256));
|
|
17
|
+
console.log(`Generated preview ${filePath}`);
|
|
18
|
+
if (argv['open']) {
|
|
19
|
+
openHtml(filePath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else if (argv['output-path']) {
|
|
23
|
+
writeFileSync(argv['output-path'], JSON.stringify({ outcome: outcome.toHuman(), storageDiff }, null, 2));
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
console.dir({ outcome: outcome.toHuman(), storageDiff }, { depth: null, colors: false });
|
|
27
|
+
}
|
|
28
|
+
process.exit(0);
|
|
29
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { blake2AsHex } from '@polkadot/util-crypto';
|
|
2
|
+
import { hexToU8a } from '@polkadot/util';
|
|
3
|
+
import { defaultLogger } from '../../logger';
|
|
4
|
+
import { generateHtmlDiffPreviewFile } from '../../utils/generate-html-diff';
|
|
5
|
+
import { newHeader, printRuntimeLogs, runTask, setStorage, taskHandler } from '@acala-network/chopsticks-core';
|
|
6
|
+
import { openHtml } from '../../utils/open-html';
|
|
7
|
+
import { setupContext } from '../../context';
|
|
8
|
+
export const dryRunPreimage = async (argv) => {
|
|
9
|
+
const context = await setupContext(argv);
|
|
10
|
+
const extrinsic = argv['preimage'];
|
|
11
|
+
const block = context.chain.head;
|
|
12
|
+
const registry = await block.registry;
|
|
13
|
+
const header = await newHeader(block);
|
|
14
|
+
const data = hexToU8a(extrinsic);
|
|
15
|
+
const hash = blake2AsHex(data, 256);
|
|
16
|
+
await setStorage(context.chain, {
|
|
17
|
+
Preimage: {
|
|
18
|
+
PreimageFor: [[[[hash, data.byteLength]], extrinsic]],
|
|
19
|
+
StatusFor: [
|
|
20
|
+
[
|
|
21
|
+
[hash],
|
|
22
|
+
{
|
|
23
|
+
Requested: {
|
|
24
|
+
count: 1,
|
|
25
|
+
len: data.byteLength,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
Scheduler: {
|
|
32
|
+
Agenda: [
|
|
33
|
+
[
|
|
34
|
+
[block.number + 1],
|
|
35
|
+
[
|
|
36
|
+
{
|
|
37
|
+
maybeId: '0x64656d6f637261633a0000000000000000000000000000000000000000000000',
|
|
38
|
+
priority: 63,
|
|
39
|
+
call: {
|
|
40
|
+
Lookup: {
|
|
41
|
+
hash: hash,
|
|
42
|
+
len: data.byteLength,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
origin: { system: { Root: null } },
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
],
|
|
49
|
+
],
|
|
50
|
+
Lookup: [[['0x64656d6f637261633a0000000000000000000000000000000000000000000000'], [block.number + 1, 0]]],
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
const calls = [['Core_initialize_block', [header.toHex()]]];
|
|
54
|
+
for (const inherent of await block.chain.getInherents()) {
|
|
55
|
+
calls.push(['BlockBuilder_apply_extrinsic', [inherent]]);
|
|
56
|
+
}
|
|
57
|
+
calls.push(['BlockBuilder_finalize_block', []]);
|
|
58
|
+
defaultLogger.info({ preimage: registry.createType('Call', data).toHuman() }, 'Dry run preimage');
|
|
59
|
+
const result = await runTask({
|
|
60
|
+
wasm: await block.wasm,
|
|
61
|
+
calls,
|
|
62
|
+
mockSignatureHost: false,
|
|
63
|
+
allowUnresolvedImports: false,
|
|
64
|
+
runtimeLogLevel: argv['runtime-log-level'] || 0,
|
|
65
|
+
}, taskHandler(block));
|
|
66
|
+
if ('Error' in result) {
|
|
67
|
+
throw new Error(result.Error);
|
|
68
|
+
}
|
|
69
|
+
printRuntimeLogs(result.Call.runtimeLogs);
|
|
70
|
+
const filePath = await generateHtmlDiffPreviewFile(block, result.Call.storageDiff, hash);
|
|
71
|
+
console.log(`Generated preview ${filePath}`);
|
|
72
|
+
if (argv['open']) {
|
|
73
|
+
openHtml(filePath);
|
|
74
|
+
}
|
|
75
|
+
// if dry-run preimage has extrinsic arguments then dry-run extrinsic
|
|
76
|
+
// this is usefull to test something after preimage is applied
|
|
77
|
+
if (argv['extrinsic']) {
|
|
78
|
+
await context.chain.newBlock();
|
|
79
|
+
const input = argv['address'] ? { call: argv['extrinsic'], address: argv['address'] } : argv['extrinsic'];
|
|
80
|
+
const { outcome, storageDiff } = await context.chain.dryRunExtrinsic(input);
|
|
81
|
+
if (outcome.isErr) {
|
|
82
|
+
throw new Error(outcome.asErr.toString());
|
|
83
|
+
}
|
|
84
|
+
defaultLogger.info(outcome.toHuman(), 'dry_run_outcome');
|
|
85
|
+
const filePath = await generateHtmlDiffPreviewFile(context.chain.head, storageDiff, blake2AsHex(argv['extrinsic'], 256));
|
|
86
|
+
console.log(`Generated preview ${filePath}`);
|
|
87
|
+
if (argv['open']) {
|
|
88
|
+
openHtml(filePath);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
process.exit(0);
|
|
92
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ResponseError } from '@acala-network/chopsticks-core';
|
|
3
|
+
import { decodeStorageDiff } from '../../utils/decoder';
|
|
4
|
+
import { generateHtmlDiff } from '../../utils/generate-html-diff';
|
|
5
|
+
const zHex = z.custom((val) => /^0x\w+$/.test(val));
|
|
6
|
+
const zHash = z.string().length(66).and(zHex);
|
|
7
|
+
const zParaId = z.string().regex(/^\d+$/).transform(Number);
|
|
8
|
+
const schema = z.object({
|
|
9
|
+
raw: z.boolean().optional(),
|
|
10
|
+
html: z.boolean().optional(),
|
|
11
|
+
extrinsic: zHex
|
|
12
|
+
.or(z.object({
|
|
13
|
+
call: zHex,
|
|
14
|
+
address: zHex,
|
|
15
|
+
}))
|
|
16
|
+
.optional(),
|
|
17
|
+
hrmp: z
|
|
18
|
+
.record(zParaId, z
|
|
19
|
+
.array(z.object({
|
|
20
|
+
sentAt: z.number(),
|
|
21
|
+
data: zHex,
|
|
22
|
+
}))
|
|
23
|
+
.min(1))
|
|
24
|
+
.optional(),
|
|
25
|
+
dmp: z
|
|
26
|
+
.array(z.object({
|
|
27
|
+
sentAt: z.number(),
|
|
28
|
+
msg: zHex,
|
|
29
|
+
}))
|
|
30
|
+
.min(1)
|
|
31
|
+
.optional(),
|
|
32
|
+
ump: z.record(zParaId, z.array(zHex).min(1)).optional(),
|
|
33
|
+
at: zHash.optional(),
|
|
34
|
+
});
|
|
35
|
+
// custom rpc name (optional). e.g. dryRun will be called as dev_dryRun
|
|
36
|
+
export const name = 'dryRun';
|
|
37
|
+
/**
|
|
38
|
+
* Dry run an extrinsic or messages.
|
|
39
|
+
* If `html` is true, return the generated storage diff html string.
|
|
40
|
+
* If `raw` is true, return the raw storage diff.
|
|
41
|
+
* Otherwise, return `{ oldState, newState, delta }`.
|
|
42
|
+
*
|
|
43
|
+
* This function is a dev rpc handler. Use `dev_dryRun` as the method name when calling it.
|
|
44
|
+
*
|
|
45
|
+
* @param context - The context object of the rpc handler
|
|
46
|
+
* @param params - The parameters of the rpc handler
|
|
47
|
+
*
|
|
48
|
+
* @example Dry run an dmp
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { WsProvider } from '@polkadot/rpc-provider'
|
|
51
|
+
* const ws = new WsProvider(`ws://localhost:8000`)
|
|
52
|
+
* const params = [
|
|
53
|
+
{
|
|
54
|
+
raw: false,
|
|
55
|
+
dmp: [
|
|
56
|
+
// https://acala.subscan.io/xcm_message/polkadot-2ab22918c567455af3563989d852f307f4cc1250
|
|
57
|
+
{
|
|
58
|
+
sentAt: 14471353,
|
|
59
|
+
msg: '0x02100104000100000b00280b9bba030a13000100000b00280b9bba03010300286bee0d0100040001010070c53d8e216f9c0f2e3b11c53f5f4bf3e078b995d5f0ed590f889f41e20e6531',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
]
|
|
64
|
+
* await ws.send('dev_dryRun', params)
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export const rpc = async (context, [params]) => {
|
|
68
|
+
const { html, extrinsic, hrmp, dmp, ump, raw, at } = schema.parse(params);
|
|
69
|
+
const dryRun = async () => {
|
|
70
|
+
if (extrinsic) {
|
|
71
|
+
const { outcome, storageDiff } = await context.chain.dryRunExtrinsic(extrinsic, at);
|
|
72
|
+
if (outcome.isErr) {
|
|
73
|
+
throw new ResponseError(1, outcome.asErr.toString());
|
|
74
|
+
}
|
|
75
|
+
return storageDiff;
|
|
76
|
+
}
|
|
77
|
+
if (hrmp) {
|
|
78
|
+
return context.chain.dryRunHrmp(hrmp, at);
|
|
79
|
+
}
|
|
80
|
+
if (dmp) {
|
|
81
|
+
return context.chain.dryRunDmp(dmp, at);
|
|
82
|
+
}
|
|
83
|
+
if (ump) {
|
|
84
|
+
return context.chain.dryRunUmp(ump, at);
|
|
85
|
+
}
|
|
86
|
+
throw new ResponseError(1, 'No extrinsic to run');
|
|
87
|
+
};
|
|
88
|
+
const storageDiff = await dryRun();
|
|
89
|
+
if (html) {
|
|
90
|
+
return generateHtmlDiff(context.chain.head, storageDiff);
|
|
91
|
+
}
|
|
92
|
+
if (raw) {
|
|
93
|
+
return storageDiff;
|
|
94
|
+
}
|
|
95
|
+
const { oldState, newState, delta } = await decodeStorageDiff(context.chain.head, storageDiff);
|
|
96
|
+
return {
|
|
97
|
+
old: oldState,
|
|
98
|
+
new: newState,
|
|
99
|
+
delta,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { camelCase } from 'lodash';
|
|
2
|
+
import { lstatSync, readdirSync } from 'fs';
|
|
3
|
+
import { defaultLogger } from '../logger';
|
|
4
|
+
const logger = defaultLogger.child({ name: 'plugin' });
|
|
5
|
+
export const pluginHandlers = {};
|
|
6
|
+
const plugins = readdirSync(__dirname).filter((file) => lstatSync(`${__dirname}/${file}`).isDirectory());
|
|
7
|
+
(async () => {
|
|
8
|
+
for (const plugin of plugins) {
|
|
9
|
+
const { rpc, name } = await import(`./${plugin}`);
|
|
10
|
+
if (rpc) {
|
|
11
|
+
const methodName = name || camelCase(plugin);
|
|
12
|
+
pluginHandlers[`dev_${methodName}`] = rpc;
|
|
13
|
+
logger.debug(`Registered plugin ${plugin} RPC`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
17
|
+
export const pluginExtendCli = async (y) => {
|
|
18
|
+
for (const plugin of plugins) {
|
|
19
|
+
const { cli } = await import(`./${plugin}`);
|
|
20
|
+
if (cli) {
|
|
21
|
+
cli(y);
|
|
22
|
+
logger.debug(`Registered plugin ${plugin} CLI`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|