@acala-network/chopsticks 0.8.5-4 → 0.8.5-6
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 +1 -4
- 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/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/run-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']) {
|
|
@@ -8,7 +8,6 @@ const node_fs_1 = require("node:fs");
|
|
|
8
8
|
const zod_1 = require("zod");
|
|
9
9
|
const lodash_1 = __importDefault(require("lodash"));
|
|
10
10
|
const chopsticks_core_1 = require("@acala-network/chopsticks-core");
|
|
11
|
-
const logger_1 = require("../../logger");
|
|
12
11
|
const cli_options_1 = require("../../cli-options");
|
|
13
12
|
const generate_html_diff_1 = require("../../utils/generate-html-diff");
|
|
14
13
|
const open_html_1 = require("../../utils/open-html");
|
|
@@ -50,9 +49,7 @@ const cli = (y) => {
|
|
|
50
49
|
if ('Error' in result) {
|
|
51
50
|
throw new Error(result.Error);
|
|
52
51
|
}
|
|
53
|
-
|
|
54
|
-
logger_1.defaultLogger.info(`RuntimeLogs:\n${logs}`);
|
|
55
|
-
}
|
|
52
|
+
(0, chopsticks_core_1.printRuntimeLogs)(result.Call.runtimeLogs);
|
|
56
53
|
if (argv.html) {
|
|
57
54
|
const filePath = await (0, generate_html_diff_1.generateHtmlDiffPreviewFile)(parent, result.Call.storageDiff, block.hash);
|
|
58
55
|
console.log(`Generated preview ${filePath}`);
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ResponseError } from '@acala-network/chopsticks-core';
|
|
2
|
+
import { defaultLogger } from '../../logger';
|
|
3
|
+
/**
|
|
4
|
+
* Build new blocks.
|
|
5
|
+
*
|
|
6
|
+
* This function is a dev rpc handler. Use `dev_newBlock` as the method name when calling it.
|
|
7
|
+
*
|
|
8
|
+
* @param context - The context object of the rpc handler
|
|
9
|
+
* @param params - The parameters of the rpc handler
|
|
10
|
+
*
|
|
11
|
+
* @example Build 2 blocks
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { WsProvider } from '@polkadot/rpc-provider'
|
|
14
|
+
* const ws = new WsProvider(`ws://localhost:8000`)
|
|
15
|
+
* await ws.send('dev_newBlock', [{ count: 2 }])
|
|
16
|
+
* ```
|
|
17
|
+
* @example Build a block with upward messages
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { WsProvider } from '@polkadot/rpc-provider'
|
|
20
|
+
* const ws = new WsProvider(`ws://localhost:8000`)
|
|
21
|
+
* await ws.send('dev_newBlock', [
|
|
22
|
+
* {
|
|
23
|
+
* ump: {
|
|
24
|
+
* // https://acala.subscan.io/xcm_message/polkadot-ff66f28818d0b74573e62db8317e354b253fbc80
|
|
25
|
+
* 2000: [
|
|
26
|
+
* '0x021000040000000007903fc4db080a130000000007903fc4db08000d010004000101009c4b11a0974cba4a395c94832fba812868a6cb0ba09e8519b3521093ea359905',
|
|
27
|
+
* ],
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* ])
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example Build two blocks with unsafeBlockHeight
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { WsProvider } from '@polkadot/rpc-provider'
|
|
36
|
+
* const ws = new WsProvider(`ws://localhost:8000`)
|
|
37
|
+
* // this will create two blocks with block height 100000001 and 100000002
|
|
38
|
+
* await ws.send('dev_newBlock', [{ count: 2, unsafeBlockHeight: 100000001 }])
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export const rpc = async (context, params) => {
|
|
42
|
+
const [param] = params;
|
|
43
|
+
const { count, to, hrmp, ump, dmp, transactions, unsafeBlockHeight } = param || {};
|
|
44
|
+
const now = context.chain.head.number;
|
|
45
|
+
const diff = to ? to - now : count;
|
|
46
|
+
const finalCount = diff > 0 ? diff : 1;
|
|
47
|
+
let finalHash;
|
|
48
|
+
if (unsafeBlockHeight < now) {
|
|
49
|
+
throw new ResponseError(1, 'unsafeBlockHeight must be greater than current block height');
|
|
50
|
+
}
|
|
51
|
+
for (let i = 0; i < finalCount; i++) {
|
|
52
|
+
const block = await context.chain
|
|
53
|
+
.newBlock({
|
|
54
|
+
transactions,
|
|
55
|
+
horizontalMessages: hrmp,
|
|
56
|
+
upwardMessages: ump,
|
|
57
|
+
downwardMessages: dmp,
|
|
58
|
+
unsafeBlockHeight: i === 0 ? unsafeBlockHeight : undefined,
|
|
59
|
+
})
|
|
60
|
+
.catch((error) => {
|
|
61
|
+
throw new ResponseError(1, error.toString());
|
|
62
|
+
});
|
|
63
|
+
defaultLogger.debug({ hash: block.hash }, 'dev_newBlock');
|
|
64
|
+
finalHash = block.hash;
|
|
65
|
+
}
|
|
66
|
+
return finalHash;
|
|
67
|
+
};
|