@acala-network/chopsticks-core 1.3.1 → 1.4.0
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 +2 -0
- package/dist/cjs/api.js +49 -6
- package/dist/cjs/blockchain/block-builder.d.ts +16 -1
- package/dist/cjs/blockchain/block-builder.js +22 -0
- package/dist/cjs/blockchain/block.d.ts +10 -0
- package/dist/cjs/blockchain/block.js +12 -0
- package/dist/cjs/blockchain/head-state.js +27 -7
- package/dist/cjs/blockchain/index.d.ts +9 -0
- package/dist/cjs/blockchain/index.js +18 -0
- package/dist/cjs/blockchain/storage-layer.js +47 -15
- package/dist/cjs/database.d.ts +4 -0
- package/dist/cjs/setup.js +4 -0
- package/dist/esm/api.d.ts +2 -0
- package/dist/esm/api.js +31 -5
- package/dist/esm/blockchain/block-builder.d.ts +16 -1
- package/dist/esm/blockchain/block-builder.js +28 -0
- package/dist/esm/blockchain/block.d.ts +10 -0
- package/dist/esm/blockchain/block.js +12 -0
- package/dist/esm/blockchain/head-state.js +27 -7
- package/dist/esm/blockchain/index.d.ts +9 -0
- package/dist/esm/blockchain/index.js +19 -1
- package/dist/esm/blockchain/storage-layer.js +43 -14
- package/dist/esm/database.d.ts +4 -0
- package/dist/esm/setup.js +4 -0
- package/package.json +2 -2
package/dist/cjs/api.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ProviderInterface, ProviderInterfaceCallback } from '@polkadot/rpc-provider/types';
|
|
2
2
|
import type { ExtDef } from '@polkadot/types/extrinsic/signedExtensions/types';
|
|
3
3
|
import type { HexString } from '@polkadot/util/types';
|
|
4
|
+
import type { Database } from './database.js';
|
|
4
5
|
import type { ChainProperties, Header, SignedBlock } from './index.js';
|
|
5
6
|
/**
|
|
6
7
|
* API class. Calls provider to get on-chain data.
|
|
@@ -18,6 +19,7 @@ export declare class Api {
|
|
|
18
19
|
#private;
|
|
19
20
|
readonly signedExtensions: ExtDef;
|
|
20
21
|
constructor(provider: ProviderInterface, signedExtensions?: ExtDef);
|
|
22
|
+
setDb(db: Database, scope: string): void;
|
|
21
23
|
disconnect(): Promise<void>;
|
|
22
24
|
get isReady(): Promise<void> | undefined;
|
|
23
25
|
get chain(): Promise<string>;
|
package/dist/cjs/api.js
CHANGED
|
@@ -51,6 +51,16 @@ function _class_private_field_set(receiver, privateMap, value) {
|
|
|
51
51
|
_class_apply_descriptor_set(receiver, descriptor, value);
|
|
52
52
|
return value;
|
|
53
53
|
}
|
|
54
|
+
function _class_private_method_get(receiver, privateSet, fn) {
|
|
55
|
+
if (!privateSet.has(receiver)) {
|
|
56
|
+
throw new TypeError("attempted to get private field on non-instance");
|
|
57
|
+
}
|
|
58
|
+
return fn;
|
|
59
|
+
}
|
|
60
|
+
function _class_private_method_init(obj, privateSet) {
|
|
61
|
+
_check_private_redeclaration(obj, privateSet);
|
|
62
|
+
privateSet.add(obj);
|
|
63
|
+
}
|
|
54
64
|
function _define_property(obj, key, value) {
|
|
55
65
|
if (key in obj) {
|
|
56
66
|
Object.defineProperty(obj, key, {
|
|
@@ -69,8 +79,19 @@ function _interop_require_default(obj) {
|
|
|
69
79
|
default: obj
|
|
70
80
|
};
|
|
71
81
|
}
|
|
72
|
-
|
|
82
|
+
const CACHEABLE_METHODS = new Set([
|
|
83
|
+
'system_chain',
|
|
84
|
+
'system_properties',
|
|
85
|
+
'system_name',
|
|
86
|
+
'chain_getBlockHash',
|
|
87
|
+
'chain_getHeader'
|
|
88
|
+
]);
|
|
89
|
+
var _provider = /*#__PURE__*/ new WeakMap(), _ready = /*#__PURE__*/ new WeakMap(), _chain = /*#__PURE__*/ new WeakMap(), _chainProperties = /*#__PURE__*/ new WeakMap(), _db = /*#__PURE__*/ new WeakMap(), _scope = /*#__PURE__*/ new WeakMap(), _apiHooks = /*#__PURE__*/ new WeakMap(), _cachedSend = /*#__PURE__*/ new WeakSet();
|
|
73
90
|
class Api {
|
|
91
|
+
setDb(db, scope) {
|
|
92
|
+
_class_private_field_set(this, _db, db);
|
|
93
|
+
_class_private_field_set(this, _scope, scope);
|
|
94
|
+
}
|
|
74
95
|
async disconnect() {
|
|
75
96
|
return _class_private_field_get(this, _provider).disconnect();
|
|
76
97
|
}
|
|
@@ -112,21 +133,21 @@ class Api {
|
|
|
112
133
|
return _class_private_field_get(this, _provider).send(method, params, isCacheable);
|
|
113
134
|
}
|
|
114
135
|
async getSystemName() {
|
|
115
|
-
return this.
|
|
136
|
+
return _class_private_method_get(this, _cachedSend, cachedSend).call(this, 'system_name', []);
|
|
116
137
|
}
|
|
117
138
|
async getSystemProperties() {
|
|
118
|
-
return this.
|
|
139
|
+
return _class_private_method_get(this, _cachedSend, cachedSend).call(this, 'system_properties', []);
|
|
119
140
|
}
|
|
120
141
|
async getSystemChain() {
|
|
121
|
-
return this.
|
|
142
|
+
return _class_private_method_get(this, _cachedSend, cachedSend).call(this, 'system_chain', []);
|
|
122
143
|
}
|
|
123
144
|
async getBlockHash(blockNumber) {
|
|
124
|
-
return this.
|
|
145
|
+
return _class_private_method_get(this, _cachedSend, cachedSend).call(this, 'chain_getBlockHash', Number.isInteger(blockNumber) ? [
|
|
125
146
|
blockNumber
|
|
126
147
|
] : [], !!blockNumber);
|
|
127
148
|
}
|
|
128
149
|
async getHeader(hash) {
|
|
129
|
-
return this.
|
|
150
|
+
return _class_private_method_get(this, _cachedSend, cachedSend).call(this, 'chain_getHeader', hash ? [
|
|
130
151
|
hash
|
|
131
152
|
] : [], !!hash);
|
|
132
153
|
}
|
|
@@ -226,6 +247,7 @@ class Api {
|
|
|
226
247
|
return _class_private_field_get(this, _provider).subscribe('chain_finalizedHead', 'chain_subscribeFinalizedHeads', [], cb);
|
|
227
248
|
}
|
|
228
249
|
constructor(provider, signedExtensions){
|
|
250
|
+
_class_private_method_init(this, _cachedSend);
|
|
229
251
|
_class_private_field_init(this, _provider, {
|
|
230
252
|
writable: true,
|
|
231
253
|
value: void 0
|
|
@@ -242,6 +264,14 @@ class Api {
|
|
|
242
264
|
writable: true,
|
|
243
265
|
value: void 0
|
|
244
266
|
});
|
|
267
|
+
_class_private_field_init(this, _db, {
|
|
268
|
+
writable: true,
|
|
269
|
+
value: void 0
|
|
270
|
+
});
|
|
271
|
+
_class_private_field_init(this, _scope, {
|
|
272
|
+
writable: true,
|
|
273
|
+
value: void 0
|
|
274
|
+
});
|
|
245
275
|
_define_property(this, "signedExtensions", void 0);
|
|
246
276
|
_class_private_field_init(this, _apiHooks, {
|
|
247
277
|
writable: true,
|
|
@@ -251,3 +281,16 @@ class Api {
|
|
|
251
281
|
this.signedExtensions = signedExtensions || {};
|
|
252
282
|
}
|
|
253
283
|
}
|
|
284
|
+
async function cachedSend(method, params, isCacheable) {
|
|
285
|
+
if (_class_private_field_get(this, _db)?.queryRpcCall && _class_private_field_get(this, _scope) && CACHEABLE_METHODS.has(method)) {
|
|
286
|
+
const key = JSON.stringify(params);
|
|
287
|
+
const cached = await _class_private_field_get(this, _db).queryRpcCall(_class_private_field_get(this, _scope), method, key);
|
|
288
|
+
if (cached !== null) return JSON.parse(cached);
|
|
289
|
+
_class_private_field_get(this, _apiHooks)?.fetching?.();
|
|
290
|
+
const result = await _class_private_field_get(this, _provider).send(method, params, isCacheable);
|
|
291
|
+
await _class_private_field_get(this, _db).saveRpcCall?.(_class_private_field_get(this, _scope), method, key, JSON.stringify(result));
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
_class_private_field_get(this, _apiHooks)?.fetching?.();
|
|
295
|
+
return _class_private_field_get(this, _provider).send(method, params, isCacheable);
|
|
296
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Header, TransactionValidityError } from '@polkadot/types/interfaces';
|
|
1
|
+
import type { ApplyExtrinsicResult, Header, TransactionValidityError } from '@polkadot/types/interfaces';
|
|
2
2
|
import type { HexString } from '@polkadot/util/types';
|
|
3
3
|
import type { TaskCallResponse } from '../wasm-executor/index.js';
|
|
4
4
|
import { Block } from './block.js';
|
|
@@ -10,6 +10,21 @@ export type BuildBlockCallbacks = {
|
|
|
10
10
|
onPhaseApplied?: (phase: 'initialize' | 'finalize' | number, resp: TaskCallResponse) => void;
|
|
11
11
|
};
|
|
12
12
|
export declare const buildBlock: (head: Block, inherentProviders: InherentProvider[], params: BuildBlockParams, callbacks?: BuildBlockCallbacks) => Promise<[Block, HexString[]]>;
|
|
13
|
+
export type DryRunResult = {
|
|
14
|
+
outcome: ApplyExtrinsicResult;
|
|
15
|
+
storageDiff: [HexString, HexString | null][];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Dry-run a batch of extrinsics against the same initialized block.
|
|
19
|
+
*
|
|
20
|
+
* Core_initialize_block and the inherents are executed once. Each extrinsic
|
|
21
|
+
* is then applied on a temporary storage layer that is pushed before the call
|
|
22
|
+
* and popped after, so no extrinsic side-effects leak into the next.
|
|
23
|
+
*
|
|
24
|
+
* Caveat: because account nonces revert with each pop, all extrinsics must be
|
|
25
|
+
* pre-signed with the same base nonce.
|
|
26
|
+
*/
|
|
27
|
+
export declare const dryRunExtrinsicsAmortized: (head: Block, inherentProviders: InherentProvider[], extrinsics: HexString[], params: BuildBlockParams) => Promise<DryRunResult[]>;
|
|
13
28
|
export declare const dryRunExtrinsic: (head: Block, inherentProviders: InherentProvider[], extrinsic: HexString | {
|
|
14
29
|
call: HexString;
|
|
15
30
|
address: string;
|
|
@@ -15,6 +15,9 @@ _export(exports, {
|
|
|
15
15
|
get dryRunExtrinsic () {
|
|
16
16
|
return dryRunExtrinsic;
|
|
17
17
|
},
|
|
18
|
+
get dryRunExtrinsicsAmortized () {
|
|
19
|
+
return dryRunExtrinsicsAmortized;
|
|
20
|
+
},
|
|
18
21
|
get dryRunInherents () {
|
|
19
22
|
return dryRunInherents;
|
|
20
23
|
},
|
|
@@ -378,6 +381,25 @@ const buildBlock = async (head, inherentProviders, params, callbacks)=>{
|
|
|
378
381
|
pendingExtrinsics
|
|
379
382
|
];
|
|
380
383
|
};
|
|
384
|
+
const dryRunExtrinsicsAmortized = async (head, inherentProviders, extrinsics, params)=>{
|
|
385
|
+
const registry = await head.registry;
|
|
386
|
+
const header = await newHeader(head);
|
|
387
|
+
const { block: newBlock } = await initNewBlock(head, header, inherentProviders, params);
|
|
388
|
+
const results = [];
|
|
389
|
+
for (const extrinsic of extrinsics){
|
|
390
|
+
newBlock.pushStorageLayer();
|
|
391
|
+
const resp = await newBlock.call('BlockBuilder_apply_extrinsic', [
|
|
392
|
+
extrinsic
|
|
393
|
+
]);
|
|
394
|
+
const outcome = registry.createType('ApplyExtrinsicResult', resp.result);
|
|
395
|
+
results.push({
|
|
396
|
+
outcome,
|
|
397
|
+
storageDiff: resp.storageDiff
|
|
398
|
+
});
|
|
399
|
+
newBlock.popStorageLayer();
|
|
400
|
+
}
|
|
401
|
+
return results;
|
|
402
|
+
};
|
|
381
403
|
const dryRunExtrinsic = async (head, inherentProviders, extrinsic, params)=>{
|
|
382
404
|
const registry = await head.registry;
|
|
383
405
|
const header = await newHeader(head);
|
|
@@ -69,6 +69,16 @@ export declare class Block {
|
|
|
69
69
|
* Pop a layer from the storage stack.
|
|
70
70
|
*/
|
|
71
71
|
popStorageLayer(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Truncate the storage layer stack back to a target depth.
|
|
74
|
+
*
|
|
75
|
+
* Used by snapshot-restore mechanisms to undo accumulated dev.setStorage
|
|
76
|
+
* calls when reverting to a previously captured state, without losing the
|
|
77
|
+
* underlying block.
|
|
78
|
+
*/
|
|
79
|
+
resetStorageLayers(targetCount: number): void;
|
|
80
|
+
/** The current depth of the storage layer stack. */
|
|
81
|
+
get storageLayerCount(): number;
|
|
72
82
|
/**
|
|
73
83
|
* Get storage diff.
|
|
74
84
|
*/
|
|
@@ -170,6 +170,18 @@ class Block {
|
|
|
170
170
|
_class_private_field_get(this, _storages).pop();
|
|
171
171
|
}
|
|
172
172
|
/**
|
|
173
|
+
* Truncate the storage layer stack back to a target depth.
|
|
174
|
+
*
|
|
175
|
+
* Used by snapshot-restore mechanisms to undo accumulated dev.setStorage
|
|
176
|
+
* calls when reverting to a previously captured state, without losing the
|
|
177
|
+
* underlying block.
|
|
178
|
+
*/ resetStorageLayers(targetCount) {
|
|
179
|
+
while(_class_private_field_get(this, _storages).length > targetCount)_class_private_field_get(this, _storages).pop();
|
|
180
|
+
}
|
|
181
|
+
/** The current depth of the storage layer stack. */ get storageLayerCount() {
|
|
182
|
+
return _class_private_field_get(this, _storages).length;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
173
185
|
* Get storage diff.
|
|
174
186
|
*/ async storageDiff() {
|
|
175
187
|
const storage = {};
|
|
@@ -95,20 +95,40 @@ class HeadState {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
const diff = await _class_private_field_get(this, _head).storageDiff();
|
|
98
|
+
const newValues = {
|
|
99
|
+
...diff
|
|
100
|
+
};
|
|
101
|
+
const watchedKeys = new Set();
|
|
102
|
+
for (const [keys] of Object.values(_class_private_field_get(this, _storageListeners))){
|
|
103
|
+
for (const key of keys)watchedKeys.add(key);
|
|
104
|
+
}
|
|
105
|
+
for (const key of watchedKeys){
|
|
106
|
+
if (newValues[key] === undefined && _class_private_field_get(this, _oldValues)[key] !== undefined) {
|
|
107
|
+
newValues[key] = await head.get(key) ?? null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
98
110
|
for (const [keys, cb] of Object.values(_class_private_field_get(this, _storageListeners))){
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
])
|
|
103
|
-
|
|
111
|
+
const changes = [];
|
|
112
|
+
for (const key of keys){
|
|
113
|
+
if (newValues[key] === undefined) continue;
|
|
114
|
+
if (newValues[key] !== _class_private_field_get(this, _oldValues)[key]) {
|
|
115
|
+
changes.push([
|
|
116
|
+
key,
|
|
117
|
+
newValues[key]
|
|
118
|
+
]);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (changes.length > 0) {
|
|
104
122
|
try {
|
|
105
|
-
await cb(head,
|
|
123
|
+
await cb(head, changes);
|
|
106
124
|
} catch (error) {
|
|
107
125
|
logger.error(error, 'setHead storage diff callback error');
|
|
108
126
|
}
|
|
109
127
|
}
|
|
110
128
|
}
|
|
111
|
-
|
|
129
|
+
for (const [key, value] of Object.entries(newValues)){
|
|
130
|
+
_class_private_field_get(this, _oldValues)[key] = value;
|
|
131
|
+
}
|
|
112
132
|
}
|
|
113
133
|
constructor(head){
|
|
114
134
|
_class_private_field_init(this, _headListeners, {
|
|
@@ -151,6 +151,15 @@ export declare class Blockchain {
|
|
|
151
151
|
outcome: ApplyExtrinsicResult;
|
|
152
152
|
storageDiff: [HexString, HexString | null][];
|
|
153
153
|
}>;
|
|
154
|
+
/**
|
|
155
|
+
* Dry-run a batch of extrinsics in block `at`, amortizing block initialization
|
|
156
|
+
* across the whole batch. Each extrinsic is applied independently — its storage
|
|
157
|
+
* changes do not accumulate into subsequent extrinsics.
|
|
158
|
+
*/
|
|
159
|
+
dryRunExtrinsicsAmortized(extrinsics: HexString[], at?: HexString): Promise<{
|
|
160
|
+
outcome: ApplyExtrinsicResult;
|
|
161
|
+
storageDiff: [HexString, HexString | null][];
|
|
162
|
+
}[]>;
|
|
154
163
|
/**
|
|
155
164
|
* Dry run hrmp messages in block `at`.
|
|
156
165
|
* Return the storage diff.
|
|
@@ -326,6 +326,24 @@ class Blockchain {
|
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
328
|
/**
|
|
329
|
+
* Dry-run a batch of extrinsics in block `at`, amortizing block initialization
|
|
330
|
+
* across the whole batch. Each extrinsic is applied independently — its storage
|
|
331
|
+
* changes do not accumulate into subsequent extrinsics.
|
|
332
|
+
*/ async dryRunExtrinsicsAmortized(extrinsics, at) {
|
|
333
|
+
await this.api.isReady;
|
|
334
|
+
const head = at ? await this.getBlock(at) : this.head;
|
|
335
|
+
if (!head) {
|
|
336
|
+
throw new Error(`Cannot find block ${at}`);
|
|
337
|
+
}
|
|
338
|
+
const params = {
|
|
339
|
+
transactions: [],
|
|
340
|
+
downwardMessages: [],
|
|
341
|
+
upwardMessages: [],
|
|
342
|
+
horizontalMessages: {}
|
|
343
|
+
};
|
|
344
|
+
return (0, _blockbuilder.dryRunExtrinsicsAmortized)(head, _class_private_field_get(this, _inherentProviders), extrinsics, params);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
329
347
|
* Dry run hrmp messages in block `at`.
|
|
330
348
|
* Return the storage diff.
|
|
331
349
|
*/ async dryRunHrmp(hrmp, at) {
|
|
@@ -87,7 +87,7 @@ var StorageValueKind = /*#__PURE__*/ function(StorageValueKind) {
|
|
|
87
87
|
StorageValueKind["DeletedPrefix"] = "DeletedPrefix";
|
|
88
88
|
return StorageValueKind;
|
|
89
89
|
}({});
|
|
90
|
-
var _api = /*#__PURE__*/ new WeakMap(), _at = /*#__PURE__*/ new WeakMap(), _db = /*#__PURE__*/ new WeakMap(), _keyCache = /*#__PURE__*/ new WeakMap(), _defaultChildKeyCache = /*#__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(), _inflight = /*#__PURE__*/ new WeakMap();
|
|
91
91
|
class RemoteStorageLayer {
|
|
92
92
|
deleted(_key) {
|
|
93
93
|
return false;
|
|
@@ -99,13 +99,20 @@ class RemoteStorageLayer {
|
|
|
99
99
|
return res.value ?? undefined;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
+
const inflight = _class_private_field_get(this, _inflight).get(key);
|
|
103
|
+
if (inflight) return inflight;
|
|
102
104
|
logger.trace({
|
|
103
105
|
at: _class_private_field_get(this, _at),
|
|
104
106
|
key
|
|
105
107
|
}, 'RemoteStorageLayer get');
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
const fetch = _class_private_field_get(this, _api).getStorage(key, _class_private_field_get(this, _at)).then((data)=>{
|
|
109
|
+
_class_private_field_get(this, _db)?.saveStorage(_class_private_field_get(this, _at), key, data);
|
|
110
|
+
return data ?? undefined;
|
|
111
|
+
}).finally(()=>{
|
|
112
|
+
_class_private_field_get(this, _inflight).delete(key);
|
|
113
|
+
});
|
|
114
|
+
_class_private_field_get(this, _inflight).set(key, fetch);
|
|
115
|
+
return fetch;
|
|
109
116
|
}
|
|
110
117
|
async getMany(keys, _cache) {
|
|
111
118
|
const result = [];
|
|
@@ -165,12 +172,33 @@ class RemoteStorageLayer {
|
|
|
165
172
|
}, 'RemoteStorageLayer getKeysPaged');
|
|
166
173
|
const isChild = (0, _index.isPrefixedChildKey)(prefix);
|
|
167
174
|
const minPrefixLen = isChild ? _index.CHILD_PREFIX_LENGTH : _index.PREFIX_LENGTH;
|
|
168
|
-
//
|
|
169
|
-
|
|
175
|
+
// KeyCache groups by the first `minPrefixLen` chars; it cannot correctly answer queries
|
|
176
|
+
// whose prefix is longer than that grouping width, so proxy directly to upstream.
|
|
177
|
+
if (prefix.length < minPrefixLen || startKey.length < minPrefixLen || prefix.length > minPrefixLen || startKey.length > minPrefixLen) {
|
|
170
178
|
return _class_private_field_get(this, _api).getKeysPaged(prefix, pageSize, startKey, _class_private_field_get(this, _at));
|
|
171
179
|
}
|
|
180
|
+
const startKeyEqualsPrefix = startKey === prefix;
|
|
181
|
+
let cachedKeys;
|
|
182
|
+
if (_class_private_field_get(this, _db)?.queryPagedKeys && startKeyEqualsPrefix) {
|
|
183
|
+
const cached = await _class_private_field_get(this, _db).queryPagedKeys(_class_private_field_get(this, _at), prefix);
|
|
184
|
+
if (cached) {
|
|
185
|
+
cachedKeys = cached;
|
|
186
|
+
isChild ? _class_private_field_get(this, _defaultChildKeyCache).feed([
|
|
187
|
+
startKey,
|
|
188
|
+
...cached
|
|
189
|
+
]) : _class_private_field_get(this, _keyCache).feed([
|
|
190
|
+
startKey,
|
|
191
|
+
...cached
|
|
192
|
+
]);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
172
195
|
let batchComplete = false;
|
|
196
|
+
let fetchedNewKeys = false;
|
|
173
197
|
const keysPaged = [];
|
|
198
|
+
// Seed with cached keys so a cache-hit-then-extend doesn't truncate the persisted set.
|
|
199
|
+
const allFetchedKeys = cachedKeys ? [
|
|
200
|
+
...cachedKeys
|
|
201
|
+
] : [];
|
|
174
202
|
while(keysPaged.length < pageSize){
|
|
175
203
|
const nextKey = isChild ? await _class_private_field_get(this, _defaultChildKeyCache).next(startKey) : await _class_private_field_get(this, _keyCache).next(startKey);
|
|
176
204
|
if (nextKey) {
|
|
@@ -203,15 +231,7 @@ class RemoteStorageLayer {
|
|
|
203
231
|
break;
|
|
204
232
|
}
|
|
205
233
|
if (_class_private_field_get(this, _db)) {
|
|
206
|
-
|
|
207
|
-
const newBatch = [];
|
|
208
|
-
for (const key of batch){
|
|
209
|
-
const res = await _class_private_field_get(this, _db).queryStorage(_class_private_field_get(this, _at), key);
|
|
210
|
-
if (res) {
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
newBatch.push(key);
|
|
214
|
-
}
|
|
234
|
+
const newBatch = await Promise.all(batch.map((key)=>_class_private_field_get(this, _db).queryStorage(_class_private_field_get(this, _at), key).then((r)=>r ? null : key))).then((rs)=>rs.filter((k)=>k !== null));
|
|
215
235
|
if (newBatch.length > 0) {
|
|
216
236
|
// batch fetch storage values and save to db, they may be used later
|
|
217
237
|
_class_private_field_get(this, _api).getStorageBatch(prefix, newBatch, _class_private_field_get(this, _at)).then((storage)=>{
|
|
@@ -220,8 +240,15 @@ class RemoteStorageLayer {
|
|
|
220
240
|
}
|
|
221
241
|
});
|
|
222
242
|
}
|
|
243
|
+
if (startKeyEqualsPrefix) {
|
|
244
|
+
allFetchedKeys.push(...batch);
|
|
245
|
+
fetchedNewKeys = true;
|
|
246
|
+
}
|
|
223
247
|
}
|
|
224
248
|
}
|
|
249
|
+
if (_class_private_field_get(this, _db)?.savePagedKeys && startKeyEqualsPrefix && fetchedNewKeys) {
|
|
250
|
+
await _class_private_field_get(this, _db).savePagedKeys(_class_private_field_get(this, _at), prefix, allFetchedKeys);
|
|
251
|
+
}
|
|
225
252
|
return keysPaged;
|
|
226
253
|
}
|
|
227
254
|
constructor(api, at, db){
|
|
@@ -245,6 +272,10 @@ class RemoteStorageLayer {
|
|
|
245
272
|
writable: true,
|
|
246
273
|
value: new _keycache.default(_index.CHILD_PREFIX_LENGTH)
|
|
247
274
|
});
|
|
275
|
+
_class_private_field_init(this, _inflight, {
|
|
276
|
+
writable: true,
|
|
277
|
+
value: new Map()
|
|
278
|
+
});
|
|
248
279
|
_class_private_field_set(this, _api, api);
|
|
249
280
|
_class_private_field_set(this, _at, at);
|
|
250
281
|
_class_private_field_set(this, _db, db);
|
|
@@ -361,6 +392,7 @@ class StorageLayer {
|
|
|
361
392
|
return knownBest;
|
|
362
393
|
}
|
|
363
394
|
async getKeysPaged(prefix, pageSize, startKey) {
|
|
395
|
+
if (pageSize > BATCH_SIZE) throw new Error(`pageSize must be less or equal to ${BATCH_SIZE}`);
|
|
364
396
|
if (!startKey || startKey === '0x') {
|
|
365
397
|
startKey = prefix;
|
|
366
398
|
}
|
package/dist/cjs/database.d.ts
CHANGED
|
@@ -24,4 +24,8 @@ export declare class Database {
|
|
|
24
24
|
saveStorage: (blockHash: HexString, key: HexString, value: HexString | null) => Promise<void>;
|
|
25
25
|
saveStorageBatch?: (entries: KeyValueEntry[]) => Promise<void>;
|
|
26
26
|
queryStorage: (blockHash: HexString, key: HexString) => Promise<KeyValueEntry | null>;
|
|
27
|
+
queryPagedKeys?: (blockHash: HexString, prefix: HexString) => Promise<HexString[] | null>;
|
|
28
|
+
savePagedKeys?: (blockHash: HexString, prefix: HexString, keys: HexString[]) => Promise<void>;
|
|
29
|
+
queryRpcCall?: (scope: string, method: string, params: string) => Promise<string | null>;
|
|
30
|
+
saveRpcCall?: (scope: string, method: string, params: string, result: string) => Promise<void>;
|
|
27
31
|
}
|
package/dist/cjs/setup.js
CHANGED
|
@@ -38,6 +38,10 @@ const processOptions = async (options)=>{
|
|
|
38
38
|
const api = new _api.Api(provider);
|
|
39
39
|
// setup api hooks
|
|
40
40
|
api.onFetching(options.hooks?.apiFetching);
|
|
41
|
+
if (options.db) {
|
|
42
|
+
const scope = typeof options.endpoint === 'string' ? options.endpoint : JSON.stringify(options.endpoint ?? '');
|
|
43
|
+
api.setDb(options.db, scope);
|
|
44
|
+
}
|
|
41
45
|
await api.isReady;
|
|
42
46
|
let blockHash;
|
|
43
47
|
if (options.block == null) {
|
package/dist/esm/api.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ProviderInterface, ProviderInterfaceCallback } from '@polkadot/rpc-provider/types';
|
|
2
2
|
import type { ExtDef } from '@polkadot/types/extrinsic/signedExtensions/types';
|
|
3
3
|
import type { HexString } from '@polkadot/util/types';
|
|
4
|
+
import type { Database } from './database.js';
|
|
4
5
|
import type { ChainProperties, Header, SignedBlock } from './index.js';
|
|
5
6
|
/**
|
|
6
7
|
* API class. Calls provider to get on-chain data.
|
|
@@ -18,6 +19,7 @@ export declare class Api {
|
|
|
18
19
|
#private;
|
|
19
20
|
readonly signedExtensions: ExtDef;
|
|
20
21
|
constructor(provider: ProviderInterface, signedExtensions?: ExtDef);
|
|
22
|
+
setDb(db: Database, scope: string): void;
|
|
21
23
|
disconnect(): Promise<void>;
|
|
22
24
|
get isReady(): Promise<void> | undefined;
|
|
23
25
|
get chain(): Promise<string>;
|
package/dist/esm/api.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import RpcError from '@polkadot/rpc-provider/coder/error';
|
|
2
2
|
import _ from 'lodash';
|
|
3
3
|
import { prefixedChildKey, splitChildKey, stripChildPrefix } from './utils/index.js';
|
|
4
|
+
const CACHEABLE_METHODS = new Set([
|
|
5
|
+
'system_chain',
|
|
6
|
+
'system_properties',
|
|
7
|
+
'system_name',
|
|
8
|
+
'chain_getBlockHash',
|
|
9
|
+
'chain_getHeader'
|
|
10
|
+
]);
|
|
4
11
|
/**
|
|
5
12
|
* API class. Calls provider to get on-chain data.
|
|
6
13
|
* Either `endpoint` or `genesis` porvider must be provided.
|
|
@@ -17,12 +24,31 @@ import { prefixedChildKey, splitChildKey, stripChildPrefix } from './utils/index
|
|
|
17
24
|
#ready;
|
|
18
25
|
#chain;
|
|
19
26
|
#chainProperties;
|
|
27
|
+
#db;
|
|
28
|
+
#scope;
|
|
20
29
|
signedExtensions;
|
|
21
30
|
#apiHooks = {};
|
|
22
31
|
constructor(provider, signedExtensions){
|
|
23
32
|
this.#provider = provider;
|
|
24
33
|
this.signedExtensions = signedExtensions || {};
|
|
25
34
|
}
|
|
35
|
+
setDb(db, scope) {
|
|
36
|
+
this.#db = db;
|
|
37
|
+
this.#scope = scope;
|
|
38
|
+
}
|
|
39
|
+
async #cachedSend(method, params, isCacheable) {
|
|
40
|
+
if (this.#db?.queryRpcCall && this.#scope && CACHEABLE_METHODS.has(method)) {
|
|
41
|
+
const key = JSON.stringify(params);
|
|
42
|
+
const cached = await this.#db.queryRpcCall(this.#scope, method, key);
|
|
43
|
+
if (cached !== null) return JSON.parse(cached);
|
|
44
|
+
this.#apiHooks?.fetching?.();
|
|
45
|
+
const result = await this.#provider.send(method, params, isCacheable);
|
|
46
|
+
await this.#db.saveRpcCall?.(this.#scope, method, key, JSON.stringify(result));
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
this.#apiHooks?.fetching?.();
|
|
50
|
+
return this.#provider.send(method, params, isCacheable);
|
|
51
|
+
}
|
|
26
52
|
async disconnect() {
|
|
27
53
|
return this.#provider.disconnect();
|
|
28
54
|
}
|
|
@@ -64,21 +90,21 @@ import { prefixedChildKey, splitChildKey, stripChildPrefix } from './utils/index
|
|
|
64
90
|
return this.#provider.send(method, params, isCacheable);
|
|
65
91
|
}
|
|
66
92
|
async getSystemName() {
|
|
67
|
-
return this
|
|
93
|
+
return this.#cachedSend('system_name', []);
|
|
68
94
|
}
|
|
69
95
|
async getSystemProperties() {
|
|
70
|
-
return this
|
|
96
|
+
return this.#cachedSend('system_properties', []);
|
|
71
97
|
}
|
|
72
98
|
async getSystemChain() {
|
|
73
|
-
return this
|
|
99
|
+
return this.#cachedSend('system_chain', []);
|
|
74
100
|
}
|
|
75
101
|
async getBlockHash(blockNumber) {
|
|
76
|
-
return this
|
|
102
|
+
return this.#cachedSend('chain_getBlockHash', Number.isInteger(blockNumber) ? [
|
|
77
103
|
blockNumber
|
|
78
104
|
] : [], !!blockNumber);
|
|
79
105
|
}
|
|
80
106
|
async getHeader(hash) {
|
|
81
|
-
return this
|
|
107
|
+
return this.#cachedSend('chain_getHeader', hash ? [
|
|
82
108
|
hash
|
|
83
109
|
] : [], !!hash);
|
|
84
110
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Header, TransactionValidityError } from '@polkadot/types/interfaces';
|
|
1
|
+
import type { ApplyExtrinsicResult, Header, TransactionValidityError } from '@polkadot/types/interfaces';
|
|
2
2
|
import type { HexString } from '@polkadot/util/types';
|
|
3
3
|
import type { TaskCallResponse } from '../wasm-executor/index.js';
|
|
4
4
|
import { Block } from './block.js';
|
|
@@ -10,6 +10,21 @@ export type BuildBlockCallbacks = {
|
|
|
10
10
|
onPhaseApplied?: (phase: 'initialize' | 'finalize' | number, resp: TaskCallResponse) => void;
|
|
11
11
|
};
|
|
12
12
|
export declare const buildBlock: (head: Block, inherentProviders: InherentProvider[], params: BuildBlockParams, callbacks?: BuildBlockCallbacks) => Promise<[Block, HexString[]]>;
|
|
13
|
+
export type DryRunResult = {
|
|
14
|
+
outcome: ApplyExtrinsicResult;
|
|
15
|
+
storageDiff: [HexString, HexString | null][];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Dry-run a batch of extrinsics against the same initialized block.
|
|
19
|
+
*
|
|
20
|
+
* Core_initialize_block and the inherents are executed once. Each extrinsic
|
|
21
|
+
* is then applied on a temporary storage layer that is pushed before the call
|
|
22
|
+
* and popped after, so no extrinsic side-effects leak into the next.
|
|
23
|
+
*
|
|
24
|
+
* Caveat: because account nonces revert with each pop, all extrinsics must be
|
|
25
|
+
* pre-signed with the same base nonce.
|
|
26
|
+
*/
|
|
27
|
+
export declare const dryRunExtrinsicsAmortized: (head: Block, inherentProviders: InherentProvider[], extrinsics: HexString[], params: BuildBlockParams) => Promise<DryRunResult[]>;
|
|
13
28
|
export declare const dryRunExtrinsic: (head: Block, inherentProviders: InherentProvider[], extrinsic: HexString | {
|
|
14
29
|
call: HexString;
|
|
15
30
|
address: string;
|
|
@@ -354,6 +354,34 @@ export const buildBlock = async (head, inherentProviders, params, callbacks)=>{
|
|
|
354
354
|
pendingExtrinsics
|
|
355
355
|
];
|
|
356
356
|
};
|
|
357
|
+
/**
|
|
358
|
+
* Dry-run a batch of extrinsics against the same initialized block.
|
|
359
|
+
*
|
|
360
|
+
* Core_initialize_block and the inherents are executed once. Each extrinsic
|
|
361
|
+
* is then applied on a temporary storage layer that is pushed before the call
|
|
362
|
+
* and popped after, so no extrinsic side-effects leak into the next.
|
|
363
|
+
*
|
|
364
|
+
* Caveat: because account nonces revert with each pop, all extrinsics must be
|
|
365
|
+
* pre-signed with the same base nonce.
|
|
366
|
+
*/ export const dryRunExtrinsicsAmortized = async (head, inherentProviders, extrinsics, params)=>{
|
|
367
|
+
const registry = await head.registry;
|
|
368
|
+
const header = await newHeader(head);
|
|
369
|
+
const { block: newBlock } = await initNewBlock(head, header, inherentProviders, params);
|
|
370
|
+
const results = [];
|
|
371
|
+
for (const extrinsic of extrinsics){
|
|
372
|
+
newBlock.pushStorageLayer();
|
|
373
|
+
const resp = await newBlock.call('BlockBuilder_apply_extrinsic', [
|
|
374
|
+
extrinsic
|
|
375
|
+
]);
|
|
376
|
+
const outcome = registry.createType('ApplyExtrinsicResult', resp.result);
|
|
377
|
+
results.push({
|
|
378
|
+
outcome,
|
|
379
|
+
storageDiff: resp.storageDiff
|
|
380
|
+
});
|
|
381
|
+
newBlock.popStorageLayer();
|
|
382
|
+
}
|
|
383
|
+
return results;
|
|
384
|
+
};
|
|
357
385
|
export const dryRunExtrinsic = async (head, inherentProviders, extrinsic, params)=>{
|
|
358
386
|
const registry = await head.registry;
|
|
359
387
|
const header = await newHeader(head);
|
|
@@ -69,6 +69,16 @@ export declare class Block {
|
|
|
69
69
|
* Pop a layer from the storage stack.
|
|
70
70
|
*/
|
|
71
71
|
popStorageLayer(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Truncate the storage layer stack back to a target depth.
|
|
74
|
+
*
|
|
75
|
+
* Used by snapshot-restore mechanisms to undo accumulated dev.setStorage
|
|
76
|
+
* calls when reverting to a previously captured state, without losing the
|
|
77
|
+
* underlying block.
|
|
78
|
+
*/
|
|
79
|
+
resetStorageLayers(targetCount: number): void;
|
|
80
|
+
/** The current depth of the storage layer stack. */
|
|
81
|
+
get storageLayerCount(): number;
|
|
72
82
|
/**
|
|
73
83
|
* Get storage diff.
|
|
74
84
|
*/
|
|
@@ -160,6 +160,18 @@ import { RemoteStorageLayer, StorageLayer, StorageValueKind } from './storage-la
|
|
|
160
160
|
this.#storages.pop();
|
|
161
161
|
}
|
|
162
162
|
/**
|
|
163
|
+
* Truncate the storage layer stack back to a target depth.
|
|
164
|
+
*
|
|
165
|
+
* Used by snapshot-restore mechanisms to undo accumulated dev.setStorage
|
|
166
|
+
* calls when reverting to a previously captured state, without losing the
|
|
167
|
+
* underlying block.
|
|
168
|
+
*/ resetStorageLayers(targetCount) {
|
|
169
|
+
while(this.#storages.length > targetCount)this.#storages.pop();
|
|
170
|
+
}
|
|
171
|
+
/** The current depth of the storage layer stack. */ get storageLayerCount() {
|
|
172
|
+
return this.#storages.length;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
163
175
|
* Get storage diff.
|
|
164
176
|
*/ async storageDiff() {
|
|
165
177
|
const storage = {};
|
|
@@ -43,19 +43,39 @@ export class HeadState {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
const diff = await this.#head.storageDiff();
|
|
46
|
+
const newValues = {
|
|
47
|
+
...diff
|
|
48
|
+
};
|
|
49
|
+
const watchedKeys = new Set();
|
|
50
|
+
for (const [keys] of Object.values(this.#storageListeners)){
|
|
51
|
+
for (const key of keys)watchedKeys.add(key);
|
|
52
|
+
}
|
|
53
|
+
for (const key of watchedKeys){
|
|
54
|
+
if (newValues[key] === undefined && this.#oldValues[key] !== undefined) {
|
|
55
|
+
newValues[key] = await head.get(key) ?? null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
46
58
|
for (const [keys, cb] of Object.values(this.#storageListeners)){
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
])
|
|
51
|
-
|
|
59
|
+
const changes = [];
|
|
60
|
+
for (const key of keys){
|
|
61
|
+
if (newValues[key] === undefined) continue;
|
|
62
|
+
if (newValues[key] !== this.#oldValues[key]) {
|
|
63
|
+
changes.push([
|
|
64
|
+
key,
|
|
65
|
+
newValues[key]
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (changes.length > 0) {
|
|
52
70
|
try {
|
|
53
|
-
await cb(head,
|
|
71
|
+
await cb(head, changes);
|
|
54
72
|
} catch (error) {
|
|
55
73
|
logger.error(error, 'setHead storage diff callback error');
|
|
56
74
|
}
|
|
57
75
|
}
|
|
58
76
|
}
|
|
59
|
-
Object.
|
|
77
|
+
for (const [key, value] of Object.entries(newValues)){
|
|
78
|
+
this.#oldValues[key] = value;
|
|
79
|
+
}
|
|
60
80
|
}
|
|
61
81
|
}
|
|
@@ -151,6 +151,15 @@ export declare class Blockchain {
|
|
|
151
151
|
outcome: ApplyExtrinsicResult;
|
|
152
152
|
storageDiff: [HexString, HexString | null][];
|
|
153
153
|
}>;
|
|
154
|
+
/**
|
|
155
|
+
* Dry-run a batch of extrinsics in block `at`, amortizing block initialization
|
|
156
|
+
* across the whole batch. Each extrinsic is applied independently — its storage
|
|
157
|
+
* changes do not accumulate into subsequent extrinsics.
|
|
158
|
+
*/
|
|
159
|
+
dryRunExtrinsicsAmortized(extrinsics: HexString[], at?: HexString): Promise<{
|
|
160
|
+
outcome: ApplyExtrinsicResult;
|
|
161
|
+
storageDiff: [HexString, HexString | null][];
|
|
162
|
+
}[]>;
|
|
154
163
|
/**
|
|
155
164
|
* Dry run hrmp messages in block `at`.
|
|
156
165
|
* Return the storage diff.
|
|
@@ -7,7 +7,7 @@ import { defaultLogger } from '../logger.js';
|
|
|
7
7
|
import { OffchainWorker } from '../offchain.js';
|
|
8
8
|
import { compactHex } from '../utils/index.js';
|
|
9
9
|
import { Block } from './block.js';
|
|
10
|
-
import { dryRunExtrinsic, dryRunInherents } from './block-builder.js';
|
|
10
|
+
import { dryRunExtrinsic, dryRunExtrinsicsAmortized, dryRunInherents } from './block-builder.js';
|
|
11
11
|
import { HeadState } from './head-state.js';
|
|
12
12
|
import { TxPool } from './txpool.js';
|
|
13
13
|
const logger = defaultLogger.child({
|
|
@@ -330,6 +330,24 @@ const logger = defaultLogger.child({
|
|
|
330
330
|
};
|
|
331
331
|
}
|
|
332
332
|
/**
|
|
333
|
+
* Dry-run a batch of extrinsics in block `at`, amortizing block initialization
|
|
334
|
+
* across the whole batch. Each extrinsic is applied independently — its storage
|
|
335
|
+
* changes do not accumulate into subsequent extrinsics.
|
|
336
|
+
*/ async dryRunExtrinsicsAmortized(extrinsics, at) {
|
|
337
|
+
await this.api.isReady;
|
|
338
|
+
const head = at ? await this.getBlock(at) : this.head;
|
|
339
|
+
if (!head) {
|
|
340
|
+
throw new Error(`Cannot find block ${at}`);
|
|
341
|
+
}
|
|
342
|
+
const params = {
|
|
343
|
+
transactions: [],
|
|
344
|
+
downwardMessages: [],
|
|
345
|
+
upwardMessages: [],
|
|
346
|
+
horizontalMessages: {}
|
|
347
|
+
};
|
|
348
|
+
return dryRunExtrinsicsAmortized(head, this.#inherentProviders, extrinsics, params);
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
333
351
|
* Dry run hrmp messages in block `at`.
|
|
334
352
|
* Return the storage diff.
|
|
335
353
|
*/ async dryRunHrmp(hrmp, at) {
|
|
@@ -17,6 +17,7 @@ export class RemoteStorageLayer {
|
|
|
17
17
|
#db;
|
|
18
18
|
#keyCache = new KeyCache(PREFIX_LENGTH);
|
|
19
19
|
#defaultChildKeyCache = new KeyCache(CHILD_PREFIX_LENGTH);
|
|
20
|
+
#inflight = new Map();
|
|
20
21
|
constructor(api, at, db){
|
|
21
22
|
this.#api = api;
|
|
22
23
|
this.#at = at;
|
|
@@ -32,13 +33,20 @@ export class RemoteStorageLayer {
|
|
|
32
33
|
return res.value ?? undefined;
|
|
33
34
|
}
|
|
34
35
|
}
|
|
36
|
+
const inflight = this.#inflight.get(key);
|
|
37
|
+
if (inflight) return inflight;
|
|
35
38
|
logger.trace({
|
|
36
39
|
at: this.#at,
|
|
37
40
|
key
|
|
38
41
|
}, 'RemoteStorageLayer get');
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
const fetch = this.#api.getStorage(key, this.#at).then((data)=>{
|
|
43
|
+
this.#db?.saveStorage(this.#at, key, data);
|
|
44
|
+
return data ?? undefined;
|
|
45
|
+
}).finally(()=>{
|
|
46
|
+
this.#inflight.delete(key);
|
|
47
|
+
});
|
|
48
|
+
this.#inflight.set(key, fetch);
|
|
49
|
+
return fetch;
|
|
42
50
|
}
|
|
43
51
|
async getMany(keys, _cache) {
|
|
44
52
|
const result = [];
|
|
@@ -98,12 +106,33 @@ export class RemoteStorageLayer {
|
|
|
98
106
|
}, 'RemoteStorageLayer getKeysPaged');
|
|
99
107
|
const isChild = isPrefixedChildKey(prefix);
|
|
100
108
|
const minPrefixLen = isChild ? CHILD_PREFIX_LENGTH : PREFIX_LENGTH;
|
|
101
|
-
//
|
|
102
|
-
|
|
109
|
+
// KeyCache groups by the first `minPrefixLen` chars; it cannot correctly answer queries
|
|
110
|
+
// whose prefix is longer than that grouping width, so proxy directly to upstream.
|
|
111
|
+
if (prefix.length < minPrefixLen || startKey.length < minPrefixLen || prefix.length > minPrefixLen || startKey.length > minPrefixLen) {
|
|
103
112
|
return this.#api.getKeysPaged(prefix, pageSize, startKey, this.#at);
|
|
104
113
|
}
|
|
114
|
+
const startKeyEqualsPrefix = startKey === prefix;
|
|
115
|
+
let cachedKeys;
|
|
116
|
+
if (this.#db?.queryPagedKeys && startKeyEqualsPrefix) {
|
|
117
|
+
const cached = await this.#db.queryPagedKeys(this.#at, prefix);
|
|
118
|
+
if (cached) {
|
|
119
|
+
cachedKeys = cached;
|
|
120
|
+
isChild ? this.#defaultChildKeyCache.feed([
|
|
121
|
+
startKey,
|
|
122
|
+
...cached
|
|
123
|
+
]) : this.#keyCache.feed([
|
|
124
|
+
startKey,
|
|
125
|
+
...cached
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
105
129
|
let batchComplete = false;
|
|
130
|
+
let fetchedNewKeys = false;
|
|
106
131
|
const keysPaged = [];
|
|
132
|
+
// Seed with cached keys so a cache-hit-then-extend doesn't truncate the persisted set.
|
|
133
|
+
const allFetchedKeys = cachedKeys ? [
|
|
134
|
+
...cachedKeys
|
|
135
|
+
] : [];
|
|
107
136
|
while(keysPaged.length < pageSize){
|
|
108
137
|
const nextKey = isChild ? await this.#defaultChildKeyCache.next(startKey) : await this.#keyCache.next(startKey);
|
|
109
138
|
if (nextKey) {
|
|
@@ -136,15 +165,7 @@ export class RemoteStorageLayer {
|
|
|
136
165
|
break;
|
|
137
166
|
}
|
|
138
167
|
if (this.#db) {
|
|
139
|
-
|
|
140
|
-
const newBatch = [];
|
|
141
|
-
for (const key of batch){
|
|
142
|
-
const res = await this.#db.queryStorage(this.#at, key);
|
|
143
|
-
if (res) {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
newBatch.push(key);
|
|
147
|
-
}
|
|
168
|
+
const newBatch = await Promise.all(batch.map((key)=>this.#db.queryStorage(this.#at, key).then((r)=>r ? null : key))).then((rs)=>rs.filter((k)=>k !== null));
|
|
148
169
|
if (newBatch.length > 0) {
|
|
149
170
|
// batch fetch storage values and save to db, they may be used later
|
|
150
171
|
this.#api.getStorageBatch(prefix, newBatch, this.#at).then((storage)=>{
|
|
@@ -153,8 +174,15 @@ export class RemoteStorageLayer {
|
|
|
153
174
|
}
|
|
154
175
|
});
|
|
155
176
|
}
|
|
177
|
+
if (startKeyEqualsPrefix) {
|
|
178
|
+
allFetchedKeys.push(...batch);
|
|
179
|
+
fetchedNewKeys = true;
|
|
180
|
+
}
|
|
156
181
|
}
|
|
157
182
|
}
|
|
183
|
+
if (this.#db?.savePagedKeys && startKeyEqualsPrefix && fetchedNewKeys) {
|
|
184
|
+
await this.#db.savePagedKeys(this.#at, prefix, allFetchedKeys);
|
|
185
|
+
}
|
|
158
186
|
return keysPaged;
|
|
159
187
|
}
|
|
160
188
|
}
|
|
@@ -290,6 +318,7 @@ export class StorageLayer {
|
|
|
290
318
|
return knownBest;
|
|
291
319
|
}
|
|
292
320
|
async getKeysPaged(prefix, pageSize, startKey) {
|
|
321
|
+
if (pageSize > BATCH_SIZE) throw new Error(`pageSize must be less or equal to ${BATCH_SIZE}`);
|
|
293
322
|
if (!startKey || startKey === '0x') {
|
|
294
323
|
startKey = prefix;
|
|
295
324
|
}
|
package/dist/esm/database.d.ts
CHANGED
|
@@ -24,4 +24,8 @@ export declare class Database {
|
|
|
24
24
|
saveStorage: (blockHash: HexString, key: HexString, value: HexString | null) => Promise<void>;
|
|
25
25
|
saveStorageBatch?: (entries: KeyValueEntry[]) => Promise<void>;
|
|
26
26
|
queryStorage: (blockHash: HexString, key: HexString) => Promise<KeyValueEntry | null>;
|
|
27
|
+
queryPagedKeys?: (blockHash: HexString, prefix: HexString) => Promise<HexString[] | null>;
|
|
28
|
+
savePagedKeys?: (blockHash: HexString, prefix: HexString, keys: HexString[]) => Promise<void>;
|
|
29
|
+
queryRpcCall?: (scope: string, method: string, params: string) => Promise<string | null>;
|
|
30
|
+
saveRpcCall?: (scope: string, method: string, params: string, result: string) => Promise<void>;
|
|
27
31
|
}
|
package/dist/esm/setup.js
CHANGED
|
@@ -20,6 +20,10 @@ export const processOptions = async (options)=>{
|
|
|
20
20
|
const api = new Api(provider);
|
|
21
21
|
// setup api hooks
|
|
22
22
|
api.onFetching(options.hooks?.apiFetching);
|
|
23
|
+
if (options.db) {
|
|
24
|
+
const scope = typeof options.endpoint === 'string' ? options.endpoint : JSON.stringify(options.endpoint ?? '');
|
|
25
|
+
api.setDb(options.db, scope);
|
|
26
|
+
}
|
|
23
27
|
await api.isReady;
|
|
24
28
|
let blockHash;
|
|
25
29
|
if (options.block == null) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acala-network/chopsticks-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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.
|
|
17
|
+
"@acala-network/chopsticks-executor": "1.4.0",
|
|
18
18
|
"@polkadot/rpc-provider": "^16.4.1",
|
|
19
19
|
"@polkadot/types": "^16.4.1",
|
|
20
20
|
"@polkadot/types-codec": "^16.4.1",
|