@acala-network/chopsticks-core 1.2.1 → 1.2.3

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
@@ -33,7 +33,7 @@ export declare class Api {
33
33
  getBlock(hash?: string): Promise<SignedBlock | null>;
34
34
  getStorage(key: string, hash?: string): Promise<`0x${string}` | null>;
35
35
  getKeysPaged(prefix: string, pageSize: number, startKey: string, hash?: string): Promise<`0x${string}`[]>;
36
- getStorageBatch(prefix: HexString, keys: HexString[], hash?: HexString): Promise<[`0x${string}`, `0x${string}` | null][]>;
36
+ getStorageBatch(prefix: HexString, keys: HexString[], hash?: HexString): any;
37
37
  subscribeRemoteNewHeads(cb: ProviderInterfaceCallback): Promise<string | number>;
38
38
  subscribeRemoteFinalizedHeads(cb: ProviderInterfaceCallback): Promise<string | number>;
39
39
  }
package/dist/cjs/api.js CHANGED
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "Api", {
8
8
  return Api;
9
9
  }
10
10
  });
11
+ const _error = /*#__PURE__*/ _interop_require_default(require("@polkadot/rpc-provider/coder/error"));
11
12
  const _lodash = /*#__PURE__*/ _interop_require_default(require("lodash"));
12
13
  const _index = require("./utils/index.js");
13
14
  function _check_private_redeclaration(obj, privateCollection) {
@@ -179,6 +180,21 @@ class Api {
179
180
  return this.send('state_getKeysPaged', params, !!hash);
180
181
  }
181
182
  async getStorageBatch(prefix, keys, hash) {
183
+ // On response limit error, retry with a smaller batch size
184
+ const retryOnError = async (ex)=>{
185
+ if (ex instanceof _error.default && (ex.code === -32008 || ex.message === 'Response is too big')) {
186
+ // Can't split beyond key size = 2
187
+ if (keys.length < 2) throw ex;
188
+ const mid = Math.floor(keys.length / 2);
189
+ const batches = [
190
+ keys.slice(0, mid),
191
+ keys.slice(mid)
192
+ ];
193
+ const results = await Promise.all(batches.map((batch)=>this.getStorageBatch(prefix, batch, hash)));
194
+ return results.flat();
195
+ }
196
+ throw ex;
197
+ };
182
198
  const [child] = (0, _index.splitChildKey)(prefix);
183
199
  if (child) {
184
200
  // child storage key, use childstate_getStorageEntries
@@ -188,14 +204,14 @@ class Api {
188
204
  keys.map((key)=>(0, _index.stripChildPrefix)(key))
189
205
  ];
190
206
  if (hash) params.push(hash);
191
- return _class_private_field_get(this, _provider).send('childstate_getStorageEntries', params, !!hash).then((values)=>_lodash.default.zip(keys, values));
207
+ return _class_private_field_get(this, _provider).send('childstate_getStorageEntries', params, !!hash).then((values)=>_lodash.default.zip(keys, values)).catch(retryOnError);
192
208
  }
193
209
  // main storage key, use state_getStorageAt
194
210
  const params = [
195
211
  keys
196
212
  ];
197
213
  if (hash) params.push(hash);
198
- return _class_private_field_get(this, _provider).send('state_queryStorageAt', params, !!hash).then((result)=>result[0]?.['changes'] || []);
214
+ return _class_private_field_get(this, _provider).send('state_queryStorageAt', params, !!hash).then((result)=>result[0]?.['changes'] || []).catch(retryOnError);
199
215
  }
200
216
  async subscribeRemoteNewHeads(cb) {
201
217
  if (!_class_private_field_get(this, _provider).hasSubscriptions) {
@@ -48,6 +48,10 @@ export declare class Block {
48
48
  * Get the block storage by key.
49
49
  */
50
50
  get(key: string): Promise<HexString | undefined>;
51
+ /**
52
+ * Get the block storage by key.
53
+ */
54
+ getMany(keys: string[]): Promise<Array<HexString | undefined>>;
51
55
  read<T extends string>(type: T, query: StorageEntry, ...args: any[]): Promise<import("@polkadot/types/types").DetectCodec<import("@polkadot/types-codec/types").Codec, T> | undefined>;
52
56
  /**
53
57
  * Get paged storage keys.
@@ -127,6 +127,19 @@ class Block {
127
127
  return val;
128
128
  }
129
129
  }
130
+ /**
131
+ * Get the block storage by key.
132
+ */ async getMany(keys) {
133
+ const vals = await this.storage.getMany(keys, true);
134
+ return vals.map((val)=>{
135
+ switch(val){
136
+ case _storagelayer.StorageValueKind.Deleted:
137
+ return undefined;
138
+ default:
139
+ return val;
140
+ }
141
+ });
142
+ }
130
143
  async read(type, query, ...args) {
131
144
  const key = (0, _index.compactHex)(query(...args));
132
145
  const value = await this.get(key);
@@ -12,6 +12,7 @@ const _types = require("@polkadot/types");
12
12
  const _util = require("@polkadot/util");
13
13
  const _utilcrypto = require("@polkadot/util-crypto");
14
14
  const _lodash = /*#__PURE__*/ _interop_require_default(require("lodash"));
15
+ const _logger = require("../../../logger.js");
15
16
  const _index = require("../../../utils/index.js");
16
17
  const _proof = require("../../../utils/proof.js");
17
18
  const _index1 = require("../../../wasm-executor/index.js");
@@ -20,6 +21,10 @@ function _interop_require_default(obj) {
20
21
  default: obj
21
22
  };
22
23
  }
24
+ const logger = _logger.defaultLogger.child({
25
+ name: 'parachain-validation-data'
26
+ });
27
+ const RELAY_CHAIN_SLOT_DURATION_MILLIS = 6_000;
23
28
  const MOCK_VALIDATION_DATA = {
24
29
  validationData: {
25
30
  relayParentNumber: 1000,
@@ -46,7 +51,7 @@ const MOCK_VALIDATION_DATA = {
46
51
  horizontalMessages: [],
47
52
  downwardMessages: []
48
53
  };
49
- const getValidationData = async (parent)=>{
54
+ const getValidationData = async (parent, fallback = true)=>{
50
55
  const meta = await parent.meta;
51
56
  if (parent.number === 0) {
52
57
  const { trieRootHash, nodes } = await (0, _index1.createProof)(MOCK_VALIDATION_DATA.relayChainState.trieNodes, []);
@@ -61,15 +66,39 @@ const getValidationData = async (parent)=>{
61
66
  }
62
67
  };
63
68
  }
64
- const extrinsics = await parent.extrinsics;
65
- const validationDataExtrinsic = extrinsics.find((extrinsic)=>{
66
- const firstArg = meta.registry.createType('GenericExtrinsic', extrinsic)?.args?.[0];
67
- return firstArg && 'validationData' in firstArg;
68
- });
69
- if (!validationDataExtrinsic) {
70
- throw new Error('Missing validation data from block');
69
+ try {
70
+ const extrinsics = await parent.extrinsics;
71
+ const validationDataExtrinsic = extrinsics.find((extrinsic)=>{
72
+ const firstArg = meta.registry.createType('GenericExtrinsic', extrinsic)?.args?.[0];
73
+ return firstArg && 'validationData' in firstArg;
74
+ });
75
+ if (!validationDataExtrinsic) {
76
+ throw new Error('Missing validation data from block');
77
+ }
78
+ return meta.registry.createType('GenericExtrinsic', validationDataExtrinsic).args[0].toJSON();
79
+ } catch (e) {
80
+ logger.warn('Failed to get validation data from block %d %s', parent.number, e);
81
+ if (fallback) {
82
+ // this could fail due to wasm override that breaks the validation data format
83
+ // so we will try parent's parent
84
+ const grandParent = await parent.parentBlock;
85
+ if (grandParent) {
86
+ const data = await getValidationData(grandParent, false);
87
+ return {
88
+ ...data,
89
+ validationData: {
90
+ ...data.validationData,
91
+ relayParentNumber: data.validationData.relayParentNumber + 2
92
+ }
93
+ };
94
+ } else {
95
+ throw e;
96
+ }
97
+ } else {
98
+ // fallback failed, throw error
99
+ throw e;
100
+ }
71
101
  }
72
- return meta.registry.createType('GenericExtrinsic', validationDataExtrinsic).args[0].toJSON();
73
102
  };
74
103
  class SetValidationData {
75
104
  async createInherents(newBlock, params) {
@@ -88,16 +117,18 @@ class SetValidationData {
88
117
  const hrmpIngressChannelIndexKey = (0, _proof.hrmpIngressChannelIndex)(paraId);
89
118
  const hrmpEgressChannelIndexKey = (0, _proof.hrmpEgressChannelIndex)(paraId);
90
119
  const decoded = await (0, _index1.decodeProof)(extrinsic.validationData.relayParentStorageRoot, extrinsic.relayChainState.trieNodes);
91
- const relaySlotIncrease = Math.max(1, meta.consts.timestamp?.minimumPeriod // legacy
92
- ?.divn(3000) // relaychain min period
93
- ?.toNumber() || meta.consts.aura?.slotDuration // async backing
94
- ?.divn(6000) // relaychain block time
95
- ?.toNumber() || 1);
120
+ const slotDuration = await (0, _index.getSlotDuration)(newBlock);
121
+ const relaySlotIncrease = Math.trunc(slotDuration / RELAY_CHAIN_SLOT_DURATION_MILLIS) || 1 // at least increase by 1
122
+ ;
96
123
  for (const key of Object.values(_proof.WELL_KNOWN_KEYS)){
97
124
  if (key === _proof.WELL_KNOWN_KEYS.CURRENT_SLOT) {
98
125
  // increment current slot
99
126
  const relayCurrentSlot = decoded[key] ? meta.registry.createType('Slot', (0, _util.hexToU8a)(decoded[key])).toNumber() : await (0, _index.getCurrentSlot)(parent) * relaySlotIncrease;
100
127
  const newSlot = meta.registry.createType('Slot', relayCurrentSlot + relaySlotIncrease);
128
+ logger.debug({
129
+ relayCurrentSlot,
130
+ newSlot: newSlot.toNumber()
131
+ }, 'Updating relay current slot');
101
132
  newEntries.push([
102
133
  key,
103
134
  (0, _util.u8aToHex)(newSlot.toU8a())
@@ -7,10 +7,18 @@ export declare enum StorageValueKind {
7
7
  }
8
8
  export type StorageValue = string | StorageValueKind | undefined;
9
9
  export interface StorageLayerProvider {
10
+ /**
11
+ * Returns true if key is deleted
12
+ */
13
+ deleted(key: string): boolean;
10
14
  /**
11
15
  * Get the value of a storage key.
12
16
  */
13
17
  get(key: string, cache: boolean): Promise<StorageValue>;
18
+ /**
19
+ * Get the value of many storage keys.
20
+ */
21
+ getMany(keys: string[], _cache: boolean): Promise<StorageValue[]>;
14
22
  /**
15
23
  * Get paged storage keys.
16
24
  */
@@ -23,14 +31,18 @@ export interface StorageLayerProvider {
23
31
  export declare class RemoteStorageLayer implements StorageLayerProvider {
24
32
  #private;
25
33
  constructor(api: Api, at: HexString, db: Database | undefined);
34
+ deleted(_key: string): boolean;
26
35
  get(key: string, _cache: boolean): Promise<StorageValue>;
36
+ getMany(keys: string[], _cache: boolean): Promise<StorageValue[]>;
27
37
  findNextKey(prefix: string, startKey: string, _knownBest?: string): Promise<string | undefined>;
28
38
  getKeysPaged(prefix: string, pageSize: number, startKey: string): Promise<string[]>;
29
39
  }
30
40
  export declare class StorageLayer implements StorageLayerProvider {
31
41
  #private;
32
42
  constructor(parent?: StorageLayerProvider);
43
+ deleted(key: string): boolean;
33
44
  get(key: string, cache: boolean): Promise<StorageValue | undefined>;
45
+ getMany(keys: string[], cache: boolean): Promise<StorageValue[]>;
34
46
  set(key: string, value: StorageValue): void;
35
47
  setAll(values: Record<string, StorageValue | null> | [string, StorageValue | null][]): void;
36
48
  findNextKey(prefix: string, startKey: string, knownBest?: string): Promise<string | undefined>;
@@ -89,6 +89,9 @@ var StorageValueKind = /*#__PURE__*/ function(StorageValueKind) {
89
89
  }({});
90
90
  var _api = /*#__PURE__*/ new WeakMap(), _at = /*#__PURE__*/ new WeakMap(), _db = /*#__PURE__*/ new WeakMap(), _keyCache = /*#__PURE__*/ new WeakMap(), _defaultChildKeyCache = /*#__PURE__*/ new WeakMap();
91
91
  class RemoteStorageLayer {
92
+ deleted(_key) {
93
+ return false;
94
+ }
92
95
  async get(key, _cache) {
93
96
  if (_class_private_field_get(this, _db)) {
94
97
  const res = await _class_private_field_get(this, _db).queryStorage(_class_private_field_get(this, _at), key);
@@ -104,6 +107,48 @@ class RemoteStorageLayer {
104
107
  _class_private_field_get(this, _db)?.saveStorage(_class_private_field_get(this, _at), key, data);
105
108
  return data ?? undefined;
106
109
  }
110
+ async getMany(keys, _cache) {
111
+ const result = [];
112
+ let pending = keys.map((key, idx)=>({
113
+ key,
114
+ idx
115
+ }));
116
+ if (_class_private_field_get(this, _db)) {
117
+ const results = await Promise.all(pending.map(({ key })=>_class_private_field_get(this, _db).queryStorage(_class_private_field_get(this, _at), key)));
118
+ const oldPending = pending;
119
+ pending = [];
120
+ results.forEach((res, idx)=>{
121
+ if (res) {
122
+ result[idx] = res.value ?? undefined;
123
+ } else {
124
+ pending.push({
125
+ key: oldPending[idx].key,
126
+ idx
127
+ });
128
+ }
129
+ });
130
+ }
131
+ if (pending.length) {
132
+ logger.trace({
133
+ at: _class_private_field_get(this, _at),
134
+ keys
135
+ }, 'RemoteStorageLayer getMany');
136
+ const data = await _class_private_field_get(this, _api).getStorageBatch('0x', pending.map(({ key })=>key), _class_private_field_get(this, _at));
137
+ data.forEach(([, res], idx)=>{
138
+ result[pending[idx].idx] = res ?? undefined;
139
+ });
140
+ if (_class_private_field_get(this, _db)?.saveStorageBatch) {
141
+ _class_private_field_get(this, _db)?.saveStorageBatch(data.map(([key, value])=>({
142
+ key,
143
+ value,
144
+ blockHash: _class_private_field_get(this, _at)
145
+ })));
146
+ } else if (_class_private_field_get(this, _db)) {
147
+ data.forEach(([key, value])=>_class_private_field_get(this, _db)?.saveStorage(_class_private_field_get(this, _at), key, value));
148
+ }
149
+ }
150
+ return result;
151
+ }
107
152
  async findNextKey(prefix, startKey, _knownBest) {
108
153
  const keys = await this.getKeysPaged(prefix, 1, startKey);
109
154
  return keys[0];
@@ -205,6 +250,18 @@ class RemoteStorageLayer {
205
250
  }
206
251
  var _store = /*#__PURE__*/ new WeakMap(), _keys = /*#__PURE__*/ new WeakMap(), _deletedPrefix = /*#__PURE__*/ new WeakMap(), _parent = /*#__PURE__*/ new WeakMap(), _addKey = /*#__PURE__*/ new WeakSet(), _removeKey = /*#__PURE__*/ new WeakSet();
207
252
  class StorageLayer {
253
+ deleted(key) {
254
+ if (_class_private_field_get(this, _store).has(key)) {
255
+ return _class_private_field_get(this, _store).get(key) === "Deleted";
256
+ }
257
+ if (_class_private_field_get(this, _deletedPrefix).some((dp)=>key.startsWith(dp))) {
258
+ return true;
259
+ }
260
+ if (_class_private_field_get(this, _parent)) {
261
+ return _class_private_field_get(this, _parent).deleted(key);
262
+ }
263
+ return false;
264
+ }
208
265
  async get(key, cache) {
209
266
  if (_class_private_field_get(this, _store).has(key)) {
210
267
  return _class_private_field_get(this, _store).get(key);
@@ -221,6 +278,33 @@ class StorageLayer {
221
278
  }
222
279
  return undefined;
223
280
  }
281
+ async getMany(keys, cache) {
282
+ const result = [];
283
+ const pending = [];
284
+ const preloadedPromises = keys.map(async (key, idx)=>{
285
+ if (_class_private_field_get(this, _store).has(key)) {
286
+ result[idx] = await _class_private_field_get(this, _store).get(key);
287
+ } else if (_class_private_field_get(this, _deletedPrefix).some((dp)=>key.startsWith(dp))) {
288
+ result[idx] = "Deleted";
289
+ } else {
290
+ pending.push({
291
+ key,
292
+ idx
293
+ });
294
+ }
295
+ });
296
+ if (pending.length && _class_private_field_get(this, _parent)) {
297
+ const vals = await _class_private_field_get(this, _parent).getMany(pending.map((p)=>p.key), false);
298
+ vals.forEach((val, idx)=>{
299
+ if (cache) {
300
+ _class_private_field_get(this, _store).set(pending[idx].key, val);
301
+ }
302
+ result[pending[idx].idx] = val;
303
+ });
304
+ }
305
+ await Promise.all(preloadedPromises);
306
+ return result;
307
+ }
224
308
  set(key, value) {
225
309
  switch(value){
226
310
  case "Deleted":
@@ -283,7 +367,7 @@ class StorageLayer {
283
367
  const next = await this.findNextKey(prefix, startKey, undefined);
284
368
  if (!next) break;
285
369
  startKey = next;
286
- if (await this.get(next, false) === "Deleted") continue;
370
+ if (this.deleted(next)) continue;
287
371
  keys.push(next);
288
372
  }
289
373
  return keys;
@@ -24,10 +24,10 @@ async function getDescendantValues(block, params) {
24
24
  ...params,
25
25
  pageSize: PAGE_SIZE
26
26
  });
27
- const items = await Promise.all(keys.map((key)=>block.get(key).then((value)=>({
28
- key,
29
- value
30
- }))));
27
+ const items = (await block.getMany(keys)).map((value, idx)=>({
28
+ key: keys[idx],
29
+ value
30
+ }));
31
31
  if (keys.length < PAGE_SIZE) {
32
32
  return {
33
33
  items,
package/dist/esm/api.d.ts CHANGED
@@ -33,7 +33,7 @@ export declare class Api {
33
33
  getBlock(hash?: string): Promise<SignedBlock | null>;
34
34
  getStorage(key: string, hash?: string): Promise<`0x${string}` | null>;
35
35
  getKeysPaged(prefix: string, pageSize: number, startKey: string, hash?: string): Promise<`0x${string}`[]>;
36
- getStorageBatch(prefix: HexString, keys: HexString[], hash?: HexString): Promise<[`0x${string}`, `0x${string}` | null][]>;
36
+ getStorageBatch(prefix: HexString, keys: HexString[], hash?: HexString): any;
37
37
  subscribeRemoteNewHeads(cb: ProviderInterfaceCallback): Promise<string | number>;
38
38
  subscribeRemoteFinalizedHeads(cb: ProviderInterfaceCallback): Promise<string | number>;
39
39
  }
package/dist/esm/api.js CHANGED
@@ -1,3 +1,4 @@
1
+ import RpcError from '@polkadot/rpc-provider/coder/error';
1
2
  import _ from 'lodash';
2
3
  import { prefixedChildKey, splitChildKey, stripChildPrefix } from './utils/index.js';
3
4
  /**
@@ -131,6 +132,21 @@ import { prefixedChildKey, splitChildKey, stripChildPrefix } from './utils/index
131
132
  return this.send('state_getKeysPaged', params, !!hash);
132
133
  }
133
134
  async getStorageBatch(prefix, keys, hash) {
135
+ // On response limit error, retry with a smaller batch size
136
+ const retryOnError = async (ex)=>{
137
+ if (ex instanceof RpcError && (ex.code === -32008 || ex.message === 'Response is too big')) {
138
+ // Can't split beyond key size = 2
139
+ if (keys.length < 2) throw ex;
140
+ const mid = Math.floor(keys.length / 2);
141
+ const batches = [
142
+ keys.slice(0, mid),
143
+ keys.slice(mid)
144
+ ];
145
+ const results = await Promise.all(batches.map((batch)=>this.getStorageBatch(prefix, batch, hash)));
146
+ return results.flat();
147
+ }
148
+ throw ex;
149
+ };
134
150
  const [child] = splitChildKey(prefix);
135
151
  if (child) {
136
152
  // child storage key, use childstate_getStorageEntries
@@ -140,14 +156,14 @@ import { prefixedChildKey, splitChildKey, stripChildPrefix } from './utils/index
140
156
  keys.map((key)=>stripChildPrefix(key))
141
157
  ];
142
158
  if (hash) params.push(hash);
143
- return this.#provider.send('childstate_getStorageEntries', params, !!hash).then((values)=>_.zip(keys, values));
159
+ return this.#provider.send('childstate_getStorageEntries', params, !!hash).then((values)=>_.zip(keys, values)).catch(retryOnError);
144
160
  }
145
161
  // main storage key, use state_getStorageAt
146
162
  const params = [
147
163
  keys
148
164
  ];
149
165
  if (hash) params.push(hash);
150
- return this.#provider.send('state_queryStorageAt', params, !!hash).then((result)=>result[0]?.['changes'] || []);
166
+ return this.#provider.send('state_queryStorageAt', params, !!hash).then((result)=>result[0]?.['changes'] || []).catch(retryOnError);
151
167
  }
152
168
  async subscribeRemoteNewHeads(cb) {
153
169
  if (!this.#provider.hasSubscriptions) {
@@ -48,6 +48,10 @@ export declare class Block {
48
48
  * Get the block storage by key.
49
49
  */
50
50
  get(key: string): Promise<HexString | undefined>;
51
+ /**
52
+ * Get the block storage by key.
53
+ */
54
+ getMany(keys: string[]): Promise<Array<HexString | undefined>>;
51
55
  read<T extends string>(type: T, query: StorageEntry, ...args: any[]): Promise<import("@polkadot/types/types").DetectCodec<import("@polkadot/types-codec/types").Codec, T> | undefined>;
52
56
  /**
53
57
  * Get paged storage keys.
@@ -117,6 +117,19 @@ import { RemoteStorageLayer, StorageLayer, StorageValueKind } from './storage-la
117
117
  return val;
118
118
  }
119
119
  }
120
+ /**
121
+ * Get the block storage by key.
122
+ */ async getMany(keys) {
123
+ const vals = await this.storage.getMany(keys, true);
124
+ return vals.map((val)=>{
125
+ switch(val){
126
+ case StorageValueKind.Deleted:
127
+ return undefined;
128
+ default:
129
+ return val;
130
+ }
131
+ });
132
+ }
120
133
  async read(type, query, ...args) {
121
134
  const key = compactHex(query(...args));
122
135
  const value = await this.get(key);
@@ -2,9 +2,14 @@ import { GenericExtrinsic } from '@polkadot/types';
2
2
  import { hexToU8a, u8aConcat, u8aToHex } from '@polkadot/util';
3
3
  import { blake2AsHex, blake2AsU8a } from '@polkadot/util-crypto';
4
4
  import _ from 'lodash';
5
- import { compactHex, getCurrentSlot, getParaId } from '../../../utils/index.js';
5
+ import { defaultLogger } from '../../../logger.js';
6
+ import { compactHex, getCurrentSlot, getParaId, getSlotDuration } from '../../../utils/index.js';
6
7
  import { dmqMqcHead, hrmpChannels, hrmpEgressChannelIndex, hrmpIngressChannelIndex, paraHead, upgradeGoAheadSignal, WELL_KNOWN_KEYS } from '../../../utils/proof.js';
7
8
  import { createProof, decodeProof } from '../../../wasm-executor/index.js';
9
+ const logger = defaultLogger.child({
10
+ name: 'parachain-validation-data'
11
+ });
12
+ const RELAY_CHAIN_SLOT_DURATION_MILLIS = 6_000;
8
13
  const MOCK_VALIDATION_DATA = {
9
14
  validationData: {
10
15
  relayParentNumber: 1000,
@@ -31,7 +36,7 @@ const MOCK_VALIDATION_DATA = {
31
36
  horizontalMessages: [],
32
37
  downwardMessages: []
33
38
  };
34
- const getValidationData = async (parent)=>{
39
+ const getValidationData = async (parent, fallback = true)=>{
35
40
  const meta = await parent.meta;
36
41
  if (parent.number === 0) {
37
42
  const { trieRootHash, nodes } = await createProof(MOCK_VALIDATION_DATA.relayChainState.trieNodes, []);
@@ -46,15 +51,39 @@ const getValidationData = async (parent)=>{
46
51
  }
47
52
  };
48
53
  }
49
- const extrinsics = await parent.extrinsics;
50
- const validationDataExtrinsic = extrinsics.find((extrinsic)=>{
51
- const firstArg = meta.registry.createType('GenericExtrinsic', extrinsic)?.args?.[0];
52
- return firstArg && 'validationData' in firstArg;
53
- });
54
- if (!validationDataExtrinsic) {
55
- throw new Error('Missing validation data from block');
54
+ try {
55
+ const extrinsics = await parent.extrinsics;
56
+ const validationDataExtrinsic = extrinsics.find((extrinsic)=>{
57
+ const firstArg = meta.registry.createType('GenericExtrinsic', extrinsic)?.args?.[0];
58
+ return firstArg && 'validationData' in firstArg;
59
+ });
60
+ if (!validationDataExtrinsic) {
61
+ throw new Error('Missing validation data from block');
62
+ }
63
+ return meta.registry.createType('GenericExtrinsic', validationDataExtrinsic).args[0].toJSON();
64
+ } catch (e) {
65
+ logger.warn('Failed to get validation data from block %d %s', parent.number, e);
66
+ if (fallback) {
67
+ // this could fail due to wasm override that breaks the validation data format
68
+ // so we will try parent's parent
69
+ const grandParent = await parent.parentBlock;
70
+ if (grandParent) {
71
+ const data = await getValidationData(grandParent, false);
72
+ return {
73
+ ...data,
74
+ validationData: {
75
+ ...data.validationData,
76
+ relayParentNumber: data.validationData.relayParentNumber + 2
77
+ }
78
+ };
79
+ } else {
80
+ throw e;
81
+ }
82
+ } else {
83
+ // fallback failed, throw error
84
+ throw e;
85
+ }
56
86
  }
57
- return meta.registry.createType('GenericExtrinsic', validationDataExtrinsic).args[0].toJSON();
58
87
  };
59
88
  export class SetValidationData {
60
89
  async createInherents(newBlock, params) {
@@ -73,16 +102,18 @@ export class SetValidationData {
73
102
  const hrmpIngressChannelIndexKey = hrmpIngressChannelIndex(paraId);
74
103
  const hrmpEgressChannelIndexKey = hrmpEgressChannelIndex(paraId);
75
104
  const decoded = await decodeProof(extrinsic.validationData.relayParentStorageRoot, extrinsic.relayChainState.trieNodes);
76
- const relaySlotIncrease = Math.max(1, meta.consts.timestamp?.minimumPeriod // legacy
77
- ?.divn(3000) // relaychain min period
78
- ?.toNumber() || meta.consts.aura?.slotDuration // async backing
79
- ?.divn(6000) // relaychain block time
80
- ?.toNumber() || 1);
105
+ const slotDuration = await getSlotDuration(newBlock);
106
+ const relaySlotIncrease = Math.trunc(slotDuration / RELAY_CHAIN_SLOT_DURATION_MILLIS) || 1 // at least increase by 1
107
+ ;
81
108
  for (const key of Object.values(WELL_KNOWN_KEYS)){
82
109
  if (key === WELL_KNOWN_KEYS.CURRENT_SLOT) {
83
110
  // increment current slot
84
111
  const relayCurrentSlot = decoded[key] ? meta.registry.createType('Slot', hexToU8a(decoded[key])).toNumber() : await getCurrentSlot(parent) * relaySlotIncrease;
85
112
  const newSlot = meta.registry.createType('Slot', relayCurrentSlot + relaySlotIncrease);
113
+ logger.debug({
114
+ relayCurrentSlot,
115
+ newSlot: newSlot.toNumber()
116
+ }, 'Updating relay current slot');
86
117
  newEntries.push([
87
118
  key,
88
119
  u8aToHex(newSlot.toU8a())
@@ -7,10 +7,18 @@ export declare enum StorageValueKind {
7
7
  }
8
8
  export type StorageValue = string | StorageValueKind | undefined;
9
9
  export interface StorageLayerProvider {
10
+ /**
11
+ * Returns true if key is deleted
12
+ */
13
+ deleted(key: string): boolean;
10
14
  /**
11
15
  * Get the value of a storage key.
12
16
  */
13
17
  get(key: string, cache: boolean): Promise<StorageValue>;
18
+ /**
19
+ * Get the value of many storage keys.
20
+ */
21
+ getMany(keys: string[], _cache: boolean): Promise<StorageValue[]>;
14
22
  /**
15
23
  * Get paged storage keys.
16
24
  */
@@ -23,14 +31,18 @@ export interface StorageLayerProvider {
23
31
  export declare class RemoteStorageLayer implements StorageLayerProvider {
24
32
  #private;
25
33
  constructor(api: Api, at: HexString, db: Database | undefined);
34
+ deleted(_key: string): boolean;
26
35
  get(key: string, _cache: boolean): Promise<StorageValue>;
36
+ getMany(keys: string[], _cache: boolean): Promise<StorageValue[]>;
27
37
  findNextKey(prefix: string, startKey: string, _knownBest?: string): Promise<string | undefined>;
28
38
  getKeysPaged(prefix: string, pageSize: number, startKey: string): Promise<string[]>;
29
39
  }
30
40
  export declare class StorageLayer implements StorageLayerProvider {
31
41
  #private;
32
42
  constructor(parent?: StorageLayerProvider);
43
+ deleted(key: string): boolean;
33
44
  get(key: string, cache: boolean): Promise<StorageValue | undefined>;
45
+ getMany(keys: string[], cache: boolean): Promise<StorageValue[]>;
34
46
  set(key: string, value: StorageValue): void;
35
47
  setAll(values: Record<string, StorageValue | null> | [string, StorageValue | null][]): void;
36
48
  findNextKey(prefix: string, startKey: string, knownBest?: string): Promise<string | undefined>;
@@ -22,6 +22,9 @@ export class RemoteStorageLayer {
22
22
  this.#at = at;
23
23
  this.#db = db;
24
24
  }
25
+ deleted(_key) {
26
+ return false;
27
+ }
25
28
  async get(key, _cache) {
26
29
  if (this.#db) {
27
30
  const res = await this.#db.queryStorage(this.#at, key);
@@ -37,6 +40,48 @@ export class RemoteStorageLayer {
37
40
  this.#db?.saveStorage(this.#at, key, data);
38
41
  return data ?? undefined;
39
42
  }
43
+ async getMany(keys, _cache) {
44
+ const result = [];
45
+ let pending = keys.map((key, idx)=>({
46
+ key,
47
+ idx
48
+ }));
49
+ if (this.#db) {
50
+ const results = await Promise.all(pending.map(({ key })=>this.#db.queryStorage(this.#at, key)));
51
+ const oldPending = pending;
52
+ pending = [];
53
+ results.forEach((res, idx)=>{
54
+ if (res) {
55
+ result[idx] = res.value ?? undefined;
56
+ } else {
57
+ pending.push({
58
+ key: oldPending[idx].key,
59
+ idx
60
+ });
61
+ }
62
+ });
63
+ }
64
+ if (pending.length) {
65
+ logger.trace({
66
+ at: this.#at,
67
+ keys
68
+ }, 'RemoteStorageLayer getMany');
69
+ const data = await this.#api.getStorageBatch('0x', pending.map(({ key })=>key), this.#at);
70
+ data.forEach(([, res], idx)=>{
71
+ result[pending[idx].idx] = res ?? undefined;
72
+ });
73
+ if (this.#db?.saveStorageBatch) {
74
+ this.#db?.saveStorageBatch(data.map(([key, value])=>({
75
+ key,
76
+ value,
77
+ blockHash: this.#at
78
+ })));
79
+ } else if (this.#db) {
80
+ data.forEach(([key, value])=>this.#db?.saveStorage(this.#at, key, value));
81
+ }
82
+ }
83
+ return result;
84
+ }
40
85
  async findNextKey(prefix, startKey, _knownBest) {
41
86
  const keys = await this.getKeysPaged(prefix, 1, startKey);
42
87
  return keys[0];
@@ -134,6 +179,18 @@ export class StorageLayer {
134
179
  this.#keys.splice(idx, 1);
135
180
  }
136
181
  }
182
+ deleted(key) {
183
+ if (this.#store.has(key)) {
184
+ return this.#store.get(key) === "Deleted";
185
+ }
186
+ if (this.#deletedPrefix.some((dp)=>key.startsWith(dp))) {
187
+ return true;
188
+ }
189
+ if (this.#parent) {
190
+ return this.#parent.deleted(key);
191
+ }
192
+ return false;
193
+ }
137
194
  async get(key, cache) {
138
195
  if (this.#store.has(key)) {
139
196
  return this.#store.get(key);
@@ -150,6 +207,33 @@ export class StorageLayer {
150
207
  }
151
208
  return undefined;
152
209
  }
210
+ async getMany(keys, cache) {
211
+ const result = [];
212
+ const pending = [];
213
+ const preloadedPromises = keys.map(async (key, idx)=>{
214
+ if (this.#store.has(key)) {
215
+ result[idx] = await this.#store.get(key);
216
+ } else if (this.#deletedPrefix.some((dp)=>key.startsWith(dp))) {
217
+ result[idx] = "Deleted";
218
+ } else {
219
+ pending.push({
220
+ key,
221
+ idx
222
+ });
223
+ }
224
+ });
225
+ if (pending.length && this.#parent) {
226
+ const vals = await this.#parent.getMany(pending.map((p)=>p.key), false);
227
+ vals.forEach((val, idx)=>{
228
+ if (cache) {
229
+ this.#store.set(pending[idx].key, val);
230
+ }
231
+ result[pending[idx].idx] = val;
232
+ });
233
+ }
234
+ await Promise.all(preloadedPromises);
235
+ return result;
236
+ }
153
237
  set(key, value) {
154
238
  switch(value){
155
239
  case "Deleted":
@@ -212,7 +296,7 @@ export class StorageLayer {
212
296
  const next = await this.findNextKey(prefix, startKey, undefined);
213
297
  if (!next) break;
214
298
  startKey = next;
215
- if (await this.get(next, false) === "Deleted") continue;
299
+ if (this.deleted(next)) continue;
216
300
  keys.push(next);
217
301
  }
218
302
  return keys;
@@ -3,10 +3,10 @@ export async function getDescendantValues(block, params) {
3
3
  ...params,
4
4
  pageSize: PAGE_SIZE
5
5
  });
6
- const items = await Promise.all(keys.map((key)=>block.get(key).then((value)=>({
7
- key,
8
- value
9
- }))));
6
+ const items = (await block.getMany(keys)).map((value, idx)=>({
7
+ key: keys[idx],
8
+ value
9
+ }));
10
10
  if (keys.length < PAGE_SIZE) {
11
11
  return {
12
12
  items,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acala-network/chopsticks-core",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "author": "Acala Developers <hello@acala.network>",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -14,7 +14,7 @@
14
14
  "depcheck": "npx depcheck"
15
15
  },
16
16
  "dependencies": {
17
- "@acala-network/chopsticks-executor": "1.2.1",
17
+ "@acala-network/chopsticks-executor": "1.2.3",
18
18
  "@polkadot/rpc-provider": "^16.4.1",
19
19
  "@polkadot/types": "^16.4.1",
20
20
  "@polkadot/types-codec": "^16.4.1",