@acala-network/chopsticks 0.6.4 → 0.6.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.
@@ -135,20 +135,71 @@ const buildBlock = async (head, inherents, extrinsics, ump, onApplyExtrinsicErro
135
135
  const meta = await head.meta;
136
136
  layer = new storage_layer_1.StorageLayer(head.storage);
137
137
  for (const [paraId, upwardMessages] of Object.entries(ump)) {
138
- const queueSize = meta.registry.createType('(u32, u32)', [
139
- upwardMessages.length,
140
- upwardMessages.map((x) => x.length).reduce((s, i) => s + i, 0),
141
- ]);
142
- const messages = meta.registry.createType('Vec<Bytes>', upwardMessages);
143
- // TODO: make sure we append instead of replace
144
- layer.setAll([
145
- [(0, utils_1.compactHex)(meta.query.ump.relayDispatchQueues(paraId)), messages.toHex()],
146
- [(0, utils_1.compactHex)(meta.query.ump.relayDispatchQueueSize(paraId)), queueSize.toHex()],
147
- ]);
138
+ const upwardMessagesU8a = upwardMessages.map((x) => (0, util_1.hexToU8a)(x));
139
+ const messagesCount = upwardMessages.length;
140
+ const messagesSize = upwardMessagesU8a.map((x) => x.length).reduce((s, i) => s + i, 0);
141
+ if (meta.query.ump) {
142
+ const queueSize = meta.registry.createType('(u32, u32)', [messagesCount, messagesSize]);
143
+ const messages = meta.registry.createType('Vec<Bytes>', upwardMessages);
144
+ // TODO: make sure we append instead of replace
145
+ layer.setAll([
146
+ [(0, utils_1.compactHex)(meta.query.ump.relayDispatchQueues(paraId)), messages.toHex()],
147
+ [(0, utils_1.compactHex)(meta.query.ump.relayDispatchQueueSize(paraId)), queueSize.toHex()],
148
+ ]);
149
+ }
150
+ else if (meta.query.messageQueue) {
151
+ // TODO: make sure we append instead of replace
152
+ const origin = { ump: { para: paraId } };
153
+ let last = 0;
154
+ let heap = new Uint8Array(0);
155
+ for (const message of upwardMessagesU8a) {
156
+ const payloadLen = message.length;
157
+ const header = meta.registry.createType('(u32, bool)', [payloadLen, false]);
158
+ last = heap.length;
159
+ heap = (0, util_1.u8aConcat)(heap, header.toU8a(), message);
160
+ }
161
+ layer.setAll([
162
+ [
163
+ (0, utils_1.compactHex)(meta.query.messageQueue.bookStateFor(origin)),
164
+ meta.registry
165
+ .createType('PalletMessageQueueBookState', {
166
+ begin: 0,
167
+ end: 1,
168
+ count: 1,
169
+ readyNeighbours: { prev: origin, next: origin },
170
+ messageCount: messagesCount,
171
+ size_: messagesSize,
172
+ })
173
+ .toHex(),
174
+ ],
175
+ [
176
+ (0, utils_1.compactHex)(meta.query.messageQueue.serviceHead(origin)),
177
+ meta.registry.createType('PolkadotRuntimeParachainsInclusionAggregateMessageOrigin', origin).toHex(),
178
+ ],
179
+ [
180
+ (0, utils_1.compactHex)(meta.query.messageQueue.pages(origin, 0)),
181
+ meta.registry
182
+ .createType('PalletMessageQueuePage', {
183
+ remaining: messagesCount,
184
+ remaining_size: messagesSize,
185
+ first_index: 0,
186
+ first: 0,
187
+ last,
188
+ heap: (0, util_1.compactAddLength)(heap),
189
+ })
190
+ .toHex(),
191
+ ],
192
+ ]);
193
+ }
194
+ else {
195
+ throw new Error('Unknown ump storage');
196
+ }
148
197
  logger.trace({ paraId, upwardMessages: (0, logger_1.truncate)(upwardMessages) }, 'Pushed UMP');
149
198
  }
150
- const needsDispatch = meta.registry.createType('Vec<u32>', Object.keys(ump));
151
- layer.set((0, utils_1.compactHex)(meta.query.ump.needsDispatch()), needsDispatch.toHex());
199
+ if (meta.query.ump) {
200
+ const needsDispatch = meta.registry.createType('Vec<u32>', Object.keys(ump));
201
+ layer.set((0, utils_1.compactHex)(meta.query.ump.needsDispatch()), needsDispatch.toHex());
202
+ }
152
203
  }
153
204
  const { block: newBlock } = await initNewBlock(head, header, inherents, layer);
154
205
  logger.info({
@@ -6,11 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.StorageLayer = exports.RemoteStorageLayer = void 0;
7
7
  const lodash_1 = __importDefault(require("lodash"));
8
8
  const logger_1 = require("../logger");
9
+ const key_cache_1 = __importDefault(require("../utils/key-cache"));
9
10
  const logger = logger_1.defaultLogger.child({ name: 'layer' });
11
+ const BATCH_SIZE = 1000;
10
12
  class RemoteStorageLayer {
11
13
  #api;
12
14
  #at;
13
15
  #db;
16
+ #keyCache = new key_cache_1.default();
14
17
  constructor(api, at, db) {
15
18
  this.#api = api;
16
19
  this.#at = at;
@@ -33,8 +36,39 @@ class RemoteStorageLayer {
33
36
  }
34
37
  async fold() { }
35
38
  async getKeysPaged(prefix, pageSize, startKey) {
39
+ if (pageSize > BATCH_SIZE)
40
+ throw new Error(`pageSize must be less or equal to ${BATCH_SIZE}`);
36
41
  logger.trace({ at: this.#at, prefix, pageSize, startKey }, 'RemoteStorageLayer getKeysPaged');
37
- return this.#api.getKeysPaged(prefix, pageSize, startKey, this.#at);
42
+ // can't handle keyCache without prefix
43
+ if (prefix.length < 66) {
44
+ return this.#api.getKeysPaged(prefix, pageSize, startKey, this.#at);
45
+ }
46
+ let batchComplete = false;
47
+ const keysPaged = [];
48
+ while (keysPaged.length < pageSize) {
49
+ const nextKey = await this.#keyCache.next(startKey);
50
+ if (nextKey) {
51
+ keysPaged.push(nextKey);
52
+ startKey = nextKey;
53
+ continue;
54
+ }
55
+ // batch fetch was completed
56
+ if (batchComplete) {
57
+ break;
58
+ }
59
+ // fetch a batch of keys
60
+ const batch = await this.#api.getKeysPaged(prefix, BATCH_SIZE, startKey, this.#at);
61
+ batchComplete = batch.length < BATCH_SIZE;
62
+ // feed the key cache
63
+ if (batch.length > 0) {
64
+ this.#keyCache.feed([startKey, ...batch]);
65
+ }
66
+ if (batch.length === 0) {
67
+ // no more keys were found
68
+ break;
69
+ }
70
+ }
71
+ return keysPaged;
38
72
  }
39
73
  }
40
74
  exports.RemoteStorageLayer = RemoteStorageLayer;
@@ -128,7 +162,6 @@ class StorageLayer {
128
162
  }
129
163
  async getKeysPaged(prefix, pageSize, startKey) {
130
164
  if (!this.#deletedPrefix.some((prefix) => startKey.startsWith(prefix))) {
131
- // TODO: maintain a list of fetched ranges to avoid fetching the same range multiple times
132
165
  const remote = (await this.#parent?.getKeysPaged(prefix, pageSize, startKey)) ?? [];
133
166
  for (const key of remote) {
134
167
  if (this.#deletedPrefix.some((prefix) => key.startsWith(prefix))) {
@@ -157,8 +157,8 @@ class TxPool {
157
157
  }
158
158
  finally {
159
159
  this.#isBuilding = false;
160
- this.#buildBlockIfNeeded();
161
160
  }
161
+ this.#buildBlockIfNeeded();
162
162
  }
163
163
  async #buildBlock() {
164
164
  await this.#chain.api.isReady;
package/lib/executor.js CHANGED
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getAuraSlotDuration = exports.emptyTaskHandler = exports.taskHandler = exports.runTask = exports.createProof = exports.decodeProof = exports.calculateStateRoot = exports.getRuntimeVersion = void 0;
7
7
  const util_1 = require("@polkadot/util");
8
+ const key_cache_1 = require("./utils/key-cache");
8
9
  const chopsticks_executor_1 = require("@acala-network/chopsticks-executor");
9
10
  const logger_1 = require("./logger");
10
11
  const lodash_1 = __importDefault(require("lodash"));
@@ -48,16 +49,24 @@ const runTask = async (task, callback = exports.emptyTaskHandler) => {
48
49
  };
49
50
  exports.runTask = runTask;
50
51
  const taskHandler = (block) => {
52
+ const batchSize = 1000;
51
53
  return {
52
54
  getStorage: async function (key) {
53
55
  return block.get(key);
54
56
  },
55
57
  getPrefixKeys: async function (key) {
56
- return block.getKeysPaged({ prefix: key, pageSize: 1000, startKey: key });
58
+ let keys = [];
59
+ let startKey = key;
60
+ while (startKey) {
61
+ const batch = await block.getKeysPaged({ prefix: key.slice(0, key_cache_1.PREFIX_LENGTH), pageSize: batchSize, startKey });
62
+ keys = keys.concat(batch);
63
+ startKey = batch[batchSize - 1];
64
+ }
65
+ return keys;
57
66
  },
58
67
  getNextKey: async function (key) {
59
- const keys = await block.getKeysPaged({ prefix: '0x', pageSize: 1, startKey: key });
60
- return keys[0];
68
+ const [nextKey] = await block.getKeysPaged({ prefix: key.slice(0, key_cache_1.PREFIX_LENGTH), pageSize: 1, startKey: key });
69
+ return nextKey;
61
70
  },
62
71
  };
63
72
  };
@@ -4,7 +4,7 @@ export declare const logger: import("pino").default.Logger<{
4
4
  transport: {
5
5
  target: string;
6
6
  };
7
- } & import("pino").default.ChildLoggerOptions>;
7
+ }>;
8
8
  export declare class ResponseError extends Error {
9
9
  code: number;
10
10
  constructor(code: number, message: string);
@@ -0,0 +1,10 @@
1
+ import { HexString } from '@polkadot/util/types';
2
+ export declare const PREFIX_LENGTH = 66;
3
+ export default class KeyCache {
4
+ readonly ranges: Array<{
5
+ prefix: string;
6
+ keys: string[];
7
+ }>;
8
+ feed(keys: HexString[]): void;
9
+ next(startKey: HexString): Promise<HexString | undefined>;
10
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PREFIX_LENGTH = void 0;
7
+ const lodash_1 = __importDefault(require("lodash"));
8
+ // 0x + 32 module + 32 method
9
+ exports.PREFIX_LENGTH = 66;
10
+ class KeyCache {
11
+ ranges = [];
12
+ feed(keys) {
13
+ const _keys = keys.filter((key) => key.length >= exports.PREFIX_LENGTH);
14
+ if (_keys.length === 0)
15
+ return;
16
+ const startKey = _keys[0].slice(exports.PREFIX_LENGTH);
17
+ const endKey = _keys[_keys.length - 1].slice(exports.PREFIX_LENGTH);
18
+ const grouped = lodash_1.default.groupBy(_keys, (key) => key.slice(0, exports.PREFIX_LENGTH));
19
+ for (const [prefix, keys] of Object.entries(grouped)) {
20
+ const ranges = this.ranges.filter((range) => range.prefix === prefix);
21
+ if (ranges.length === 0) {
22
+ // no existing range with prefix
23
+ this.ranges.push({ prefix, keys: keys.map((i) => i.slice(exports.PREFIX_LENGTH)) });
24
+ continue;
25
+ }
26
+ let merged = false;
27
+ for (const range of ranges) {
28
+ const startPosition = lodash_1.default.sortedIndex(range.keys, startKey);
29
+ if (startPosition >= 0 && range.keys[startPosition] === startKey) {
30
+ // found existing range with prefix
31
+ range.keys.splice(startPosition, keys.length, ...keys.map((i) => i.slice(exports.PREFIX_LENGTH)));
32
+ merged = true;
33
+ break;
34
+ }
35
+ const endPosition = lodash_1.default.sortedIndex(range.keys, endKey);
36
+ if (endPosition >= 0 && range.keys[endPosition] === endKey) {
37
+ // found existing range with prefix
38
+ range.keys.splice(0, endPosition + 1, ...keys.map((i) => i.slice(exports.PREFIX_LENGTH)));
39
+ merged = true;
40
+ break;
41
+ }
42
+ }
43
+ // insert new prefix with range
44
+ if (!merged) {
45
+ this.ranges.push({ prefix, keys: keys.map((i) => i.slice(exports.PREFIX_LENGTH)) });
46
+ }
47
+ }
48
+ // TODO: merge ranges if they overlap
49
+ }
50
+ async next(startKey) {
51
+ if (startKey.length < exports.PREFIX_LENGTH)
52
+ return;
53
+ const prefix = startKey.slice(0, exports.PREFIX_LENGTH);
54
+ const key = startKey.slice(exports.PREFIX_LENGTH);
55
+ for (const range of this.ranges.filter((range) => range.prefix === prefix)) {
56
+ const index = lodash_1.default.sortedIndex(range.keys, key);
57
+ if (range.keys[index] !== key)
58
+ continue;
59
+ const nextKey = range.keys[index + 1];
60
+ if (nextKey) {
61
+ return [prefix, nextKey].join('');
62
+ }
63
+ }
64
+ }
65
+ }
66
+ exports.default = KeyCache;
@@ -4,6 +4,6 @@ export declare const logger: import("pino").default.Logger<{
4
4
  transport: {
5
5
  target: string;
6
6
  };
7
- } & import("pino").default.ChildLoggerOptions>;
7
+ }>;
8
8
  export declare const connectVertical: (relaychain: Blockchain, parachain: Blockchain) => Promise<void>;
9
9
  export declare const connectParachains: (parachains: Blockchain[]) => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acala-network/chopsticks",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "author": "Bryan Chen <xlchen1291@gmail.com>",
5
5
  "license": "Apache-2.0",
6
6
  "bin": "./chopsticks.js",
@@ -16,17 +16,17 @@
16
16
  "dev:moonbeam": "cd ../..; ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- --config=configs/moonbeam.yml"
17
17
  },
18
18
  "dependencies": {
19
- "@acala-network/chopsticks-executor": "0.6.4",
20
- "@polkadot/api": "^10.5.1",
19
+ "@acala-network/chopsticks-executor": "0.6.6",
20
+ "@polkadot/api": "^10.7.2",
21
21
  "axios": "^1.4.0",
22
22
  "js-yaml": "^4.1.0",
23
23
  "jsondiffpatch": "^0.4.1",
24
24
  "lodash": "^4.17.21",
25
- "pino": "^8.11.0",
25
+ "pino": "^8.14.1",
26
26
  "pino-pretty": "^10.0.0",
27
27
  "reflect-metadata": "^0.1.13",
28
28
  "sqlite3": "^5.1.6",
29
- "typeorm": "^0.3.15",
29
+ "typeorm": "^0.3.16",
30
30
  "ws": "^8.13.0",
31
31
  "yargs": "^17.7.2",
32
32
  "zod": "^3.21.4"
@@ -34,7 +34,7 @@
34
34
  "devDependencies": {
35
35
  "@types/js-yaml": "^4.0.5",
36
36
  "@types/lodash": "^4.14.194",
37
- "@types/node": "^18.16.3",
37
+ "@types/node": "^20.2.3",
38
38
  "@types/ws": "^8.5.4",
39
39
  "@types/yargs": "^17.0.24",
40
40
  "ts-node": "^10.9.1",