@acala-network/chopsticks-core 1.2.2 → 1.2.4

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) {
@@ -1,10 +1,9 @@
1
- import type { DigestItem, Header, TransactionValidityError } from '@polkadot/types/interfaces';
1
+ import type { 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';
5
5
  import type { InherentProvider } from './inherent/index.js';
6
6
  import type { BuildBlockParams } from './txpool.js';
7
- export declare const genesisDigestLogs: (head: Block) => Promise<DigestItem[]>;
8
7
  export declare const newHeader: (head: Block, unsafeBlockHeight?: number) => Promise<Header>;
9
8
  export type BuildBlockCallbacks = {
10
9
  onApplyExtrinsicError?: (extrinsic: HexString, error: TransactionValidityError) => void;
@@ -18,9 +18,6 @@ _export(exports, {
18
18
  get dryRunInherents () {
19
19
  return dryRunInherents;
20
20
  },
21
- get genesisDigestLogs () {
22
- return genesisDigestLogs;
23
- },
24
21
  get newHeader () {
25
22
  return newHeader;
26
23
  }
@@ -70,15 +67,6 @@ const genesisDigestLogs = async (head)=>{
70
67
  digest
71
68
  ];
72
69
  };
73
- const getConsensus = (header)=>{
74
- if (header.digest.logs.length === 0) return;
75
- const [consensusEngine, preDigest] = header.digest.logs[0].asPreRuntime;
76
- return {
77
- consensusEngine,
78
- preDigest,
79
- rest: header.digest.logs.slice(1)
80
- };
81
- };
82
70
  const babePreDigestSetSlot = (digest, slotNumber)=>{
83
71
  if (digest.isPrimary) {
84
72
  return {
@@ -110,50 +98,46 @@ const newHeader = async (head, unsafeBlockHeight)=>{
110
98
  const meta = await head.meta;
111
99
  const parentHeader = await head.header;
112
100
  let newLogs = !head.number ? await genesisDigestLogs(head) : parentHeader.digest.logs.toArray();
113
- const consensus = getConsensus(parentHeader);
114
- if (consensus?.consensusEngine.isAura) {
115
- const slot = await (0, _index.getCurrentSlot)(head);
116
- const newSlot = (0, _util.compactAddLength)(meta.registry.createType('Slot', slot + 1).toU8a());
117
- newLogs = [
118
- meta.registry.createType('DigestItem', {
119
- PreRuntime: [
120
- consensus.consensusEngine,
121
- newSlot
122
- ]
123
- }),
124
- ...consensus.rest
125
- ];
126
- } else if (consensus?.consensusEngine.isBabe) {
127
- const slot = await (0, _index.getCurrentSlot)(head);
128
- const digest = meta.registry.createType('RawBabePreDigest', consensus.preDigest);
129
- const newSlot = (0, _util.compactAddLength)(meta.registry.createType('RawBabePreDigest', babePreDigestSetSlot(digest, slot + 1)).toU8a());
130
- newLogs = [
131
- meta.registry.createType('DigestItem', {
132
- PreRuntime: [
133
- consensus.consensusEngine,
134
- newSlot
135
- ]
136
- }),
137
- ...consensus.rest
138
- ];
139
- } else if (consensus?.consensusEngine?.toString() === 'nmbs') {
140
- const nmbsKey = (0, _util.stringToHex)('nmbs');
141
- newLogs = [
142
- meta.registry.createType('DigestItem', {
143
- // Using previous block author
144
- PreRuntime: [
145
- consensus.consensusEngine,
146
- parentHeader.digest.logs.find((log)=>log.isPreRuntime && log.asPreRuntime[0].toHex() === nmbsKey)?.asPreRuntime[1].toHex()
147
- ]
148
- }),
149
- ...consensus.rest
150
- ];
151
- if (meta.query.randomness?.notFirstBlock) {
152
- // TODO: shouldn't modify existing head
153
- // reset notFirstBlock so randomness will skip validation
154
- head.pushStorageLayer().set((0, _index.compactHex)(meta.query.randomness.notFirstBlock()), _storagelayer.StorageValueKind.Deleted);
101
+ newLogs = await Promise.all(newLogs.map(async (item)=>{
102
+ if (item.isPreRuntime) {
103
+ const [consensusEngine, preDigest] = item.asPreRuntime;
104
+ if (consensusEngine.isAura) {
105
+ const slot = await (0, _index.getCurrentSlot)(head);
106
+ const newSlot = (0, _util.compactAddLength)(meta.registry.createType('Slot', slot + 1).toU8a());
107
+ return meta.registry.createType('DigestItem', {
108
+ PreRuntime: [
109
+ consensusEngine,
110
+ newSlot
111
+ ]
112
+ });
113
+ } else if (consensusEngine.isBabe) {
114
+ const slot = await (0, _index.getCurrentSlot)(head);
115
+ const digest = meta.registry.createType('RawBabePreDigest', preDigest);
116
+ const newSlot = (0, _util.compactAddLength)(meta.registry.createType('RawBabePreDigest', babePreDigestSetSlot(digest, slot + 1)).toU8a());
117
+ return meta.registry.createType('DigestItem', {
118
+ PreRuntime: [
119
+ consensusEngine,
120
+ newSlot
121
+ ]
122
+ });
123
+ } else if (consensusEngine?.toString() === 'nmbs') {
124
+ const nmbsKey = (0, _util.stringToHex)('nmbs');
125
+ if (meta.query.randomness?.notFirstBlock) {
126
+ // TODO: shouldn't modify existing head
127
+ // reset notFirstBlock so randomness will skip validation
128
+ head.pushStorageLayer().set((0, _index.compactHex)(meta.query.randomness.notFirstBlock()), _storagelayer.StorageValueKind.Deleted);
129
+ }
130
+ return meta.registry.createType('DigestItem', {
131
+ // Using previous block author
132
+ PreRuntime: [
133
+ consensusEngine,
134
+ parentHeader.digest.logs.find((log)=>log.isPreRuntime && log.asPreRuntime[0].toHex() === nmbsKey)?.asPreRuntime[1].toHex()
135
+ ]
136
+ });
137
+ }
155
138
  }
156
- }
139
+ return item;
140
+ }));
157
141
  const header = meta.registry.createType('Header', {
158
142
  parentHash: head.hash,
159
143
  number: unsafeBlockHeight ?? head.number + 1,
@@ -202,7 +186,10 @@ const initNewBlock = async (head, header, inherentProviders, params, storageLaye
202
186
  if (extrinsics.length === 0) {
203
187
  continue;
204
188
  }
205
- const resp = await newBlock.call('BlockBuilder_apply_extrinsic', extrinsics);
189
+ // bypass signature check during inherent extrinsics
190
+ // this is needed to allow cumulus to accept fake relay block digests
191
+ // this should be safe because there are no valid use of invalid signatures in inherents
192
+ const resp = await newBlock.call('BlockBuilder_apply_extrinsic', extrinsics, true);
206
193
  const layer = newBlock.pushStorageLayer();
207
194
  layer.setAll(resp.storageDiff);
208
195
  layers.push(layer);
@@ -92,5 +92,5 @@ export declare class Block {
92
92
  /**
93
93
  * Call a runtime method.
94
94
  */
95
- call(method: string, args: HexString[]): Promise<TaskCallResponse>;
95
+ call(method: string, args: HexString[], mockSigantureHostOverride?: boolean): Promise<TaskCallResponse>;
96
96
  }
@@ -242,7 +242,7 @@ class Block {
242
242
  }
243
243
  /**
244
244
  * Call a runtime method.
245
- */ async call(method, args) {
245
+ */ async call(method, args, mockSigantureHostOverride = false) {
246
246
  const wasm = await this.wasm;
247
247
  const response = await (0, _index1.runTask)({
248
248
  wasm,
@@ -255,7 +255,7 @@ class Block {
255
255
  mockSignatureHost: _class_private_field_get(this, _chain).mockSignatureHost,
256
256
  allowUnresolvedImports: _class_private_field_get(this, _chain).allowUnresolvedImports,
257
257
  runtimeLogLevel: _class_private_field_get(this, _chain).runtimeLogLevel
258
- }, (0, _index1.taskHandler)(this));
258
+ }, (0, _index1.taskHandler)(this), mockSigantureHostOverride);
259
259
  if ('Call' in response) {
260
260
  if (this.chain.offchainWorker) {
261
261
  // apply offchain storage
@@ -13,6 +13,7 @@ export type ValidationData = {
13
13
  relayChainState: {
14
14
  trieNodes: HexString[];
15
15
  };
16
+ relayParentDescendants?: any[];
16
17
  };
17
18
  export declare class SetValidationData implements InherentProvider {
18
19
  createInherents(newBlock: Block, params: BuildBlockParams): Promise<HexString[]>;
@@ -24,6 +24,7 @@ function _interop_require_default(obj) {
24
24
  const logger = _logger.defaultLogger.child({
25
25
  name: 'parachain-validation-data'
26
26
  });
27
+ const RELAY_CHAIN_SLOT_DURATION_MILLIS = 6_000;
27
28
  const MOCK_VALIDATION_DATA = {
28
29
  validationData: {
29
30
  relayParentNumber: 1000,
@@ -116,17 +117,14 @@ class SetValidationData {
116
117
  const hrmpIngressChannelIndexKey = (0, _proof.hrmpIngressChannelIndex)(paraId);
117
118
  const hrmpEgressChannelIndexKey = (0, _proof.hrmpEgressChannelIndex)(paraId);
118
119
  const decoded = await (0, _index1.decodeProof)(extrinsic.validationData.relayParentStorageRoot, extrinsic.relayChainState.trieNodes);
119
- const relaySlotIncrease = Math.max(1, meta.consts.timestamp?.minimumPeriod // legacy
120
- ?.divn(3000) // relaychain min period
121
- ?.toNumber() || meta.consts.aura?.slotDuration // async backing
122
- ?.divn(6000) // relaychain block time
123
- ?.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
+ ;
124
123
  for (const key of Object.values(_proof.WELL_KNOWN_KEYS)){
125
124
  if (key === _proof.WELL_KNOWN_KEYS.CURRENT_SLOT) {
126
125
  // increment current slot
127
126
  const relayCurrentSlot = decoded[key] ? meta.registry.createType('Slot', (0, _util.hexToU8a)(decoded[key])).toNumber() : await (0, _index.getCurrentSlot)(parent) * relaySlotIncrease;
128
- const newSlot = meta.registry.createType('Slot', relayCurrentSlot + relaySlotIncrease + 1) // +1 to be safe
129
- ;
127
+ const newSlot = meta.registry.createType('Slot', relayCurrentSlot + relaySlotIncrease);
130
128
  logger.debug({
131
129
  relayCurrentSlot,
132
130
  newSlot: newSlot.toNumber()
@@ -276,6 +274,7 @@ class SetValidationData {
276
274
  }
277
275
  const { trieRootHash, nodes } = await (0, _index1.createProof)(extrinsic.relayChainState.trieNodes, newEntries);
278
276
  const argsLengh = meta.tx.parachainSystem.setValidationData.meta.args.length;
277
+ const relayParentNumber = params.relayParentNumber ?? extrinsic.validationData.relayParentNumber + relaySlotIncrease;
279
278
  if (argsLengh === 1) {
280
279
  // old version
281
280
  const newData = {
@@ -285,7 +284,7 @@ class SetValidationData {
285
284
  validationData: {
286
285
  ...extrinsic.validationData,
287
286
  relayParentStorageRoot: trieRootHash,
288
- relayParentNumber: params.relayParentNumber ?? extrinsic.validationData.relayParentNumber + relaySlotIncrease
287
+ relayParentNumber
289
288
  },
290
289
  relayChainState: {
291
290
  trieNodes: nodes
@@ -297,16 +296,40 @@ class SetValidationData {
297
296
  ];
298
297
  } else if (argsLengh === 2) {
299
298
  // new version
299
+ let relayParentDescendants = extrinsic.relayParentDescendants;
300
+ if (relayParentDescendants) {
301
+ let fakeParentHeader = relayParentDescendants[0];
302
+ if (fakeParentHeader) {
303
+ fakeParentHeader = {
304
+ ...fakeParentHeader,
305
+ number: relayParentNumber,
306
+ stateRoot: trieRootHash
307
+ };
308
+ relayParentDescendants = [
309
+ fakeParentHeader,
310
+ ...relayParentDescendants.slice(1)
311
+ ];
312
+ let lastHeader;
313
+ for (const descendant of relayParentDescendants){
314
+ if (lastHeader) {
315
+ descendant.parentHash = lastHeader.hash;
316
+ descendant.number = lastHeader.number.toNumber() + 1;
317
+ }
318
+ lastHeader = meta.registry.createType('Header', descendant);
319
+ }
320
+ }
321
+ }
300
322
  const newData = {
301
323
  ...extrinsic,
302
324
  validationData: {
303
325
  ...extrinsic.validationData,
304
326
  relayParentStorageRoot: trieRootHash,
305
- relayParentNumber: params.relayParentNumber ?? extrinsic.validationData.relayParentNumber + relaySlotIncrease
327
+ relayParentNumber
306
328
  },
307
329
  relayChainState: {
308
330
  trieNodes: nodes
309
- }
331
+ },
332
+ relayParentDescendants
310
333
  };
311
334
  const horizontalMessagesArray = Object.entries(horizontalMessages).flatMap(([sender, messages])=>messages.map((msg)=>[
312
335
  sender,
@@ -106,6 +106,10 @@ const chainHead_v1_follow = async (context, [withRuntime], { subscribe })=>{
106
106
  ],
107
107
  finalizedBlockRuntime: withRuntime ? await context.chain.head.runtimeVersion : null
108
108
  });
109
+ callback({
110
+ event: 'bestBlockChanged',
111
+ bestBlockHash: context.chain.head.hash
112
+ });
109
113
  });
110
114
  return id;
111
115
  };
@@ -8,6 +8,7 @@ export declare const WELL_KNOWN_KEYS: {
8
8
  TWO_EPOCHS_AGO_RANDOMNESS: HexString;
9
9
  CURRENT_SLOT: HexString;
10
10
  ACTIVE_CONFIG: HexString;
11
+ AUTHORITIES: HexString;
11
12
  };
12
13
  export declare const dmqMqcHead: (paraId: u32) => `0x${string}`;
13
14
  export declare const upgradeGoAheadSignal: (paraId: u32) => `0x${string}`;
@@ -42,7 +42,8 @@ const WELL_KNOWN_KEYS = {
42
42
  ONE_EPOCH_AGO_RANDOMNESS: '0x1cb6f36e027abb2091cfb5110ab5087f7ce678799d3eff024253b90e84927cc6',
43
43
  TWO_EPOCHS_AGO_RANDOMNESS: '0x1cb6f36e027abb2091cfb5110ab5087f7a414cb008e0e61e46722aa60abdd672',
44
44
  CURRENT_SLOT: '0x1cb6f36e027abb2091cfb5110ab5087f06155b3cd9a8c9e5e9a23fd5dc13a5ed',
45
- ACTIVE_CONFIG: '0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385'
45
+ ACTIVE_CONFIG: '0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385',
46
+ AUTHORITIES: '0x1cb6f36e027abb2091cfb5110ab5087f5e0621c4869aa60c02be9adcc98a0d1d'
46
47
  };
47
48
  const hash = (prefix, suffix)=>{
48
49
  return (0, _util.u8aToHex)((0, _util.u8aConcat)((0, _util.hexToU8a)(prefix), (0, _utilcrypto.xxhashAsU8a)(suffix, 64), suffix));
@@ -46,7 +46,7 @@ export interface WasmExecutor {
46
46
  runTask: (task: {
47
47
  wasm: HexString;
48
48
  calls: [string, HexString[]][];
49
- mockSignatureHost: boolean;
49
+ mockSignatureHost: number;
50
50
  allowUnresolvedImports: boolean;
51
51
  runtimeLogLevel: number;
52
52
  }, callback?: JsCallback) => Promise<TaskResponse>;
@@ -65,7 +65,7 @@ export declare const createProof: (nodes: HexString[], updates: [HexString, HexS
65
65
  trieRootHash: `0x${string}`;
66
66
  nodes: `0x${string}`[];
67
67
  }>;
68
- export declare const runTask: (task: TaskCall, callback?: JsCallback) => Promise<{
68
+ export declare const runTask: (task: TaskCall, callback?: JsCallback, overrideMockSignatureHost?: boolean) => Promise<{
69
69
  Call: TaskCallResponse;
70
70
  } | {
71
71
  Error: string;
@@ -136,12 +136,13 @@ const createProof = async (nodes, updates)=>{
136
136
  };
137
137
  };
138
138
  let nextTaskId = 0;
139
- const runTask = async (task, callback = emptyTaskHandler)=>{
139
+ const runTask = async (task, callback = emptyTaskHandler, overrideMockSignatureHost = false)=>{
140
140
  const taskId = nextTaskId++;
141
141
  const task2 = {
142
142
  ...task,
143
143
  id: taskId,
144
- storageProofSize: task.storageProofSize ?? 0
144
+ storageProofSize: task.storageProofSize ?? 0,
145
+ mockSignatureHost: overrideMockSignatureHost ? 2 : task.mockSignatureHost ? 1 : 0
145
146
  };
146
147
  const worker = await getWorker();
147
148
  logger.trace((0, _logger.truncate)(task2), `runTask #${taskId}`);
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) {
@@ -1,10 +1,9 @@
1
- import type { DigestItem, Header, TransactionValidityError } from '@polkadot/types/interfaces';
1
+ import type { 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';
5
5
  import type { InherentProvider } from './inherent/index.js';
6
6
  import type { BuildBlockParams } from './txpool.js';
7
- export declare const genesisDigestLogs: (head: Block) => Promise<DigestItem[]>;
8
7
  export declare const newHeader: (head: Block, unsafeBlockHeight?: number) => Promise<Header>;
9
8
  export type BuildBlockCallbacks = {
10
9
  onApplyExtrinsicError?: (extrinsic: HexString, error: TransactionValidityError) => void;
@@ -7,7 +7,7 @@ import { StorageLayer, StorageValueKind } from './storage-layer.js';
7
7
  const logger = defaultLogger.child({
8
8
  name: 'block-builder'
9
9
  });
10
- export const genesisDigestLogs = async (head)=>{
10
+ const genesisDigestLogs = async (head)=>{
11
11
  const meta = await head.meta;
12
12
  const currentSlot = await getCurrentSlot(head);
13
13
  if (meta.consts.babe) {
@@ -43,15 +43,6 @@ export const genesisDigestLogs = async (head)=>{
43
43
  digest
44
44
  ];
45
45
  };
46
- const getConsensus = (header)=>{
47
- if (header.digest.logs.length === 0) return;
48
- const [consensusEngine, preDigest] = header.digest.logs[0].asPreRuntime;
49
- return {
50
- consensusEngine,
51
- preDigest,
52
- rest: header.digest.logs.slice(1)
53
- };
54
- };
55
46
  const babePreDigestSetSlot = (digest, slotNumber)=>{
56
47
  if (digest.isPrimary) {
57
48
  return {
@@ -83,50 +74,46 @@ export const newHeader = async (head, unsafeBlockHeight)=>{
83
74
  const meta = await head.meta;
84
75
  const parentHeader = await head.header;
85
76
  let newLogs = !head.number ? await genesisDigestLogs(head) : parentHeader.digest.logs.toArray();
86
- const consensus = getConsensus(parentHeader);
87
- if (consensus?.consensusEngine.isAura) {
88
- const slot = await getCurrentSlot(head);
89
- const newSlot = compactAddLength(meta.registry.createType('Slot', slot + 1).toU8a());
90
- newLogs = [
91
- meta.registry.createType('DigestItem', {
92
- PreRuntime: [
93
- consensus.consensusEngine,
94
- newSlot
95
- ]
96
- }),
97
- ...consensus.rest
98
- ];
99
- } else if (consensus?.consensusEngine.isBabe) {
100
- const slot = await getCurrentSlot(head);
101
- const digest = meta.registry.createType('RawBabePreDigest', consensus.preDigest);
102
- const newSlot = compactAddLength(meta.registry.createType('RawBabePreDigest', babePreDigestSetSlot(digest, slot + 1)).toU8a());
103
- newLogs = [
104
- meta.registry.createType('DigestItem', {
105
- PreRuntime: [
106
- consensus.consensusEngine,
107
- newSlot
108
- ]
109
- }),
110
- ...consensus.rest
111
- ];
112
- } else if (consensus?.consensusEngine?.toString() === 'nmbs') {
113
- const nmbsKey = stringToHex('nmbs');
114
- newLogs = [
115
- meta.registry.createType('DigestItem', {
116
- // Using previous block author
117
- PreRuntime: [
118
- consensus.consensusEngine,
119
- parentHeader.digest.logs.find((log)=>log.isPreRuntime && log.asPreRuntime[0].toHex() === nmbsKey)?.asPreRuntime[1].toHex()
120
- ]
121
- }),
122
- ...consensus.rest
123
- ];
124
- if (meta.query.randomness?.notFirstBlock) {
125
- // TODO: shouldn't modify existing head
126
- // reset notFirstBlock so randomness will skip validation
127
- head.pushStorageLayer().set(compactHex(meta.query.randomness.notFirstBlock()), StorageValueKind.Deleted);
77
+ newLogs = await Promise.all(newLogs.map(async (item)=>{
78
+ if (item.isPreRuntime) {
79
+ const [consensusEngine, preDigest] = item.asPreRuntime;
80
+ if (consensusEngine.isAura) {
81
+ const slot = await getCurrentSlot(head);
82
+ const newSlot = compactAddLength(meta.registry.createType('Slot', slot + 1).toU8a());
83
+ return meta.registry.createType('DigestItem', {
84
+ PreRuntime: [
85
+ consensusEngine,
86
+ newSlot
87
+ ]
88
+ });
89
+ } else if (consensusEngine.isBabe) {
90
+ const slot = await getCurrentSlot(head);
91
+ const digest = meta.registry.createType('RawBabePreDigest', preDigest);
92
+ const newSlot = compactAddLength(meta.registry.createType('RawBabePreDigest', babePreDigestSetSlot(digest, slot + 1)).toU8a());
93
+ return meta.registry.createType('DigestItem', {
94
+ PreRuntime: [
95
+ consensusEngine,
96
+ newSlot
97
+ ]
98
+ });
99
+ } else if (consensusEngine?.toString() === 'nmbs') {
100
+ const nmbsKey = stringToHex('nmbs');
101
+ if (meta.query.randomness?.notFirstBlock) {
102
+ // TODO: shouldn't modify existing head
103
+ // reset notFirstBlock so randomness will skip validation
104
+ head.pushStorageLayer().set(compactHex(meta.query.randomness.notFirstBlock()), StorageValueKind.Deleted);
105
+ }
106
+ return meta.registry.createType('DigestItem', {
107
+ // Using previous block author
108
+ PreRuntime: [
109
+ consensusEngine,
110
+ parentHeader.digest.logs.find((log)=>log.isPreRuntime && log.asPreRuntime[0].toHex() === nmbsKey)?.asPreRuntime[1].toHex()
111
+ ]
112
+ });
113
+ }
128
114
  }
129
- }
115
+ return item;
116
+ }));
130
117
  const header = meta.registry.createType('Header', {
131
118
  parentHash: head.hash,
132
119
  number: unsafeBlockHeight ?? head.number + 1,
@@ -175,7 +162,10 @@ const initNewBlock = async (head, header, inherentProviders, params, storageLaye
175
162
  if (extrinsics.length === 0) {
176
163
  continue;
177
164
  }
178
- const resp = await newBlock.call('BlockBuilder_apply_extrinsic', extrinsics);
165
+ // bypass signature check during inherent extrinsics
166
+ // this is needed to allow cumulus to accept fake relay block digests
167
+ // this should be safe because there are no valid use of invalid signatures in inherents
168
+ const resp = await newBlock.call('BlockBuilder_apply_extrinsic', extrinsics, true);
179
169
  const layer = newBlock.pushStorageLayer();
180
170
  layer.setAll(resp.storageDiff);
181
171
  layers.push(layer);
@@ -92,5 +92,5 @@ export declare class Block {
92
92
  /**
93
93
  * Call a runtime method.
94
94
  */
95
- call(method: string, args: HexString[]): Promise<TaskCallResponse>;
95
+ call(method: string, args: HexString[], mockSigantureHostOverride?: boolean): Promise<TaskCallResponse>;
96
96
  }
@@ -232,7 +232,7 @@ import { RemoteStorageLayer, StorageLayer, StorageValueKind } from './storage-la
232
232
  }
233
233
  /**
234
234
  * Call a runtime method.
235
- */ async call(method, args) {
235
+ */ async call(method, args, mockSigantureHostOverride = false) {
236
236
  const wasm = await this.wasm;
237
237
  const response = await runTask({
238
238
  wasm,
@@ -245,7 +245,7 @@ import { RemoteStorageLayer, StorageLayer, StorageValueKind } from './storage-la
245
245
  mockSignatureHost: this.#chain.mockSignatureHost,
246
246
  allowUnresolvedImports: this.#chain.allowUnresolvedImports,
247
247
  runtimeLogLevel: this.#chain.runtimeLogLevel
248
- }, taskHandler(this));
248
+ }, taskHandler(this), mockSigantureHostOverride);
249
249
  if ('Call' in response) {
250
250
  if (this.chain.offchainWorker) {
251
251
  // apply offchain storage
@@ -13,6 +13,7 @@ export type ValidationData = {
13
13
  relayChainState: {
14
14
  trieNodes: HexString[];
15
15
  };
16
+ relayParentDescendants?: any[];
16
17
  };
17
18
  export declare class SetValidationData implements InherentProvider {
18
19
  createInherents(newBlock: Block, params: BuildBlockParams): Promise<HexString[]>;
@@ -3,12 +3,13 @@ import { hexToU8a, u8aConcat, u8aToHex } from '@polkadot/util';
3
3
  import { blake2AsHex, blake2AsU8a } from '@polkadot/util-crypto';
4
4
  import _ from 'lodash';
5
5
  import { defaultLogger } from '../../../logger.js';
6
- import { compactHex, getCurrentSlot, getParaId } from '../../../utils/index.js';
6
+ import { compactHex, getCurrentSlot, getParaId, getSlotDuration } from '../../../utils/index.js';
7
7
  import { dmqMqcHead, hrmpChannels, hrmpEgressChannelIndex, hrmpIngressChannelIndex, paraHead, upgradeGoAheadSignal, WELL_KNOWN_KEYS } from '../../../utils/proof.js';
8
8
  import { createProof, decodeProof } from '../../../wasm-executor/index.js';
9
9
  const logger = defaultLogger.child({
10
10
  name: 'parachain-validation-data'
11
11
  });
12
+ const RELAY_CHAIN_SLOT_DURATION_MILLIS = 6_000;
12
13
  const MOCK_VALIDATION_DATA = {
13
14
  validationData: {
14
15
  relayParentNumber: 1000,
@@ -101,17 +102,14 @@ export class SetValidationData {
101
102
  const hrmpIngressChannelIndexKey = hrmpIngressChannelIndex(paraId);
102
103
  const hrmpEgressChannelIndexKey = hrmpEgressChannelIndex(paraId);
103
104
  const decoded = await decodeProof(extrinsic.validationData.relayParentStorageRoot, extrinsic.relayChainState.trieNodes);
104
- const relaySlotIncrease = Math.max(1, meta.consts.timestamp?.minimumPeriod // legacy
105
- ?.divn(3000) // relaychain min period
106
- ?.toNumber() || meta.consts.aura?.slotDuration // async backing
107
- ?.divn(6000) // relaychain block time
108
- ?.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
+ ;
109
108
  for (const key of Object.values(WELL_KNOWN_KEYS)){
110
109
  if (key === WELL_KNOWN_KEYS.CURRENT_SLOT) {
111
110
  // increment current slot
112
111
  const relayCurrentSlot = decoded[key] ? meta.registry.createType('Slot', hexToU8a(decoded[key])).toNumber() : await getCurrentSlot(parent) * relaySlotIncrease;
113
- const newSlot = meta.registry.createType('Slot', relayCurrentSlot + relaySlotIncrease + 1) // +1 to be safe
114
- ;
112
+ const newSlot = meta.registry.createType('Slot', relayCurrentSlot + relaySlotIncrease);
115
113
  logger.debug({
116
114
  relayCurrentSlot,
117
115
  newSlot: newSlot.toNumber()
@@ -261,6 +259,7 @@ export class SetValidationData {
261
259
  }
262
260
  const { trieRootHash, nodes } = await createProof(extrinsic.relayChainState.trieNodes, newEntries);
263
261
  const argsLengh = meta.tx.parachainSystem.setValidationData.meta.args.length;
262
+ const relayParentNumber = params.relayParentNumber ?? extrinsic.validationData.relayParentNumber + relaySlotIncrease;
264
263
  if (argsLengh === 1) {
265
264
  // old version
266
265
  const newData = {
@@ -270,7 +269,7 @@ export class SetValidationData {
270
269
  validationData: {
271
270
  ...extrinsic.validationData,
272
271
  relayParentStorageRoot: trieRootHash,
273
- relayParentNumber: params.relayParentNumber ?? extrinsic.validationData.relayParentNumber + relaySlotIncrease
272
+ relayParentNumber
274
273
  },
275
274
  relayChainState: {
276
275
  trieNodes: nodes
@@ -282,16 +281,40 @@ export class SetValidationData {
282
281
  ];
283
282
  } else if (argsLengh === 2) {
284
283
  // new version
284
+ let relayParentDescendants = extrinsic.relayParentDescendants;
285
+ if (relayParentDescendants) {
286
+ let fakeParentHeader = relayParentDescendants[0];
287
+ if (fakeParentHeader) {
288
+ fakeParentHeader = {
289
+ ...fakeParentHeader,
290
+ number: relayParentNumber,
291
+ stateRoot: trieRootHash
292
+ };
293
+ relayParentDescendants = [
294
+ fakeParentHeader,
295
+ ...relayParentDescendants.slice(1)
296
+ ];
297
+ let lastHeader;
298
+ for (const descendant of relayParentDescendants){
299
+ if (lastHeader) {
300
+ descendant.parentHash = lastHeader.hash;
301
+ descendant.number = lastHeader.number.toNumber() + 1;
302
+ }
303
+ lastHeader = meta.registry.createType('Header', descendant);
304
+ }
305
+ }
306
+ }
285
307
  const newData = {
286
308
  ...extrinsic,
287
309
  validationData: {
288
310
  ...extrinsic.validationData,
289
311
  relayParentStorageRoot: trieRootHash,
290
- relayParentNumber: params.relayParentNumber ?? extrinsic.validationData.relayParentNumber + relaySlotIncrease
312
+ relayParentNumber
291
313
  },
292
314
  relayChainState: {
293
315
  trieNodes: nodes
294
- }
316
+ },
317
+ relayParentDescendants
295
318
  };
296
319
  const horizontalMessagesArray = Object.entries(horizontalMessages).flatMap(([sender, messages])=>messages.map((msg)=>[
297
320
  sender,
@@ -75,6 +75,10 @@ const following = new Map();
75
75
  ],
76
76
  finalizedBlockRuntime: withRuntime ? await context.chain.head.runtimeVersion : null
77
77
  });
78
+ callback({
79
+ event: 'bestBlockChanged',
80
+ bestBlockHash: context.chain.head.hash
81
+ });
78
82
  });
79
83
  return id;
80
84
  };
@@ -8,6 +8,7 @@ export declare const WELL_KNOWN_KEYS: {
8
8
  TWO_EPOCHS_AGO_RANDOMNESS: HexString;
9
9
  CURRENT_SLOT: HexString;
10
10
  ACTIVE_CONFIG: HexString;
11
+ AUTHORITIES: HexString;
11
12
  };
12
13
  export declare const dmqMqcHead: (paraId: u32) => `0x${string}`;
13
14
  export declare const upgradeGoAheadSignal: (paraId: u32) => `0x${string}`;
@@ -6,7 +6,8 @@ export const WELL_KNOWN_KEYS = {
6
6
  ONE_EPOCH_AGO_RANDOMNESS: '0x1cb6f36e027abb2091cfb5110ab5087f7ce678799d3eff024253b90e84927cc6',
7
7
  TWO_EPOCHS_AGO_RANDOMNESS: '0x1cb6f36e027abb2091cfb5110ab5087f7a414cb008e0e61e46722aa60abdd672',
8
8
  CURRENT_SLOT: '0x1cb6f36e027abb2091cfb5110ab5087f06155b3cd9a8c9e5e9a23fd5dc13a5ed',
9
- ACTIVE_CONFIG: '0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385'
9
+ ACTIVE_CONFIG: '0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385',
10
+ AUTHORITIES: '0x1cb6f36e027abb2091cfb5110ab5087f5e0621c4869aa60c02be9adcc98a0d1d'
10
11
  };
11
12
  const hash = (prefix, suffix)=>{
12
13
  return u8aToHex(u8aConcat(hexToU8a(prefix), xxhashAsU8a(suffix, 64), suffix));
@@ -46,7 +46,7 @@ export interface WasmExecutor {
46
46
  runTask: (task: {
47
47
  wasm: HexString;
48
48
  calls: [string, HexString[]][];
49
- mockSignatureHost: boolean;
49
+ mockSignatureHost: number;
50
50
  allowUnresolvedImports: boolean;
51
51
  runtimeLogLevel: number;
52
52
  }, callback?: JsCallback) => Promise<TaskResponse>;
@@ -65,7 +65,7 @@ export declare const createProof: (nodes: HexString[], updates: [HexString, HexS
65
65
  trieRootHash: `0x${string}`;
66
66
  nodes: `0x${string}`[];
67
67
  }>;
68
- export declare const runTask: (task: TaskCall, callback?: JsCallback) => Promise<{
68
+ export declare const runTask: (task: TaskCall, callback?: JsCallback, overrideMockSignatureHost?: boolean) => Promise<{
69
69
  Call: TaskCallResponse;
70
70
  } | {
71
71
  Error: string;
@@ -49,12 +49,13 @@ export const createProof = async (nodes, updates)=>{
49
49
  };
50
50
  };
51
51
  let nextTaskId = 0;
52
- export const runTask = async (task, callback = emptyTaskHandler)=>{
52
+ export const runTask = async (task, callback = emptyTaskHandler, overrideMockSignatureHost = false)=>{
53
53
  const taskId = nextTaskId++;
54
54
  const task2 = {
55
55
  ...task,
56
56
  id: taskId,
57
- storageProofSize: task.storageProofSize ?? 0
57
+ storageProofSize: task.storageProofSize ?? 0,
58
+ mockSignatureHost: overrideMockSignatureHost ? 2 : task.mockSignatureHost ? 1 : 0
58
59
  };
59
60
  const worker = await getWorker();
60
61
  logger.trace(truncate(task2), `runTask #${taskId}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acala-network/chopsticks-core",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
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.2",
17
+ "@acala-network/chopsticks-executor": "1.2.4",
18
18
  "@polkadot/rpc-provider": "^16.4.1",
19
19
  "@polkadot/types": "^16.4.1",
20
20
  "@polkadot/types-codec": "^16.4.1",