@acala-network/chopsticks 0.6.4 → 0.6.5

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.
@@ -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))) {
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.5",
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.5",
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",