@acala-network/chopsticks-core 0.9.6 → 0.9.8-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/api.d.ts CHANGED
@@ -50,6 +50,7 @@ export declare class Api {
50
50
  getBlock(hash?: string): Promise<SignedBlock | null>;
51
51
  getStorage(key: string, hash?: string): Promise<`0x${string}` | null>;
52
52
  getKeysPaged(prefix: string, pageSize: number, startKey: string, hash?: string): Promise<string[]>;
53
+ getStorageBatch(prefix: HexString, keys: HexString[], hash?: HexString): Promise<[`0x${string}`, `0x${string}` | null][]>;
53
54
  subscribeRemoteNewHeads(cb: ProviderInterfaceCallback): Promise<string | number>;
54
55
  subscribeRemoteFinalizedHeads(cb: ProviderInterfaceCallback): Promise<string | number>;
55
56
  }
package/dist/cjs/api.js CHANGED
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "Api", {
9
9
  }
10
10
  });
11
11
  const _index = require("./utils/index.js");
12
+ const _lodash = /*#__PURE__*/ _interop_require_default(require("lodash"));
12
13
  function _check_private_redeclaration(obj, privateCollection) {
13
14
  if (privateCollection.has(obj)) {
14
15
  throw new TypeError("Cannot initialize the same private elements twice on an object");
@@ -62,6 +63,11 @@ function _define_property(obj, key, value) {
62
63
  }
63
64
  return obj;
64
65
  }
66
+ function _interop_require_default(obj) {
67
+ return obj && obj.__esModule ? obj : {
68
+ default: obj
69
+ };
70
+ }
65
71
  var _provider = /*#__PURE__*/ new WeakMap(), _ready = /*#__PURE__*/ new WeakMap(), _chain = /*#__PURE__*/ new WeakMap(), _chainProperties = /*#__PURE__*/ new WeakMap();
66
72
  class Api {
67
73
  async disconnect() {
@@ -163,6 +169,26 @@ class Api {
163
169
  return _class_private_field_get(this, _provider).send('state_getKeysPaged', params, !!hash);
164
170
  }
165
171
  }
172
+ async getStorageBatch(prefix, keys, hash) {
173
+ const [child] = (0, _index.splitChildKey)(prefix);
174
+ if (child) {
175
+ // child storage key, use childstate_getStorageEntries
176
+ // strip child prefix from keys
177
+ const params = [
178
+ child,
179
+ keys.map((key)=>(0, _index.stripChildPrefix)(key))
180
+ ];
181
+ if (hash) params.push(hash);
182
+ return _class_private_field_get(this, _provider).send('childstate_getStorageEntries', params, !!hash).then((values)=>_lodash.default.zip(keys, values));
183
+ } else {
184
+ // main storage key, use state_getStorageAt
185
+ const params = [
186
+ keys
187
+ ];
188
+ if (hash) params.push(hash);
189
+ return _class_private_field_get(this, _provider).send('state_queryStorageAt', params, !!hash).then((result)=>result[0]?.['changes'] || []);
190
+ }
191
+ }
166
192
  async subscribeRemoteNewHeads(cb) {
167
193
  if (!_class_private_field_get(this, _provider).hasSubscriptions) {
168
194
  throw new Error('subscribeRemoteNewHeads only works with subscriptions');
@@ -139,12 +139,10 @@ class Block {
139
139
  /**
140
140
  * Get paged storage keys.
141
141
  */ async getKeysPaged(options) {
142
- const layer = new _storagelayer.StorageLayer(this.storage);
143
- await layer.fold();
144
142
  const prefix = options.prefix ?? '0x';
145
143
  const startKey = options.startKey ?? '0x';
146
144
  const pageSize = options.pageSize;
147
- return layer.getKeysPaged(prefix, pageSize, startKey);
145
+ return this.storage.getKeysPaged(prefix, pageSize, startKey);
148
146
  }
149
147
  /**
150
148
  * Push a layer to the storage stack.
@@ -20,8 +20,9 @@ _export(exports, {
20
20
  }
21
21
  });
22
22
  const _lodash = /*#__PURE__*/ _interop_require_default(require("lodash"));
23
+ const _index = require("../utils/index.js");
23
24
  const _logger = require("../logger.js");
24
- const _keycache = /*#__PURE__*/ _interop_require_wildcard(require("../utils/key-cache.js"));
25
+ const _keycache = /*#__PURE__*/ _interop_require_default(require("../utils/key-cache.js"));
25
26
  function _check_private_redeclaration(obj, privateCollection) {
26
27
  if (privateCollection.has(obj)) {
27
28
  throw new TypeError("Cannot initialize the same private elements twice on an object");
@@ -77,47 +78,6 @@ function _interop_require_default(obj) {
77
78
  default: obj
78
79
  };
79
80
  }
80
- function _getRequireWildcardCache(nodeInterop) {
81
- if (typeof WeakMap !== "function") return null;
82
- var cacheBabelInterop = new WeakMap();
83
- var cacheNodeInterop = new WeakMap();
84
- return (_getRequireWildcardCache = function(nodeInterop) {
85
- return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
86
- })(nodeInterop);
87
- }
88
- function _interop_require_wildcard(obj, nodeInterop) {
89
- if (!nodeInterop && obj && obj.__esModule) {
90
- return obj;
91
- }
92
- if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
93
- return {
94
- default: obj
95
- };
96
- }
97
- var cache = _getRequireWildcardCache(nodeInterop);
98
- if (cache && cache.has(obj)) {
99
- return cache.get(obj);
100
- }
101
- var newObj = {
102
- __proto__: null
103
- };
104
- var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
105
- for(var key in obj){
106
- if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
107
- var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
108
- if (desc && (desc.get || desc.set)) {
109
- Object.defineProperty(newObj, key, desc);
110
- } else {
111
- newObj[key] = obj[key];
112
- }
113
- }
114
- }
115
- newObj.default = obj;
116
- if (cache) {
117
- cache.set(obj, newObj);
118
- }
119
- return newObj;
120
- }
121
81
  const logger = _logger.defaultLogger.child({
122
82
  name: 'layer'
123
83
  });
@@ -127,7 +87,7 @@ var StorageValueKind;
127
87
  StorageValueKind["Deleted"] = "Deleted";
128
88
  StorageValueKind["DeletedPrefix"] = "DeletedPrefix";
129
89
  })(StorageValueKind || (StorageValueKind = {}));
130
- var _api = /*#__PURE__*/ new WeakMap(), _at = /*#__PURE__*/ new WeakMap(), _db = /*#__PURE__*/ new WeakMap(), _keyCache = /*#__PURE__*/ new WeakMap();
90
+ var _api = /*#__PURE__*/ new WeakMap(), _at = /*#__PURE__*/ new WeakMap(), _db = /*#__PURE__*/ new WeakMap(), _keyCache = /*#__PURE__*/ new WeakMap(), _defaultChildKeyCache = /*#__PURE__*/ new WeakMap();
131
91
  class RemoteStorageLayer {
132
92
  async get(key, _cache) {
133
93
  if (_class_private_field_get(this, _db)) {
@@ -156,14 +116,16 @@ class RemoteStorageLayer {
156
116
  pageSize,
157
117
  startKey
158
118
  }, 'RemoteStorageLayer getKeysPaged');
119
+ const isChild = (0, _index.isPrefixedChildKey)(prefix);
120
+ const minPrefixLen = isChild ? _index.CHILD_PREFIX_LENGTH : _index.PREFIX_LENGTH;
159
121
  // can't handle keyCache without prefix
160
- if (prefix.length < _keycache.PREFIX_LENGTH || startKey.length < _keycache.PREFIX_LENGTH) {
122
+ if (prefix.length < minPrefixLen || startKey.length < minPrefixLen) {
161
123
  return _class_private_field_get(this, _api).getKeysPaged(prefix, pageSize, startKey, _class_private_field_get(this, _at));
162
124
  }
163
125
  let batchComplete = false;
164
126
  const keysPaged = [];
165
127
  while(keysPaged.length < pageSize){
166
- const nextKey = await _class_private_field_get(this, _keyCache).next(startKey);
128
+ const nextKey = isChild ? await _class_private_field_get(this, _defaultChildKeyCache).next(startKey) : await _class_private_field_get(this, _keyCache).next(startKey);
167
129
  if (nextKey) {
168
130
  keysPaged.push(nextKey);
169
131
  startKey = nextKey;
@@ -178,14 +140,29 @@ class RemoteStorageLayer {
178
140
  batchComplete = batch.length < BATCH_SIZE;
179
141
  // feed the key cache
180
142
  if (batch.length > 0) {
181
- _class_private_field_get(this, _keyCache).feed([
182
- startKey,
183
- ...batch
184
- ]);
143
+ if (isChild) {
144
+ _class_private_field_get(this, _defaultChildKeyCache).feed([
145
+ startKey,
146
+ ...batch
147
+ ]);
148
+ } else {
149
+ _class_private_field_get(this, _keyCache).feed([
150
+ startKey,
151
+ ...batch
152
+ ]);
153
+ }
185
154
  }
186
155
  if (batch.length === 0) {
187
156
  break;
188
157
  }
158
+ if (_class_private_field_get(this, _db)) {
159
+ // batch fetch storage values and save to db, they may be used later
160
+ _class_private_field_get(this, _api).getStorageBatch(prefix, batch, _class_private_field_get(this, _at)).then((storage)=>{
161
+ for (const [key, value] of storage){
162
+ _class_private_field_get(this, _db).saveStorage(_class_private_field_get(this, _at), key, value);
163
+ }
164
+ });
165
+ }
189
166
  }
190
167
  return keysPaged;
191
168
  }
@@ -204,7 +181,11 @@ class RemoteStorageLayer {
204
181
  });
205
182
  _class_private_field_init(this, _keyCache, {
206
183
  writable: true,
207
- value: new _keycache.default()
184
+ value: new _keycache.default(_index.PREFIX_LENGTH)
185
+ });
186
+ _class_private_field_init(this, _defaultChildKeyCache, {
187
+ writable: true,
188
+ value: new _keycache.default(_index.CHILD_PREFIX_LENGTH)
208
189
  });
209
190
  _class_private_field_set(this, _api, api);
210
191
  _class_private_field_set(this, _at, at);
@@ -278,30 +259,105 @@ class StorageLayer {
278
259
  }
279
260
  }
280
261
  async getKeysPaged(prefix, pageSize, startKey) {
281
- if (!_class_private_field_get(this, _deletedPrefix).some((dp)=>startKey.startsWith(dp))) {
282
- const remote = await _class_private_field_get(this, _parent)?.getKeysPaged(prefix, pageSize, startKey) ?? [];
283
- for (const key of remote){
284
- if (_class_private_field_get(this, _store).get(key) === "Deleted") {
285
- continue;
262
+ let parentFetchComplete = false;
263
+ const parentFetchKeys = async (batchSize, startKey)=>{
264
+ if (!_class_private_field_get(this, _deletedPrefix).some((dp)=>startKey.startsWith(dp))) {
265
+ const newKeys = [];
266
+ while(newKeys.length < batchSize){
267
+ const remote = await _class_private_field_get(this, _parent)?.getKeysPaged(prefix, batchSize, startKey) ?? [];
268
+ if (remote.length) {
269
+ startKey = remote[remote.length - 1];
270
+ }
271
+ for (const key of remote){
272
+ if (_class_private_field_get(this, _store).get(key) === "Deleted") {
273
+ continue;
274
+ }
275
+ if (_class_private_field_get(this, _deletedPrefix).some((dp)=>key.startsWith(dp))) {
276
+ continue;
277
+ }
278
+ newKeys.push(key);
279
+ }
280
+ if (remote.length < batchSize) {
281
+ parentFetchComplete = true;
282
+ break;
283
+ }
284
+ }
285
+ return newKeys;
286
+ } else {
287
+ parentFetchComplete = true;
288
+ return [];
289
+ }
290
+ };
291
+ const res = [];
292
+ const foundNextKey = (key)=>{
293
+ // make sure keys are unique
294
+ if (!res.includes(key)) {
295
+ res.push(key);
296
+ }
297
+ };
298
+ const iterLocalKeys = (prefix, startKey, includeFirst, endKey)=>{
299
+ let idx = _class_private_field_get(this, _keys).findIndex((x)=>x.startsWith(startKey));
300
+ if (_class_private_field_get(this, _keys)[idx] !== startKey) {
301
+ idx = _class_private_field_get(this, _keys).findIndex((x)=>x.startsWith(prefix) && x > startKey);
302
+ const key = _class_private_field_get(this, _keys)[idx];
303
+ if (key) {
304
+ if (endKey && key >= endKey) {
305
+ return startKey;
306
+ }
307
+ foundNextKey(key);
308
+ ++idx;
286
309
  }
287
- if (_class_private_field_get(this, _deletedPrefix).some((dp)=>key.startsWith(dp))) {
288
- continue;
310
+ }
311
+ if (idx !== -1) {
312
+ if (includeFirst) {
313
+ const key = _class_private_field_get(this, _keys)[idx];
314
+ if (key) {
315
+ foundNextKey(key);
316
+ }
289
317
  }
290
- _class_private_method_get(this, _addKey, addKey).call(this, key);
318
+ while(res.length < pageSize){
319
+ ++idx;
320
+ const key = _class_private_field_get(this, _keys)[idx];
321
+ if (!key || !key.startsWith(prefix)) {
322
+ break;
323
+ }
324
+ if (endKey && key >= endKey) {
325
+ break;
326
+ }
327
+ foundNextKey(key);
328
+ }
329
+ return _lodash.default.last(res) ?? startKey;
291
330
  }
331
+ return startKey;
332
+ };
333
+ if (prefix !== startKey && _class_private_field_get(this, _keys).find((x)=>x === startKey)) {
334
+ startKey = iterLocalKeys(prefix, startKey, false);
292
335
  }
293
- let idx = _lodash.default.sortedIndex(_class_private_field_get(this, _keys), startKey);
294
- if (_class_private_field_get(this, _keys)[idx] === startKey) {
295
- ++idx;
296
- }
297
- const res = [];
298
- while(res.length < pageSize){
299
- const key = _class_private_field_get(this, _keys)[idx];
300
- if (!key || !key.startsWith(prefix)) {
301
- break;
336
+ // then iterate the parent keys
337
+ let keys = await parentFetchKeys(pageSize - res.length, startKey);
338
+ if (keys.length) {
339
+ let idx = 0;
340
+ while(res.length < pageSize){
341
+ const key = keys[idx];
342
+ if (!key || !key.startsWith(prefix)) {
343
+ if (parentFetchComplete) {
344
+ break;
345
+ } else {
346
+ keys = await parentFetchKeys(pageSize - res.length, _lodash.default.last(keys));
347
+ continue;
348
+ }
349
+ }
350
+ const keyPosition = _lodash.default.sortedIndex(_class_private_field_get(this, _keys), key);
351
+ const localParentKey = _class_private_field_get(this, _keys)[keyPosition - 1];
352
+ if (localParentKey < key) {
353
+ startKey = iterLocalKeys(prefix, startKey, false, key);
354
+ }
355
+ foundNextKey(key);
356
+ ++idx;
302
357
  }
303
- res.push(key);
304
- ++idx;
358
+ }
359
+ if (res.length < pageSize) {
360
+ iterLocalKeys(prefix, startKey, prefix === startKey);
305
361
  }
306
362
  return res;
307
363
  }
@@ -1,14 +1,4 @@
1
1
  import { pino } from 'pino';
2
- export declare const pinoLogger: import("pino").Logger<{
3
- level: string;
4
- transport: {
5
- target: string;
6
- };
7
- }>;
8
- export declare const defaultLogger: pino.Logger<{
9
- level: string;
10
- transport: {
11
- target: string;
12
- };
13
- }>;
2
+ export declare const pinoLogger: import("pino").Logger<never>;
3
+ export declare const defaultLogger: pino.Logger<never>;
14
4
  export declare const truncate: (val: any) => any;
@@ -1,10 +1,5 @@
1
1
  import { Blockchain } from '../blockchain/index.js';
2
- export declare const logger: import("pino").default.Logger<{
3
- level: string;
4
- transport: {
5
- target: string;
6
- };
7
- }>;
2
+ export declare const logger: import("pino").default.Logger<never>;
8
3
  export declare class ResponseError extends Error {
9
4
  code: number;
10
5
  constructor(code: number, message: string);
@@ -17,6 +17,8 @@ export type Deferred<T> = {
17
17
  promise: Promise<T>;
18
18
  };
19
19
  export declare function defer<T>(): Deferred<T>;
20
+ export declare const CHILD_PREFIX_LENGTH: number;
21
+ export declare const PREFIX_LENGTH = 66;
20
22
  export declare const prefixedChildKey: (prefix: HexString, key: HexString) => string;
21
23
  export declare const isPrefixedChildKey: (key: HexString) => boolean;
22
24
  export declare const splitChildKey: (key: HexString) => never[] | [`0x${string}`, `0x${string}`];
@@ -9,6 +9,12 @@ function _export(target, all) {
9
9
  });
10
10
  }
11
11
  _export(exports, {
12
+ CHILD_PREFIX_LENGTH: function() {
13
+ return CHILD_PREFIX_LENGTH;
14
+ },
15
+ PREFIX_LENGTH: function() {
16
+ return PREFIX_LENGTH;
17
+ },
12
18
  compactHex: function() {
13
19
  return compactHex;
14
20
  },
@@ -111,15 +117,15 @@ function defer() {
111
117
  // The difference is that child storage keys are prefixed with the child storage key
112
118
  // :child_storage:default: as hex string
113
119
  const DEFAULT_CHILD_STORAGE = '0x3a6368696c645f73746f726167653a64656661756c743a';
114
- // length of the child storage key
115
- const CHILD_LENGTH = DEFAULT_CHILD_STORAGE.length + 64;
120
+ const CHILD_PREFIX_LENGTH = DEFAULT_CHILD_STORAGE.length + 64;
121
+ const PREFIX_LENGTH = 66;
116
122
  const prefixedChildKey = (prefix, key)=>prefix + (0, _hex.hexStripPrefix)(key);
117
123
  const isPrefixedChildKey = (key)=>key.startsWith(DEFAULT_CHILD_STORAGE);
118
124
  const splitChildKey = (key)=>{
119
125
  if (!key.startsWith(DEFAULT_CHILD_STORAGE)) return [];
120
- if (key.length < CHILD_LENGTH) return [];
121
- const child = key.slice(0, CHILD_LENGTH);
122
- const rest = key.slice(CHILD_LENGTH);
126
+ if (key.length < CHILD_PREFIX_LENGTH) return [];
127
+ const child = key.slice(0, CHILD_PREFIX_LENGTH);
128
+ const rest = key.slice(CHILD_PREFIX_LENGTH);
123
129
  return [
124
130
  child,
125
131
  (0, _hex.hexAddPrefix)(rest)
@@ -1,6 +1,7 @@
1
1
  import { HexString } from '@polkadot/util/types';
2
- export declare const PREFIX_LENGTH = 66;
3
2
  export default class KeyCache {
3
+ readonly prefixLength: number;
4
+ constructor(prefixLength: number);
4
5
  readonly ranges: Array<{
5
6
  prefix: string;
6
7
  keys: string[];
@@ -2,17 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
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
- PREFIX_LENGTH: function() {
13
- return PREFIX_LENGTH;
14
- },
15
- default: function() {
5
+ Object.defineProperty(exports, "default", {
6
+ enumerable: true,
7
+ get: function() {
16
8
  return KeyCache;
17
9
  }
18
10
  });
@@ -35,21 +27,20 @@ function _interop_require_default(obj) {
35
27
  default: obj
36
28
  };
37
29
  }
38
- const PREFIX_LENGTH = 66;
39
30
  class KeyCache {
40
31
  feed(keys) {
41
- const _keys = keys.filter((key)=>key.length >= PREFIX_LENGTH);
32
+ const _keys = keys.filter((key)=>key.length >= this.prefixLength);
42
33
  if (_keys.length === 0) return;
43
- const startKey = _keys[0].slice(PREFIX_LENGTH);
44
- const endKey = _keys[_keys.length - 1].slice(PREFIX_LENGTH);
45
- const grouped = _lodash.default.groupBy(_keys, (key)=>key.slice(0, PREFIX_LENGTH));
34
+ const startKey = _keys[0].slice(this.prefixLength);
35
+ const endKey = _keys[_keys.length - 1].slice(this.prefixLength);
36
+ const grouped = _lodash.default.groupBy(_keys, (key)=>key.slice(0, this.prefixLength));
46
37
  for (const [prefix, keys] of Object.entries(grouped)){
47
38
  const ranges = this.ranges.filter((range)=>range.prefix === prefix);
48
39
  if (ranges.length === 0) {
49
40
  // no existing range with prefix
50
41
  this.ranges.push({
51
42
  prefix,
52
- keys: keys.map((i)=>i.slice(PREFIX_LENGTH))
43
+ keys: keys.map((i)=>i.slice(this.prefixLength))
53
44
  });
54
45
  continue;
55
46
  }
@@ -58,14 +49,14 @@ class KeyCache {
58
49
  const startPosition = _lodash.default.sortedIndex(range.keys, startKey);
59
50
  if (startPosition >= 0 && range.keys[startPosition] === startKey) {
60
51
  // found existing range with prefix
61
- range.keys.splice(startPosition, keys.length, ...keys.map((i)=>i.slice(PREFIX_LENGTH)));
52
+ range.keys.splice(startPosition, keys.length, ...keys.map((i)=>i.slice(this.prefixLength)));
62
53
  merged = true;
63
54
  break;
64
55
  }
65
56
  const endPosition = _lodash.default.sortedIndex(range.keys, endKey);
66
57
  if (endPosition >= 0 && range.keys[endPosition] === endKey) {
67
58
  // found existing range with prefix
68
- range.keys.splice(0, endPosition + 1, ...keys.map((i)=>i.slice(PREFIX_LENGTH)));
59
+ range.keys.splice(0, endPosition + 1, ...keys.map((i)=>i.slice(this.prefixLength)));
69
60
  merged = true;
70
61
  break;
71
62
  }
@@ -74,17 +65,25 @@ class KeyCache {
74
65
  if (!merged) {
75
66
  this.ranges.push({
76
67
  prefix,
77
- keys: keys.map((i)=>i.slice(PREFIX_LENGTH))
68
+ keys: keys.map((i)=>i.slice(this.prefixLength))
78
69
  });
79
70
  }
80
71
  }
81
72
  // TODO: merge ranges if they overlap
82
73
  }
83
74
  async next(startKey) {
84
- if (startKey.length < PREFIX_LENGTH) return;
85
- const prefix = startKey.slice(0, PREFIX_LENGTH);
86
- const key = startKey.slice(PREFIX_LENGTH);
75
+ if (startKey.length < this.prefixLength) return;
76
+ const prefix = startKey.slice(0, this.prefixLength);
77
+ const key = startKey.slice(this.prefixLength);
87
78
  for (const range of this.ranges.filter((range)=>range.prefix === prefix)){
79
+ if (key.length === 0) {
80
+ // if key is empty then find the range with first key empty
81
+ if (range.keys[0] !== '') continue;
82
+ return [
83
+ prefix,
84
+ range.keys[1]
85
+ ].join('');
86
+ }
88
87
  const index = _lodash.default.sortedIndex(range.keys, key);
89
88
  if (range.keys[index] !== key) continue;
90
89
  const nextKey = range.keys[index + 1];
@@ -96,7 +95,10 @@ class KeyCache {
96
95
  }
97
96
  }
98
97
  }
99
- constructor(){
100
- _define_property(this, "ranges", []);
98
+ constructor(prefixLength){
99
+ _define_property(this, "prefixLength", void 0);
100
+ _define_property(this, "ranges", void 0);
101
+ this.prefixLength = prefixLength;
102
+ this.ranges = [];
101
103
  }
102
104
  }
@@ -44,9 +44,8 @@ const _comlink = /*#__PURE__*/ _interop_require_wildcard(require("comlink"));
44
44
  const _util = require("@polkadot/util");
45
45
  const _utilcrypto = require("@polkadot/util-crypto");
46
46
  const _lodash = /*#__PURE__*/ _interop_require_default(require("lodash"));
47
- const _keycache = require("../utils/key-cache.js");
48
- const _logger = require("../logger.js");
49
47
  const _index = require("../utils/index.js");
48
+ const _logger = require("../logger.js");
50
49
  function _interop_require_default(obj) {
51
50
  return obj && obj.__esModule ? obj : {
52
51
  default: obj
@@ -160,7 +159,7 @@ const taskHandler = (block)=>{
160
159
  },
161
160
  getNextKey: async function(prefix, key) {
162
161
  const [nextKey] = await block.getKeysPaged({
163
- prefix: prefix.length === 2 /** 0x */ ? key.slice(0, _keycache.PREFIX_LENGTH) : prefix,
162
+ prefix: prefix.length === 2 /** 0x */ ? key.slice(0, _index.PREFIX_LENGTH) : prefix,
164
163
  pageSize: 1,
165
164
  startKey: key
166
165
  });
@@ -1,9 +1,4 @@
1
1
  import { Blockchain } from '../blockchain/index.js';
2
- export declare const xcmLogger: import("pino").default.Logger<{
3
- level: string;
4
- transport: {
5
- target: string;
6
- };
7
- }>;
2
+ export declare const xcmLogger: import("pino").default.Logger<never>;
8
3
  export declare const connectVertical: (relaychain: Blockchain, parachain: Blockchain) => Promise<void>;
9
4
  export declare const connectParachains: (parachains: Blockchain[]) => Promise<void>;
package/dist/esm/api.d.ts CHANGED
@@ -50,6 +50,7 @@ export declare class Api {
50
50
  getBlock(hash?: string): Promise<SignedBlock | null>;
51
51
  getStorage(key: string, hash?: string): Promise<`0x${string}` | null>;
52
52
  getKeysPaged(prefix: string, pageSize: number, startKey: string, hash?: string): Promise<string[]>;
53
+ getStorageBatch(prefix: HexString, keys: HexString[], hash?: HexString): Promise<[`0x${string}`, `0x${string}` | null][]>;
53
54
  subscribeRemoteNewHeads(cb: ProviderInterfaceCallback): Promise<string | number>;
54
55
  subscribeRemoteFinalizedHeads(cb: ProviderInterfaceCallback): Promise<string | number>;
55
56
  }
package/dist/esm/api.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { prefixedChildKey, splitChildKey, stripChildPrefix } from './utils/index.js';
2
+ import _ from 'lodash';
2
3
  /**
3
4
  * API class. Calls provider to get on-chain data.
4
5
  * Either `endpoint` or `genesis` porvider must be provided.
@@ -119,6 +120,26 @@ import { prefixedChildKey, splitChildKey, stripChildPrefix } from './utils/index
119
120
  return this.#provider.send('state_getKeysPaged', params, !!hash);
120
121
  }
121
122
  }
123
+ async getStorageBatch(prefix, keys, hash) {
124
+ const [child] = splitChildKey(prefix);
125
+ if (child) {
126
+ // child storage key, use childstate_getStorageEntries
127
+ // strip child prefix from keys
128
+ const params = [
129
+ child,
130
+ keys.map((key)=>stripChildPrefix(key))
131
+ ];
132
+ if (hash) params.push(hash);
133
+ return this.#provider.send('childstate_getStorageEntries', params, !!hash).then((values)=>_.zip(keys, values));
134
+ } else {
135
+ // main storage key, use state_getStorageAt
136
+ const params = [
137
+ keys
138
+ ];
139
+ if (hash) params.push(hash);
140
+ return this.#provider.send('state_queryStorageAt', params, !!hash).then((result)=>result[0]?.['changes'] || []);
141
+ }
142
+ }
122
143
  async subscribeRemoteNewHeads(cb) {
123
144
  if (!this.#provider.hasSubscriptions) {
124
145
  throw new Error('subscribeRemoteNewHeads only works with subscriptions');
@@ -129,12 +129,10 @@ import { getRuntimeVersion, runTask, taskHandler } from '../wasm-executor/index.
129
129
  /**
130
130
  * Get paged storage keys.
131
131
  */ async getKeysPaged(options) {
132
- const layer = new StorageLayer(this.storage);
133
- await layer.fold();
134
132
  const prefix = options.prefix ?? '0x';
135
133
  const startKey = options.startKey ?? '0x';
136
134
  const pageSize = options.pageSize;
137
- return layer.getKeysPaged(prefix, pageSize, startKey);
135
+ return this.storage.getKeysPaged(prefix, pageSize, startKey);
138
136
  }
139
137
  /**
140
138
  * Push a layer to the storage stack.
@@ -1,6 +1,7 @@
1
1
  import _ from 'lodash';
2
+ import { CHILD_PREFIX_LENGTH, PREFIX_LENGTH, isPrefixedChildKey } from '../utils/index.js';
2
3
  import { defaultLogger } from '../logger.js';
3
- import KeyCache, { PREFIX_LENGTH } from '../utils/key-cache.js';
4
+ import KeyCache from '../utils/key-cache.js';
4
5
  const logger = defaultLogger.child({
5
6
  name: 'layer'
6
7
  });
@@ -14,7 +15,8 @@ export class RemoteStorageLayer {
14
15
  #api;
15
16
  #at;
16
17
  #db;
17
- #keyCache = new KeyCache();
18
+ #keyCache = new KeyCache(PREFIX_LENGTH);
19
+ #defaultChildKeyCache = new KeyCache(CHILD_PREFIX_LENGTH);
18
20
  constructor(api, at, db){
19
21
  this.#api = api;
20
22
  this.#at = at;
@@ -47,14 +49,16 @@ export class RemoteStorageLayer {
47
49
  pageSize,
48
50
  startKey
49
51
  }, 'RemoteStorageLayer getKeysPaged');
52
+ const isChild = isPrefixedChildKey(prefix);
53
+ const minPrefixLen = isChild ? CHILD_PREFIX_LENGTH : PREFIX_LENGTH;
50
54
  // can't handle keyCache without prefix
51
- if (prefix.length < PREFIX_LENGTH || startKey.length < PREFIX_LENGTH) {
55
+ if (prefix.length < minPrefixLen || startKey.length < minPrefixLen) {
52
56
  return this.#api.getKeysPaged(prefix, pageSize, startKey, this.#at);
53
57
  }
54
58
  let batchComplete = false;
55
59
  const keysPaged = [];
56
60
  while(keysPaged.length < pageSize){
57
- const nextKey = await this.#keyCache.next(startKey);
61
+ const nextKey = isChild ? await this.#defaultChildKeyCache.next(startKey) : await this.#keyCache.next(startKey);
58
62
  if (nextKey) {
59
63
  keysPaged.push(nextKey);
60
64
  startKey = nextKey;
@@ -69,14 +73,29 @@ export class RemoteStorageLayer {
69
73
  batchComplete = batch.length < BATCH_SIZE;
70
74
  // feed the key cache
71
75
  if (batch.length > 0) {
72
- this.#keyCache.feed([
73
- startKey,
74
- ...batch
75
- ]);
76
+ if (isChild) {
77
+ this.#defaultChildKeyCache.feed([
78
+ startKey,
79
+ ...batch
80
+ ]);
81
+ } else {
82
+ this.#keyCache.feed([
83
+ startKey,
84
+ ...batch
85
+ ]);
86
+ }
76
87
  }
77
88
  if (batch.length === 0) {
78
89
  break;
79
90
  }
91
+ if (this.#db) {
92
+ // batch fetch storage values and save to db, they may be used later
93
+ this.#api.getStorageBatch(prefix, batch, this.#at).then((storage)=>{
94
+ for (const [key, value] of storage){
95
+ this.#db.saveStorage(this.#at, key, value);
96
+ }
97
+ });
98
+ }
80
99
  }
81
100
  return keysPaged;
82
101
  }
@@ -169,30 +188,105 @@ export class StorageLayer {
169
188
  }
170
189
  }
171
190
  async getKeysPaged(prefix, pageSize, startKey) {
172
- if (!this.#deletedPrefix.some((dp)=>startKey.startsWith(dp))) {
173
- const remote = await this.#parent?.getKeysPaged(prefix, pageSize, startKey) ?? [];
174
- for (const key of remote){
175
- if (this.#store.get(key) === "Deleted") {
176
- continue;
191
+ let parentFetchComplete = false;
192
+ const parentFetchKeys = async (batchSize, startKey)=>{
193
+ if (!this.#deletedPrefix.some((dp)=>startKey.startsWith(dp))) {
194
+ const newKeys = [];
195
+ while(newKeys.length < batchSize){
196
+ const remote = await this.#parent?.getKeysPaged(prefix, batchSize, startKey) ?? [];
197
+ if (remote.length) {
198
+ startKey = remote[remote.length - 1];
199
+ }
200
+ for (const key of remote){
201
+ if (this.#store.get(key) === "Deleted") {
202
+ continue;
203
+ }
204
+ if (this.#deletedPrefix.some((dp)=>key.startsWith(dp))) {
205
+ continue;
206
+ }
207
+ newKeys.push(key);
208
+ }
209
+ if (remote.length < batchSize) {
210
+ parentFetchComplete = true;
211
+ break;
212
+ }
177
213
  }
178
- if (this.#deletedPrefix.some((dp)=>key.startsWith(dp))) {
179
- continue;
214
+ return newKeys;
215
+ } else {
216
+ parentFetchComplete = true;
217
+ return [];
218
+ }
219
+ };
220
+ const res = [];
221
+ const foundNextKey = (key)=>{
222
+ // make sure keys are unique
223
+ if (!res.includes(key)) {
224
+ res.push(key);
225
+ }
226
+ };
227
+ const iterLocalKeys = (prefix, startKey, includeFirst, endKey)=>{
228
+ let idx = this.#keys.findIndex((x)=>x.startsWith(startKey));
229
+ if (this.#keys[idx] !== startKey) {
230
+ idx = this.#keys.findIndex((x)=>x.startsWith(prefix) && x > startKey);
231
+ const key = this.#keys[idx];
232
+ if (key) {
233
+ if (endKey && key >= endKey) {
234
+ return startKey;
235
+ }
236
+ foundNextKey(key);
237
+ ++idx;
180
238
  }
181
- this.#addKey(key);
182
239
  }
240
+ if (idx !== -1) {
241
+ if (includeFirst) {
242
+ const key = this.#keys[idx];
243
+ if (key) {
244
+ foundNextKey(key);
245
+ }
246
+ }
247
+ while(res.length < pageSize){
248
+ ++idx;
249
+ const key = this.#keys[idx];
250
+ if (!key || !key.startsWith(prefix)) {
251
+ break;
252
+ }
253
+ if (endKey && key >= endKey) {
254
+ break;
255
+ }
256
+ foundNextKey(key);
257
+ }
258
+ return _.last(res) ?? startKey;
259
+ }
260
+ return startKey;
261
+ };
262
+ if (prefix !== startKey && this.#keys.find((x)=>x === startKey)) {
263
+ startKey = iterLocalKeys(prefix, startKey, false);
183
264
  }
184
- let idx = _.sortedIndex(this.#keys, startKey);
185
- if (this.#keys[idx] === startKey) {
186
- ++idx;
187
- }
188
- const res = [];
189
- while(res.length < pageSize){
190
- const key = this.#keys[idx];
191
- if (!key || !key.startsWith(prefix)) {
192
- break;
265
+ // then iterate the parent keys
266
+ let keys = await parentFetchKeys(pageSize - res.length, startKey);
267
+ if (keys.length) {
268
+ let idx = 0;
269
+ while(res.length < pageSize){
270
+ const key = keys[idx];
271
+ if (!key || !key.startsWith(prefix)) {
272
+ if (parentFetchComplete) {
273
+ break;
274
+ } else {
275
+ keys = await parentFetchKeys(pageSize - res.length, _.last(keys));
276
+ continue;
277
+ }
278
+ }
279
+ const keyPosition = _.sortedIndex(this.#keys, key);
280
+ const localParentKey = this.#keys[keyPosition - 1];
281
+ if (localParentKey < key) {
282
+ startKey = iterLocalKeys(prefix, startKey, false, key);
283
+ }
284
+ foundNextKey(key);
285
+ ++idx;
193
286
  }
194
- res.push(key);
195
- ++idx;
287
+ }
288
+ if (res.length < pageSize) {
289
+ iterLocalKeys(prefix, startKey, prefix === startKey);
196
290
  }
197
291
  return res;
198
292
  }
@@ -1,14 +1,4 @@
1
1
  import { pino } from 'pino';
2
- export declare const pinoLogger: import("pino").Logger<{
3
- level: string;
4
- transport: {
5
- target: string;
6
- };
7
- }>;
8
- export declare const defaultLogger: pino.Logger<{
9
- level: string;
10
- transport: {
11
- target: string;
12
- };
13
- }>;
2
+ export declare const pinoLogger: import("pino").Logger<never>;
3
+ export declare const defaultLogger: pino.Logger<never>;
14
4
  export declare const truncate: (val: any) => any;
@@ -1,10 +1,5 @@
1
1
  import { Blockchain } from '../blockchain/index.js';
2
- export declare const logger: import("pino").default.Logger<{
3
- level: string;
4
- transport: {
5
- target: string;
6
- };
7
- }>;
2
+ export declare const logger: import("pino").default.Logger<never>;
8
3
  export declare class ResponseError extends Error {
9
4
  code: number;
10
5
  constructor(code: number, message: string);
@@ -17,6 +17,8 @@ export type Deferred<T> = {
17
17
  promise: Promise<T>;
18
18
  };
19
19
  export declare function defer<T>(): Deferred<T>;
20
+ export declare const CHILD_PREFIX_LENGTH: number;
21
+ export declare const PREFIX_LENGTH = 66;
20
22
  export declare const prefixedChildKey: (prefix: HexString, key: HexString) => string;
21
23
  export declare const isPrefixedChildKey: (key: HexString) => boolean;
22
24
  export declare const splitChildKey: (key: HexString) => never[] | [`0x${string}`, `0x${string}`];
@@ -57,7 +57,9 @@ export function defer() {
57
57
  // :child_storage:default: as hex string
58
58
  const DEFAULT_CHILD_STORAGE = '0x3a6368696c645f73746f726167653a64656661756c743a';
59
59
  // length of the child storage key
60
- const CHILD_LENGTH = DEFAULT_CHILD_STORAGE.length + 64;
60
+ export const CHILD_PREFIX_LENGTH = DEFAULT_CHILD_STORAGE.length + 64;
61
+ // 0x + 32 module + 32 method
62
+ export const PREFIX_LENGTH = 66;
61
63
  // returns a key that is prefixed with the child storage key
62
64
  export const prefixedChildKey = (prefix, key)=>prefix + hexStripPrefix(key);
63
65
  // returns true if the key is a child storage key
@@ -65,9 +67,9 @@ export const isPrefixedChildKey = (key)=>key.startsWith(DEFAULT_CHILD_STORAGE);
65
67
  // returns a key that is split into the child storage key and the rest
66
68
  export const splitChildKey = (key)=>{
67
69
  if (!key.startsWith(DEFAULT_CHILD_STORAGE)) return [];
68
- if (key.length < CHILD_LENGTH) return [];
69
- const child = key.slice(0, CHILD_LENGTH);
70
- const rest = key.slice(CHILD_LENGTH);
70
+ if (key.length < CHILD_PREFIX_LENGTH) return [];
71
+ const child = key.slice(0, CHILD_PREFIX_LENGTH);
72
+ const rest = key.slice(CHILD_PREFIX_LENGTH);
71
73
  return [
72
74
  child,
73
75
  hexAddPrefix(rest)
@@ -1,6 +1,7 @@
1
1
  import { HexString } from '@polkadot/util/types';
2
- export declare const PREFIX_LENGTH = 66;
3
2
  export default class KeyCache {
3
+ readonly prefixLength: number;
4
+ constructor(prefixLength: number);
4
5
  readonly ranges: Array<{
5
6
  prefix: string;
6
7
  keys: string[];
@@ -1,21 +1,24 @@
1
1
  import _ from 'lodash';
2
- // 0x + 32 module + 32 method
3
- export const PREFIX_LENGTH = 66;
4
2
  export default class KeyCache {
5
- ranges = [];
3
+ prefixLength;
4
+ constructor(prefixLength){
5
+ this.prefixLength = prefixLength;
6
+ this.ranges = [];
7
+ }
8
+ ranges;
6
9
  feed(keys) {
7
- const _keys = keys.filter((key)=>key.length >= PREFIX_LENGTH);
10
+ const _keys = keys.filter((key)=>key.length >= this.prefixLength);
8
11
  if (_keys.length === 0) return;
9
- const startKey = _keys[0].slice(PREFIX_LENGTH);
10
- const endKey = _keys[_keys.length - 1].slice(PREFIX_LENGTH);
11
- const grouped = _.groupBy(_keys, (key)=>key.slice(0, PREFIX_LENGTH));
12
+ const startKey = _keys[0].slice(this.prefixLength);
13
+ const endKey = _keys[_keys.length - 1].slice(this.prefixLength);
14
+ const grouped = _.groupBy(_keys, (key)=>key.slice(0, this.prefixLength));
12
15
  for (const [prefix, keys] of Object.entries(grouped)){
13
16
  const ranges = this.ranges.filter((range)=>range.prefix === prefix);
14
17
  if (ranges.length === 0) {
15
18
  // no existing range with prefix
16
19
  this.ranges.push({
17
20
  prefix,
18
- keys: keys.map((i)=>i.slice(PREFIX_LENGTH))
21
+ keys: keys.map((i)=>i.slice(this.prefixLength))
19
22
  });
20
23
  continue;
21
24
  }
@@ -24,14 +27,14 @@ export default class KeyCache {
24
27
  const startPosition = _.sortedIndex(range.keys, startKey);
25
28
  if (startPosition >= 0 && range.keys[startPosition] === startKey) {
26
29
  // found existing range with prefix
27
- range.keys.splice(startPosition, keys.length, ...keys.map((i)=>i.slice(PREFIX_LENGTH)));
30
+ range.keys.splice(startPosition, keys.length, ...keys.map((i)=>i.slice(this.prefixLength)));
28
31
  merged = true;
29
32
  break;
30
33
  }
31
34
  const endPosition = _.sortedIndex(range.keys, endKey);
32
35
  if (endPosition >= 0 && range.keys[endPosition] === endKey) {
33
36
  // found existing range with prefix
34
- range.keys.splice(0, endPosition + 1, ...keys.map((i)=>i.slice(PREFIX_LENGTH)));
37
+ range.keys.splice(0, endPosition + 1, ...keys.map((i)=>i.slice(this.prefixLength)));
35
38
  merged = true;
36
39
  break;
37
40
  }
@@ -40,17 +43,25 @@ export default class KeyCache {
40
43
  if (!merged) {
41
44
  this.ranges.push({
42
45
  prefix,
43
- keys: keys.map((i)=>i.slice(PREFIX_LENGTH))
46
+ keys: keys.map((i)=>i.slice(this.prefixLength))
44
47
  });
45
48
  }
46
49
  }
47
50
  // TODO: merge ranges if they overlap
48
51
  }
49
52
  async next(startKey) {
50
- if (startKey.length < PREFIX_LENGTH) return;
51
- const prefix = startKey.slice(0, PREFIX_LENGTH);
52
- const key = startKey.slice(PREFIX_LENGTH);
53
+ if (startKey.length < this.prefixLength) return;
54
+ const prefix = startKey.slice(0, this.prefixLength);
55
+ const key = startKey.slice(this.prefixLength);
53
56
  for (const range of this.ranges.filter((range)=>range.prefix === prefix)){
57
+ if (key.length === 0) {
58
+ // if key is empty then find the range with first key empty
59
+ if (range.keys[0] !== '') continue;
60
+ return [
61
+ prefix,
62
+ range.keys[1]
63
+ ].join('');
64
+ }
54
65
  const index = _.sortedIndex(range.keys, key);
55
66
  if (range.keys[index] !== key) continue;
56
67
  const nextKey = range.keys[index + 1];
@@ -2,9 +2,8 @@ import * as Comlink from 'comlink';
2
2
  import { hexToString, hexToU8a, u8aToBn } from '@polkadot/util';
3
3
  import { randomAsHex } from '@polkadot/util-crypto';
4
4
  import _ from 'lodash';
5
- import { PREFIX_LENGTH } from '../utils/key-cache.js';
5
+ import { PREFIX_LENGTH, stripChildPrefix } from '../utils/index.js';
6
6
  import { defaultLogger, truncate } from '../logger.js';
7
- import { stripChildPrefix } from '../utils/index.js';
8
7
  const logger = defaultLogger.child({
9
8
  name: 'executor'
10
9
  });
@@ -1,9 +1,4 @@
1
1
  import { Blockchain } from '../blockchain/index.js';
2
- export declare const xcmLogger: import("pino").default.Logger<{
3
- level: string;
4
- transport: {
5
- target: string;
6
- };
7
- }>;
2
+ export declare const xcmLogger: import("pino").default.Logger<never>;
8
3
  export declare const connectVertical: (relaychain: Blockchain, parachain: Blockchain) => Promise<void>;
9
4
  export declare const connectParachains: (parachains: Blockchain[]) => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acala-network/chopsticks-core",
3
- "version": "0.9.6",
3
+ "version": "0.9.8-1",
4
4
  "author": "Acala Developers <hello@acala.network>",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -12,28 +12,28 @@
12
12
  "docs:prep": "typedoc"
13
13
  },
14
14
  "dependencies": {
15
- "@acala-network/chopsticks-executor": "0.9.6",
16
- "@polkadot/rpc-provider": "^10.10.1",
17
- "@polkadot/types": "^10.10.1",
18
- "@polkadot/types-codec": "^10.10.1",
19
- "@polkadot/types-known": "^10.10.1",
20
- "@polkadot/util": "^12.5.1",
21
- "@polkadot/util-crypto": "^12.5.1",
15
+ "@acala-network/chopsticks-executor": "0.9.8-1",
16
+ "@polkadot/rpc-provider": "^10.11.2",
17
+ "@polkadot/types": "^10.11.2",
18
+ "@polkadot/types-codec": "^10.11.2",
19
+ "@polkadot/types-known": "^10.11.2",
20
+ "@polkadot/util": "^12.6.2",
21
+ "@polkadot/util-crypto": "^12.6.2",
22
22
  "comlink": "^4.4.1",
23
23
  "eventemitter3": "^5.0.1",
24
24
  "lodash": "^4.17.21",
25
25
  "lru-cache": "^10.1.0",
26
- "pino": "^8.16.2",
27
- "pino-pretty": "^10.2.3",
26
+ "pino": "^8.17.2",
27
+ "pino-pretty": "^10.3.1",
28
28
  "rxjs": "^7.8.1",
29
29
  "zod": "^3.22.4"
30
30
  },
31
31
  "devDependencies": {
32
- "@swc/cli": "0.1.63",
33
- "@swc/core": "^1.3.100",
32
+ "@swc/cli": "0.1.65",
33
+ "@swc/core": "^1.3.105",
34
34
  "@types/lodash": "^4.14.202",
35
35
  "typescript": "^5.3.3",
36
- "vitest": "^1.0.4"
36
+ "vitest": "^1.2.1"
37
37
  },
38
38
  "files": [
39
39
  "dist/esm/**",