@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 +1 -1
- package/dist/cjs/api.js +18 -2
- package/dist/cjs/blockchain/block.d.ts +4 -0
- package/dist/cjs/blockchain/block.js +13 -0
- package/dist/cjs/blockchain/inherent/parachain/validation-data.js +45 -14
- package/dist/cjs/blockchain/storage-layer.d.ts +12 -0
- package/dist/cjs/blockchain/storage-layer.js +85 -1
- package/dist/cjs/rpc/rpc-spec/storage-common.js +4 -4
- package/dist/esm/api.d.ts +1 -1
- package/dist/esm/api.js +18 -2
- package/dist/esm/blockchain/block.d.ts +4 -0
- package/dist/esm/blockchain/block.js +13 -0
- package/dist/esm/blockchain/inherent/parachain/validation-data.js +46 -15
- package/dist/esm/blockchain/storage-layer.d.ts +12 -0
- package/dist/esm/blockchain/storage-layer.js +85 -1
- package/dist/esm/rpc/rpc-spec/storage-common.js +4 -4
- package/package.json +2 -2
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):
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
92
|
-
|
|
93
|
-
|
|
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 (
|
|
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
|
|
28
|
-
|
|
29
|
-
|
|
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):
|
|
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 {
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
77
|
-
|
|
78
|
-
|
|
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 (
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
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.
|
|
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.
|
|
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",
|