@acala-network/chopsticks-core 1.4.2 → 1.5.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 CHANGED
@@ -36,6 +36,11 @@ export declare class Api {
36
36
  getStorage(key: string, hash?: string): Promise<`0x${string}` | null>;
37
37
  getKeysPaged(prefix: string, pageSize: number, startKey: string, hash?: string): Promise<`0x${string}`[]>;
38
38
  getStorageBatch(prefix: HexString, keys: HexString[], hash?: HexString): any;
39
+ /** Upstream `state_getReadProof`. Throws if upstream doesn't expose it. */
40
+ getReadProof(keys: HexString[], hash?: HexString): Promise<{
41
+ at: HexString;
42
+ proof: HexString[];
43
+ }>;
39
44
  subscribeRemoteNewHeads(cb: ProviderInterfaceCallback): Promise<string | number>;
40
45
  subscribeRemoteFinalizedHeads(cb: ProviderInterfaceCallback): Promise<string | number>;
41
46
  }
package/dist/cjs/api.js CHANGED
@@ -234,6 +234,15 @@ class Api {
234
234
  if (hash) params.push(hash);
235
235
  return _class_private_field_get(this, _provider).send('state_queryStorageAt', params, !!hash).then((result)=>result[0]?.['changes'] || []).catch(retryOnError);
236
236
  }
237
+ /** Upstream `state_getReadProof`. Throws if upstream doesn't expose it. */ async getReadProof(keys, hash) {
238
+ const params = hash ? [
239
+ keys,
240
+ hash
241
+ ] : [
242
+ keys
243
+ ];
244
+ return this.send('state_getReadProof', params, !!hash);
245
+ }
237
246
  async subscribeRemoteNewHeads(cb) {
238
247
  if (!_class_private_field_get(this, _provider).hasSubscriptions) {
239
248
  throw new Error('subscribeRemoteNewHeads only works with subscriptions');
@@ -0,0 +1,5 @@
1
+ import type { HexString } from '@polkadot/util/types';
2
+ /** `ParaInfo { best_head_hash: { at_relay_block_number: u32, head_hash: H256 }, next_imported_hash_position: u32 }` */
3
+ export declare const encodeParaInfo: (atRelayBlockNumber: number, headHash: Uint8Array, nextImportedHashPosition: number) => HexString;
4
+ /** `ParaStoredHeaderData(Vec<u8>)` wrapping SCALE-encoded `StoredHeaderData { number: u32, state_root: H256 }`. */
5
+ export declare const encodeParaStoredHeaderData: (blockNumber: number, stateRoot: Uint8Array) => HexString;
@@ -0,0 +1,38 @@
1
+ // Value encoders for `pallet_bridge_parachains` storage items. The bridge connector forges
2
+ // these values via `dev_setStorage`, so they're SCALE-encoded by hand — runtime metadata
3
+ // describes their types but can't construct them. Storage *keys*, by contrast, are derived
4
+ // from metadata by the callers (`api.query[pallet].item.key(...)`), never hand-hashed.
5
+ "use strict";
6
+ Object.defineProperty(exports, "__esModule", {
7
+ value: true
8
+ });
9
+ function _export(target, all) {
10
+ for(var name in all)Object.defineProperty(target, name, {
11
+ enumerable: true,
12
+ get: Object.getOwnPropertyDescriptor(all, name).get
13
+ });
14
+ }
15
+ _export(exports, {
16
+ get encodeParaInfo () {
17
+ return encodeParaInfo;
18
+ },
19
+ get encodeParaStoredHeaderData () {
20
+ return encodeParaStoredHeaderData;
21
+ }
22
+ });
23
+ const _util = require("@polkadot/util");
24
+ const u32LE = (v)=>(0, _util.nToU8a)(v, {
25
+ bitLength: 32,
26
+ isLe: true
27
+ });
28
+ const requireBytes = (label, b, len)=>{
29
+ if (b.length !== len) throw new Error(`${label} must be ${len} bytes, got ${b.length}`);
30
+ };
31
+ const encodeParaInfo = (atRelayBlockNumber, headHash, nextImportedHashPosition)=>{
32
+ requireBytes('headHash', headHash, 32);
33
+ return (0, _util.u8aToHex)((0, _util.u8aConcat)(u32LE(atRelayBlockNumber), headHash, u32LE(nextImportedHashPosition)));
34
+ };
35
+ const encodeParaStoredHeaderData = (blockNumber, stateRoot)=>{
36
+ requireBytes('stateRoot', stateRoot, 32);
37
+ return (0, _util.u8aToHex)((0, _util.compactAddLength)((0, _util.u8aConcat)(u32LE(blockNumber), stateRoot)));
38
+ };
@@ -0,0 +1,2 @@
1
+ export declare const bridgeLogger: import("pino").Logger<never, boolean>;
2
+ export * from './encode.js';
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "bridgeLogger", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return bridgeLogger;
9
+ }
10
+ });
11
+ const _logger = require("../logger.js");
12
+ _export_star(require("./encode.js"), exports);
13
+ function _export_star(from, to) {
14
+ Object.keys(from).forEach(function(k) {
15
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
16
+ Object.defineProperty(to, k, {
17
+ enumerable: true,
18
+ get: function() {
19
+ return from[k];
20
+ }
21
+ });
22
+ }
23
+ });
24
+ return from;
25
+ }
26
+ const bridgeLogger = _logger.defaultLogger.child({
27
+ name: 'bridge'
28
+ });
@@ -37,6 +37,7 @@ export * from './blockchain/index.js';
37
37
  export * from './blockchain/inherent/index.js';
38
38
  export * from './blockchain/storage-layer.js';
39
39
  export * from './blockchain/txpool.js';
40
+ export * from './bridge/index.js';
40
41
  export * from './chopsticks-provider.js';
41
42
  export * from './database.js';
42
43
  export * from './env.js';
package/dist/cjs/index.js CHANGED
@@ -18,6 +18,7 @@ _export_star(require("./blockchain/index.js"), exports);
18
18
  _export_star(require("./blockchain/inherent/index.js"), exports);
19
19
  _export_star(require("./blockchain/storage-layer.js"), exports);
20
20
  _export_star(require("./blockchain/txpool.js"), exports);
21
+ _export_star(require("./bridge/index.js"), exports);
21
22
  _export_star(require("./chopsticks-provider.js"), exports);
22
23
  _export_star(require("./database.js"), exports);
23
24
  _export_star(require("./env.js"), exports);
@@ -0,0 +1,20 @@
1
+ import type { HexString } from '@polkadot/util/types';
2
+ import type { Handler } from '../shared.js';
3
+ /**
4
+ * @param context
5
+ * @param params - [`keys`, `blockhash?`]
6
+ *
7
+ * @return `{ at, proof, stateRoot }`
8
+ *
9
+ * Chopsticks-specific variant of `state_getReadProof` that also returns `stateRoot` — the
10
+ * root the composed proof verifies against, which verifiers must check instead of the header
11
+ * state root (see `buildReadProof`). Kept out of the spec method so it stays `{ at, proof }`.
12
+ */
13
+ export declare const dev_getReadProof: Handler<[
14
+ HexString[],
15
+ HexString | undefined
16
+ ], {
17
+ at: HexString;
18
+ proof: HexString[];
19
+ stateRoot: HexString;
20
+ }>;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "dev_getReadProof", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return dev_getReadProof;
9
+ }
10
+ });
11
+ const _readproof = require("../read-proof.js");
12
+ const dev_getReadProof = async (context, [keys, hash])=>(0, _readproof.buildReadProof)(context, keys, hash);
@@ -1,4 +1,9 @@
1
1
  declare const handlers: {
2
+ dev_getReadProof: import("../shared.js").Handler<[`0x${string}`[], `0x${string}` | undefined], {
3
+ at: import("@polkadot/util/types").HexString;
4
+ proof: import("@polkadot/util/types").HexString[];
5
+ stateRoot: import("@polkadot/util/types").HexString;
6
+ }>;
2
7
  dev_newBlock: (context: import("../shared.js").Context, [params]: [import("./new-block.js").NewBlockParams]) => Promise<`0x${string}`>;
3
8
  dev_setBlockBuildMode: (context: import("../shared.js").Context, [mode]: [import("../../index.js").BuildBlockMode]) => Promise<void>;
4
9
  dev_setHead: (context: import("../shared.js").Context, [params]: [number | `0x${string}`]) => Promise<`0x${string}`>;
@@ -7,6 +12,7 @@ declare const handlers: {
7
12
  dev_timeTravel: (context: import("../shared.js").Context, [date]: [string | number]) => Promise<number>;
8
13
  };
9
14
  export default handlers;
15
+ export * from './get-read-proof.js';
10
16
  export * from './new-block.js';
11
17
  export * from './set-block-build-mode.js';
12
18
  export * from './set-head.js';
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "default", {
8
8
  return _default;
9
9
  }
10
10
  });
11
+ const _getreadproof = _export_star(require("./get-read-proof.js"), exports);
11
12
  const _newblock = _export_star(require("./new-block.js"), exports);
12
13
  const _setblockbuildmode = _export_star(require("./set-block-build-mode.js"), exports);
13
14
  const _sethead = _export_star(require("./set-head.js"), exports);
@@ -28,6 +29,7 @@ function _export_star(from, to) {
28
29
  return from;
29
30
  }
30
31
  const handlers = {
32
+ dev_getReadProof: _getreadproof.dev_getReadProof,
31
33
  dev_newBlock: _newblock.dev_newBlock,
32
34
  dev_setBlockBuildMode: _setblockbuildmode.dev_setBlockBuildMode,
33
35
  dev_setHead: _sethead.dev_setHead,
@@ -0,0 +1,15 @@
1
+ import type { HexString } from '@polkadot/util/types';
2
+ import { type Context } from './shared.js';
3
+ /**
4
+ * Compose a storage read proof: chopsticks keeps no trie of its own, so it fetches a base
5
+ * proof from upstream (falling back to upstream head for chopsticks-only blocks) and
6
+ * re-applies chopsticks-side values via `createProof`. Returns the proof nodes and the
7
+ * recomputed trie root — which diverges from `chain_getHeader(at).state_root` once local
8
+ * overrides are applied. Shared by `state_getReadProof` (drops the root, spec shape) and
9
+ * `dev_getReadProof` (keeps it). Child-storage keys are rejected.
10
+ */
11
+ export declare const buildReadProof: (context: Context, keys: HexString[], hash: HexString | undefined) => Promise<{
12
+ at: HexString;
13
+ proof: HexString[];
14
+ stateRoot: HexString;
15
+ }>;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "buildReadProof", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return buildReadProof;
9
+ }
10
+ });
11
+ const _logger = require("../logger.js");
12
+ const _index = require("../utils/index.js");
13
+ const _index1 = require("../wasm-executor/index.js");
14
+ const _shared = require("./shared.js");
15
+ const logger = _logger.defaultLogger.child({
16
+ name: 'rpc-read-proof'
17
+ });
18
+ const buildReadProof = async (context, keys, hash)=>{
19
+ if (keys.length === 0) {
20
+ throw new _shared.ResponseError(-32602, 'getReadProof requires a non-empty array of keys');
21
+ }
22
+ for (const key of keys){
23
+ if ((0, _index.isPrefixedChildKey)(key)) {
24
+ throw new _shared.ResponseError(-32601, `getReadProof does not support child-storage keys (got ${key}); use state_getChildReadProof`);
25
+ }
26
+ }
27
+ const block = await context.chain.getBlock(hash);
28
+ if (!block) {
29
+ throw new _shared.ResponseError(1, `Block ${hash ?? 'head'} not found`);
30
+ }
31
+ // Upstream rejects chopsticks-only blocks with UnknownBlock; fall back to upstream
32
+ // head whose trie is guaranteed available.
33
+ const fetchUpstreamProof = async ()=>{
34
+ try {
35
+ return await context.chain.api.getReadProof(keys, block.hash);
36
+ } catch (err) {
37
+ logger.debug({
38
+ err: err.message,
39
+ blockHash: block.hash
40
+ }, 'getReadProof at block failed; retrying at head');
41
+ try {
42
+ return await context.chain.api.getReadProof(keys);
43
+ } catch (err2) {
44
+ throw new _shared.ResponseError(-32603, `getReadProof: upstream rejected at block ${block.hash} and at head (${err2.message})`);
45
+ }
46
+ }
47
+ };
48
+ // The local reads and the upstream proof are independent — overlap their round trips.
49
+ const [updates, upstreamProof] = await Promise.all([
50
+ Promise.all(keys.map(async (key)=>[
51
+ key,
52
+ await block.get(key) ?? null
53
+ ])),
54
+ fetchUpstreamProof()
55
+ ]);
56
+ const { trieRootHash, nodes } = await (0, _index1.createProof)(upstreamProof.proof, updates);
57
+ return {
58
+ at: block.hash,
59
+ proof: nodes,
60
+ stateRoot: trieRootHash
61
+ };
62
+ };
@@ -35,6 +35,10 @@ declare const handlers: {
35
35
  changes: [string, string | null][];
36
36
  }]>;
37
37
  state_call: import("../shared.js").Handler<[import("@polkadot/util/types").HexString, import("@polkadot/util/types").HexString, import("@polkadot/util/types").HexString], import("@polkadot/util/types").HexString>;
38
+ state_getReadProof: import("../shared.js").Handler<[import("@polkadot/util/types").HexString[], import("@polkadot/util/types").HexString | undefined], {
39
+ at: import("@polkadot/util/types").HexString;
40
+ proof: import("@polkadot/util/types").HexString[];
41
+ }>;
38
42
  state_subscribeRuntimeVersion: import("../shared.js").Handler<[], string>;
39
43
  state_unsubscribeRuntimeVersion: import("../shared.js").Handler<[import("@polkadot/util/types").HexString], void>;
40
44
  state_subscribeStorage: import("../shared.js").Handler<[string[]], string>;
@@ -51,6 +51,24 @@ export declare const state_queryStorageAt: Handler<[
51
51
  * @return result in hash
52
52
  */
53
53
  export declare const state_call: Handler<[HexString, HexString, HexString], HexString>;
54
+ /**
55
+ * @param context
56
+ * @param params - [`keys`, `blockhash?`]
57
+ *
58
+ * @return `{ at, proof }` — proof is a list of SCALE-encoded trie nodes.
59
+ *
60
+ * Spec-compatible response shape. The composed proof's root diverges from
61
+ * `chain_getHeader(at).state_root` once local overrides are applied (see `buildReadProof`);
62
+ * callers that need the recomputed root should use chopsticks's `dev_getReadProof`.
63
+ * Child-storage keys are rejected — use `state_getChildReadProof`.
64
+ */
65
+ export declare const state_getReadProof: Handler<[
66
+ HexString[],
67
+ HexString | undefined
68
+ ], {
69
+ at: HexString;
70
+ proof: HexString[];
71
+ }>;
54
72
  /**
55
73
  * @return subscription id
56
74
  */
@@ -24,6 +24,9 @@ _export(exports, {
24
24
  get state_getMetadata () {
25
25
  return state_getMetadata;
26
26
  },
27
+ get state_getReadProof () {
28
+ return state_getReadProof;
29
+ },
27
30
  get state_getRuntimeVersion () {
28
31
  return state_getRuntimeVersion;
29
32
  },
@@ -52,6 +55,7 @@ _export(exports, {
52
55
  const _util = require("@polkadot/util");
53
56
  const _logger = require("../../logger.js");
54
57
  const _index = require("../../utils/index.js");
58
+ const _readproof = require("../read-proof.js");
55
59
  const _shared = require("../shared.js");
56
60
  const logger = _logger.defaultLogger.child({
57
61
  name: 'rpc-state'
@@ -103,6 +107,13 @@ const state_call = async (context, [method, data, hash])=>{
103
107
  ]);
104
108
  return resp.result;
105
109
  };
110
+ const state_getReadProof = async (context, [keys, hash])=>{
111
+ const { at, proof } = await (0, _readproof.buildReadProof)(context, keys, hash);
112
+ return {
113
+ at,
114
+ proof
115
+ };
116
+ };
106
117
  const state_subscribeRuntimeVersion = async (context, _params, { subscribe })=>{
107
118
  let update = (_block)=>{};
108
119
  const id = await context.chain.headState.subscribeStorage([
package/dist/esm/api.d.ts CHANGED
@@ -36,6 +36,11 @@ export declare class Api {
36
36
  getStorage(key: string, hash?: string): Promise<`0x${string}` | null>;
37
37
  getKeysPaged(prefix: string, pageSize: number, startKey: string, hash?: string): Promise<`0x${string}`[]>;
38
38
  getStorageBatch(prefix: HexString, keys: HexString[], hash?: HexString): any;
39
+ /** Upstream `state_getReadProof`. Throws if upstream doesn't expose it. */
40
+ getReadProof(keys: HexString[], hash?: HexString): Promise<{
41
+ at: HexString;
42
+ proof: HexString[];
43
+ }>;
39
44
  subscribeRemoteNewHeads(cb: ProviderInterfaceCallback): Promise<string | number>;
40
45
  subscribeRemoteFinalizedHeads(cb: ProviderInterfaceCallback): Promise<string | number>;
41
46
  }
package/dist/esm/api.js CHANGED
@@ -191,6 +191,15 @@ const CACHEABLE_METHODS = new Set([
191
191
  if (hash) params.push(hash);
192
192
  return this.#provider.send('state_queryStorageAt', params, !!hash).then((result)=>result[0]?.['changes'] || []).catch(retryOnError);
193
193
  }
194
+ /** Upstream `state_getReadProof`. Throws if upstream doesn't expose it. */ async getReadProof(keys, hash) {
195
+ const params = hash ? [
196
+ keys,
197
+ hash
198
+ ] : [
199
+ keys
200
+ ];
201
+ return this.send('state_getReadProof', params, !!hash);
202
+ }
194
203
  async subscribeRemoteNewHeads(cb) {
195
204
  if (!this.#provider.hasSubscriptions) {
196
205
  throw new Error('subscribeRemoteNewHeads only works with subscriptions');
@@ -0,0 +1,5 @@
1
+ import type { HexString } from '@polkadot/util/types';
2
+ /** `ParaInfo { best_head_hash: { at_relay_block_number: u32, head_hash: H256 }, next_imported_hash_position: u32 }` */
3
+ export declare const encodeParaInfo: (atRelayBlockNumber: number, headHash: Uint8Array, nextImportedHashPosition: number) => HexString;
4
+ /** `ParaStoredHeaderData(Vec<u8>)` wrapping SCALE-encoded `StoredHeaderData { number: u32, state_root: H256 }`. */
5
+ export declare const encodeParaStoredHeaderData: (blockNumber: number, stateRoot: Uint8Array) => HexString;
@@ -0,0 +1,20 @@
1
+ // Value encoders for `pallet_bridge_parachains` storage items. The bridge connector forges
2
+ // these values via `dev_setStorage`, so they're SCALE-encoded by hand — runtime metadata
3
+ // describes their types but can't construct them. Storage *keys*, by contrast, are derived
4
+ // from metadata by the callers (`api.query[pallet].item.key(...)`), never hand-hashed.
5
+ import { compactAddLength, nToU8a, u8aConcat, u8aToHex } from '@polkadot/util';
6
+ const u32LE = (v)=>nToU8a(v, {
7
+ bitLength: 32,
8
+ isLe: true
9
+ });
10
+ const requireBytes = (label, b, len)=>{
11
+ if (b.length !== len) throw new Error(`${label} must be ${len} bytes, got ${b.length}`);
12
+ };
13
+ /** `ParaInfo { best_head_hash: { at_relay_block_number: u32, head_hash: H256 }, next_imported_hash_position: u32 }` */ export const encodeParaInfo = (atRelayBlockNumber, headHash, nextImportedHashPosition)=>{
14
+ requireBytes('headHash', headHash, 32);
15
+ return u8aToHex(u8aConcat(u32LE(atRelayBlockNumber), headHash, u32LE(nextImportedHashPosition)));
16
+ };
17
+ /** `ParaStoredHeaderData(Vec<u8>)` wrapping SCALE-encoded `StoredHeaderData { number: u32, state_root: H256 }`. */ export const encodeParaStoredHeaderData = (blockNumber, stateRoot)=>{
18
+ requireBytes('stateRoot', stateRoot, 32);
19
+ return u8aToHex(compactAddLength(u8aConcat(u32LE(blockNumber), stateRoot)));
20
+ };
@@ -0,0 +1,2 @@
1
+ export declare const bridgeLogger: import("pino").Logger<never, boolean>;
2
+ export * from './encode.js';
@@ -0,0 +1,5 @@
1
+ import { defaultLogger } from '../logger.js';
2
+ export const bridgeLogger = defaultLogger.child({
3
+ name: 'bridge'
4
+ });
5
+ export * from './encode.js';
@@ -37,6 +37,7 @@ export * from './blockchain/index.js';
37
37
  export * from './blockchain/inherent/index.js';
38
38
  export * from './blockchain/storage-layer.js';
39
39
  export * from './blockchain/txpool.js';
40
+ export * from './bridge/index.js';
40
41
  export * from './chopsticks-provider.js';
41
42
  export * from './database.js';
42
43
  export * from './env.js';
package/dist/esm/index.js CHANGED
@@ -14,6 +14,7 @@ export * from './blockchain/index.js';
14
14
  export * from './blockchain/inherent/index.js';
15
15
  export * from './blockchain/storage-layer.js';
16
16
  export * from './blockchain/txpool.js';
17
+ export * from './bridge/index.js';
17
18
  export * from './chopsticks-provider.js';
18
19
  export * from './database.js';
19
20
  export * from './env.js';
@@ -0,0 +1,20 @@
1
+ import type { HexString } from '@polkadot/util/types';
2
+ import type { Handler } from '../shared.js';
3
+ /**
4
+ * @param context
5
+ * @param params - [`keys`, `blockhash?`]
6
+ *
7
+ * @return `{ at, proof, stateRoot }`
8
+ *
9
+ * Chopsticks-specific variant of `state_getReadProof` that also returns `stateRoot` — the
10
+ * root the composed proof verifies against, which verifiers must check instead of the header
11
+ * state root (see `buildReadProof`). Kept out of the spec method so it stays `{ at, proof }`.
12
+ */
13
+ export declare const dev_getReadProof: Handler<[
14
+ HexString[],
15
+ HexString | undefined
16
+ ], {
17
+ at: HexString;
18
+ proof: HexString[];
19
+ stateRoot: HexString;
20
+ }>;
@@ -0,0 +1,11 @@
1
+ import { buildReadProof } from '../read-proof.js';
2
+ /**
3
+ * @param context
4
+ * @param params - [`keys`, `blockhash?`]
5
+ *
6
+ * @return `{ at, proof, stateRoot }`
7
+ *
8
+ * Chopsticks-specific variant of `state_getReadProof` that also returns `stateRoot` — the
9
+ * root the composed proof verifies against, which verifiers must check instead of the header
10
+ * state root (see `buildReadProof`). Kept out of the spec method so it stays `{ at, proof }`.
11
+ */ export const dev_getReadProof = async (context, [keys, hash])=>buildReadProof(context, keys, hash);
@@ -1,4 +1,9 @@
1
1
  declare const handlers: {
2
+ dev_getReadProof: import("../shared.js").Handler<[`0x${string}`[], `0x${string}` | undefined], {
3
+ at: import("@polkadot/util/types").HexString;
4
+ proof: import("@polkadot/util/types").HexString[];
5
+ stateRoot: import("@polkadot/util/types").HexString;
6
+ }>;
2
7
  dev_newBlock: (context: import("../shared.js").Context, [params]: [import("./new-block.js").NewBlockParams]) => Promise<`0x${string}`>;
3
8
  dev_setBlockBuildMode: (context: import("../shared.js").Context, [mode]: [import("../../index.js").BuildBlockMode]) => Promise<void>;
4
9
  dev_setHead: (context: import("../shared.js").Context, [params]: [number | `0x${string}`]) => Promise<`0x${string}`>;
@@ -7,6 +12,7 @@ declare const handlers: {
7
12
  dev_timeTravel: (context: import("../shared.js").Context, [date]: [string | number]) => Promise<number>;
8
13
  };
9
14
  export default handlers;
15
+ export * from './get-read-proof.js';
10
16
  export * from './new-block.js';
11
17
  export * from './set-block-build-mode.js';
12
18
  export * from './set-head.js';
@@ -1,3 +1,4 @@
1
+ import { dev_getReadProof } from './get-read-proof.js';
1
2
  import { dev_newBlock } from './new-block.js';
2
3
  import { dev_setBlockBuildMode } from './set-block-build-mode.js';
3
4
  import { dev_setHead } from './set-head.js';
@@ -5,6 +6,7 @@ import { dev_setRuntimeLogLevel } from './set-runtime-log-level.js';
5
6
  import { dev_setStorage } from './set-storage.js';
6
7
  import { dev_timeTravel } from './time-travel.js';
7
8
  const handlers = {
9
+ dev_getReadProof,
8
10
  dev_newBlock,
9
11
  dev_setBlockBuildMode,
10
12
  dev_setHead,
@@ -13,6 +15,7 @@ const handlers = {
13
15
  dev_timeTravel
14
16
  };
15
17
  export default handlers;
18
+ export * from './get-read-proof.js';
16
19
  export * from './new-block.js';
17
20
  export * from './set-block-build-mode.js';
18
21
  export * from './set-head.js';
@@ -0,0 +1,15 @@
1
+ import type { HexString } from '@polkadot/util/types';
2
+ import { type Context } from './shared.js';
3
+ /**
4
+ * Compose a storage read proof: chopsticks keeps no trie of its own, so it fetches a base
5
+ * proof from upstream (falling back to upstream head for chopsticks-only blocks) and
6
+ * re-applies chopsticks-side values via `createProof`. Returns the proof nodes and the
7
+ * recomputed trie root — which diverges from `chain_getHeader(at).state_root` once local
8
+ * overrides are applied. Shared by `state_getReadProof` (drops the root, spec shape) and
9
+ * `dev_getReadProof` (keeps it). Child-storage keys are rejected.
10
+ */
11
+ export declare const buildReadProof: (context: Context, keys: HexString[], hash: HexString | undefined) => Promise<{
12
+ at: HexString;
13
+ proof: HexString[];
14
+ stateRoot: HexString;
15
+ }>;
@@ -0,0 +1,59 @@
1
+ import { defaultLogger } from '../logger.js';
2
+ import { isPrefixedChildKey } from '../utils/index.js';
3
+ import { createProof } from '../wasm-executor/index.js';
4
+ import { ResponseError } from './shared.js';
5
+ const logger = defaultLogger.child({
6
+ name: 'rpc-read-proof'
7
+ });
8
+ /**
9
+ * Compose a storage read proof: chopsticks keeps no trie of its own, so it fetches a base
10
+ * proof from upstream (falling back to upstream head for chopsticks-only blocks) and
11
+ * re-applies chopsticks-side values via `createProof`. Returns the proof nodes and the
12
+ * recomputed trie root — which diverges from `chain_getHeader(at).state_root` once local
13
+ * overrides are applied. Shared by `state_getReadProof` (drops the root, spec shape) and
14
+ * `dev_getReadProof` (keeps it). Child-storage keys are rejected.
15
+ */ export const buildReadProof = async (context, keys, hash)=>{
16
+ if (keys.length === 0) {
17
+ throw new ResponseError(-32602, 'getReadProof requires a non-empty array of keys');
18
+ }
19
+ for (const key of keys){
20
+ if (isPrefixedChildKey(key)) {
21
+ throw new ResponseError(-32601, `getReadProof does not support child-storage keys (got ${key}); use state_getChildReadProof`);
22
+ }
23
+ }
24
+ const block = await context.chain.getBlock(hash);
25
+ if (!block) {
26
+ throw new ResponseError(1, `Block ${hash ?? 'head'} not found`);
27
+ }
28
+ // Upstream rejects chopsticks-only blocks with UnknownBlock; fall back to upstream
29
+ // head whose trie is guaranteed available.
30
+ const fetchUpstreamProof = async ()=>{
31
+ try {
32
+ return await context.chain.api.getReadProof(keys, block.hash);
33
+ } catch (err) {
34
+ logger.debug({
35
+ err: err.message,
36
+ blockHash: block.hash
37
+ }, 'getReadProof at block failed; retrying at head');
38
+ try {
39
+ return await context.chain.api.getReadProof(keys);
40
+ } catch (err2) {
41
+ throw new ResponseError(-32603, `getReadProof: upstream rejected at block ${block.hash} and at head (${err2.message})`);
42
+ }
43
+ }
44
+ };
45
+ // The local reads and the upstream proof are independent — overlap their round trips.
46
+ const [updates, upstreamProof] = await Promise.all([
47
+ Promise.all(keys.map(async (key)=>[
48
+ key,
49
+ await block.get(key) ?? null
50
+ ])),
51
+ fetchUpstreamProof()
52
+ ]);
53
+ const { trieRootHash, nodes } = await createProof(upstreamProof.proof, updates);
54
+ return {
55
+ at: block.hash,
56
+ proof: nodes,
57
+ stateRoot: trieRootHash
58
+ };
59
+ };
@@ -35,6 +35,10 @@ declare const handlers: {
35
35
  changes: [string, string | null][];
36
36
  }]>;
37
37
  state_call: import("../shared.js").Handler<[import("@polkadot/util/types").HexString, import("@polkadot/util/types").HexString, import("@polkadot/util/types").HexString], import("@polkadot/util/types").HexString>;
38
+ state_getReadProof: import("../shared.js").Handler<[import("@polkadot/util/types").HexString[], import("@polkadot/util/types").HexString | undefined], {
39
+ at: import("@polkadot/util/types").HexString;
40
+ proof: import("@polkadot/util/types").HexString[];
41
+ }>;
38
42
  state_subscribeRuntimeVersion: import("../shared.js").Handler<[], string>;
39
43
  state_unsubscribeRuntimeVersion: import("../shared.js").Handler<[import("@polkadot/util/types").HexString], void>;
40
44
  state_subscribeStorage: import("../shared.js").Handler<[string[]], string>;
@@ -51,6 +51,24 @@ export declare const state_queryStorageAt: Handler<[
51
51
  * @return result in hash
52
52
  */
53
53
  export declare const state_call: Handler<[HexString, HexString, HexString], HexString>;
54
+ /**
55
+ * @param context
56
+ * @param params - [`keys`, `blockhash?`]
57
+ *
58
+ * @return `{ at, proof }` — proof is a list of SCALE-encoded trie nodes.
59
+ *
60
+ * Spec-compatible response shape. The composed proof's root diverges from
61
+ * `chain_getHeader(at).state_root` once local overrides are applied (see `buildReadProof`);
62
+ * callers that need the recomputed root should use chopsticks's `dev_getReadProof`.
63
+ * Child-storage keys are rejected — use `state_getChildReadProof`.
64
+ */
65
+ export declare const state_getReadProof: Handler<[
66
+ HexString[],
67
+ HexString | undefined
68
+ ], {
69
+ at: HexString;
70
+ proof: HexString[];
71
+ }>;
54
72
  /**
55
73
  * @return subscription id
56
74
  */
@@ -1,6 +1,7 @@
1
1
  import { stringToHex } from '@polkadot/util';
2
2
  import { defaultLogger } from '../../logger.js';
3
3
  import { isPrefixedChildKey, prefixedChildKey, stripChildPrefix } from '../../utils/index.js';
4
+ import { buildReadProof } from '../read-proof.js';
4
5
  import { ResponseError } from '../shared.js';
5
6
  const logger = defaultLogger.child({
6
7
  name: 'rpc-state'
@@ -82,6 +83,23 @@ const logger = defaultLogger.child({
82
83
  ]);
83
84
  return resp.result;
84
85
  };
86
+ /**
87
+ * @param context
88
+ * @param params - [`keys`, `blockhash?`]
89
+ *
90
+ * @return `{ at, proof }` — proof is a list of SCALE-encoded trie nodes.
91
+ *
92
+ * Spec-compatible response shape. The composed proof's root diverges from
93
+ * `chain_getHeader(at).state_root` once local overrides are applied (see `buildReadProof`);
94
+ * callers that need the recomputed root should use chopsticks's `dev_getReadProof`.
95
+ * Child-storage keys are rejected — use `state_getChildReadProof`.
96
+ */ export const state_getReadProof = async (context, [keys, hash])=>{
97
+ const { at, proof } = await buildReadProof(context, keys, hash);
98
+ return {
99
+ at,
100
+ proof
101
+ };
102
+ };
85
103
  /**
86
104
  * @return subscription id
87
105
  */ export const state_subscribeRuntimeVersion = async (context, _params, { subscribe })=>{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acala-network/chopsticks-core",
3
- "version": "1.4.2",
3
+ "version": "1.5.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.4.2",
17
+ "@acala-network/chopsticks-executor": "1.5.0",
18
18
  "@polkadot/rpc-provider": "^16.4.1",
19
19
  "@polkadot/types": "^16.4.1",
20
20
  "@polkadot/types-codec": "^16.4.1",