@polkadot-api/forklift 0.1.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.
Files changed (63) hide show
  1. package/README.md +394 -0
  2. package/bin/cli.js +388 -0
  3. package/bin/cli.js.map +1 -0
  4. package/dist/.papi/descriptors/dist/descriptors-CVixQzDI.js +27 -0
  5. package/dist/.papi/descriptors/dist/descriptors-CVixQzDI.js.map +1 -0
  6. package/dist/.papi/descriptors/dist/index.js +40 -0
  7. package/dist/.papi/descriptors/dist/index.js.map +1 -0
  8. package/dist/.papi/descriptors/dist/metadataTypes-OmVFeQs5.js +4 -0
  9. package/dist/.papi/descriptors/dist/metadataTypes-OmVFeQs5.js.map +1 -0
  10. package/dist/.papi/descriptors/dist/parachain_metadata-CQQZadL1.js +4 -0
  11. package/dist/.papi/descriptors/dist/parachain_metadata-CQQZadL1.js.map +1 -0
  12. package/dist/.papi/descriptors/dist/relay_metadata-BAI7pjXf.js +4 -0
  13. package/dist/.papi/descriptors/dist/relay_metadata-BAI7pjXf.js.map +1 -0
  14. package/dist/index.d.ts +64 -0
  15. package/dist/src/block-builder/create-block.js +232 -0
  16. package/dist/src/block-builder/create-block.js.map +1 -0
  17. package/dist/src/block-builder/para-enter.js +21 -0
  18. package/dist/src/block-builder/para-enter.js.map +1 -0
  19. package/dist/src/block-builder/set-validation-data.js +284 -0
  20. package/dist/src/block-builder/set-validation-data.js.map +1 -0
  21. package/dist/src/block-builder/slot-utils.js +68 -0
  22. package/dist/src/block-builder/slot-utils.js.map +1 -0
  23. package/dist/src/block-builder/timestamp.js +20 -0
  24. package/dist/src/block-builder/timestamp.js.map +1 -0
  25. package/dist/src/chain.js +334 -0
  26. package/dist/src/chain.js.map +1 -0
  27. package/dist/src/codecs.js +103 -0
  28. package/dist/src/codecs.js.map +1 -0
  29. package/dist/src/executor.js +87 -0
  30. package/dist/src/executor.js.map +1 -0
  31. package/dist/src/forklift.js +177 -0
  32. package/dist/src/forklift.js.map +1 -0
  33. package/dist/src/index.js +3 -0
  34. package/dist/src/index.js.map +1 -0
  35. package/dist/src/logger.js +11 -0
  36. package/dist/src/logger.js.map +1 -0
  37. package/dist/src/prequeries.js +19 -0
  38. package/dist/src/prequeries.js.map +1 -0
  39. package/dist/src/rpc/archive_v1.js +223 -0
  40. package/dist/src/rpc/archive_v1.js.map +1 -0
  41. package/dist/src/rpc/chainHead_v1.js +383 -0
  42. package/dist/src/rpc/chainHead_v1.js.map +1 -0
  43. package/dist/src/rpc/chainSpec_v1.js +14 -0
  44. package/dist/src/rpc/chainSpec_v1.js.map +1 -0
  45. package/dist/src/rpc/dev.js +32 -0
  46. package/dist/src/rpc/dev.js.map +1 -0
  47. package/dist/src/rpc/forklift_xcm.js +99 -0
  48. package/dist/src/rpc/forklift_xcm.js.map +1 -0
  49. package/dist/src/rpc/rpc_utils.js +20 -0
  50. package/dist/src/rpc/rpc_utils.js.map +1 -0
  51. package/dist/src/rpc/transaction_v1.js +13 -0
  52. package/dist/src/rpc/transaction_v1.js.map +1 -0
  53. package/dist/src/serve.js +88 -0
  54. package/dist/src/serve.js.map +1 -0
  55. package/dist/src/source.js +125 -0
  56. package/dist/src/source.js.map +1 -0
  57. package/dist/src/storage.js +223 -0
  58. package/dist/src/storage.js.map +1 -0
  59. package/dist/src/txPool.js +177 -0
  60. package/dist/src/txPool.js.map +1 -0
  61. package/dist/src/xcm.js +292 -0
  62. package/dist/src/xcm.js.map +1 -0
  63. package/package.json +61 -0
package/README.md ADDED
@@ -0,0 +1,394 @@
1
+ # forklift
2
+
3
+ A tool for forking live Substrate/Polkadot-SDK chains locally, built natively on [polkadot-api](https://github.com/polkadot-api/polkadot-api).
4
+
5
+ Forklift is inspired by [@acala-network/chopsticks](https://github.com/AcalaNetwork/chopsticks), but it was built primarily for testing workflows that need multiple live branches of the chain, making it possible to simulate forks, reorgs, and pruned branches.
6
+
7
+ ## Features
8
+
9
+ - Multiple live branches of the chain, including competing forks, reorgs, and pruned branches
10
+ - Immutable merkle-trie-backed storage with structural sharing between blocks
11
+ - Relay / parachain wiring helpers for local XCM testing
12
+ - Native `polkadot-api` implementation without `polkadot-js`
13
+ - Based on the new chainHead_v1 / archive_v1 JSON-RPC methods
14
+ - YAML-based CLI config for single-chain and multi-chain setups
15
+
16
+ ## Installation
17
+
18
+ ```sh
19
+ pnpm i @polkadot-api/forklift
20
+ ```
21
+
22
+ Then run:
23
+
24
+ ```sh
25
+ pnpm forklift --help
26
+ ```
27
+
28
+ ## CLI
29
+
30
+ Forklift can be started in two ways:
31
+
32
+ 1. Directly from a remote endpoint
33
+ 2. From a YAML config file
34
+
35
+ ### Direct mode
36
+
37
+ ```sh
38
+ forklift <url> [options]
39
+ ```
40
+
41
+ Arguments:
42
+
43
+ | Argument | Description |
44
+ | -------- | --------------------------------- |
45
+ | `url` | WebSocket URL of the node to fork |
46
+
47
+ Options:
48
+
49
+ | Option | Description | Default |
50
+ | ------------------------- | ------------------------------------------------------------- | ---------------- |
51
+ | `-b, --block <block>` | Block number or block hash to fork from | latest finalized |
52
+ | `-p, --port <port>` | Preferred local WebSocket port | `3000` |
53
+ | `-c, --config <file>` | Load a YAML config instead of using direct mode | |
54
+ | `-l, --log-level <level>` | Log level: `trace`, `debug`, `info`, `warn`, `error`, `fatal` | `info` |
55
+
56
+ Examples:
57
+
58
+ ```sh
59
+ # Fork the latest finalized block
60
+ forklift wss://rpc.polkadot.io
61
+
62
+ # Fork a specific block number
63
+ forklift wss://rpc.polkadot.io --block 22000000
64
+
65
+ # Fork a specific block hash
66
+ forklift wss://rpc.polkadot.io --block 0xabc123...
67
+
68
+ # Prefer a specific local port
69
+ forklift wss://rpc.polkadot.io --port 9000
70
+ ```
71
+
72
+ The forklift CLI exposes a JSON-RPC WebSocket endpoint. In direct mode that is typically:
73
+
74
+ ```txt
75
+ ws://localhost:3000
76
+ ```
77
+
78
+ If the requested port is already in use, forklift will try the next free port.
79
+
80
+ ## YAML Config
81
+
82
+ For anything beyond a single fork, the YAML config is the intended interface.
83
+
84
+ ```sh
85
+ forklift --config forklift.yml
86
+ ```
87
+
88
+ The config supports either:
89
+
90
+ - a single chain at the root level
91
+ - multiple named chains under `chains:`
92
+
93
+ ### Single-chain config
94
+
95
+ ```yaml
96
+ endpoint: wss://rpc.polkadot.io
97
+ block: 22000000
98
+ port: 3000
99
+ options:
100
+ buildBlockMode:
101
+ timer: 100
102
+ finalizeMode:
103
+ timer: 2000
104
+ storage:
105
+ - key: 0x3a636f6465
106
+ value: null
107
+ ```
108
+
109
+ ### Multi-chain config
110
+
111
+ ```yaml
112
+ chains:
113
+ relay:
114
+ endpoint: wss://rpc.polkadot.io
115
+ port: 3000
116
+
117
+ assetHub:
118
+ endpoint: wss://sys.ibp.network/asset-hub-polkadot
119
+ port: 3001
120
+ parachainOf: relay
121
+
122
+ bridgeHub:
123
+ endpoint: wss://sys.ibp.network/bridge-hub-polkadot
124
+ port: 3002
125
+ parachainOf: relay
126
+ ```
127
+
128
+ In multi-chain mode:
129
+
130
+ - each entry under `chains:` starts its own local fork
131
+ - `parachainOf: <name>` declares that a chain should be attached to another local chain as its relay
132
+ - chains that share the same relay are also attached to each other as siblings
133
+
134
+ That makes the config suitable for relay/parachain and parachain/parachain XCM testing setups.
135
+
136
+ ## Config Fields
137
+
138
+ Each chain config supports the following fields:
139
+
140
+ | Field | Type | Description |
141
+ | ------------- | -------- | ----------------------------------------------- | --------------------------------------------------- |
142
+ | `endpoint` | `string | string[]` | Remote WebSocket endpoint or endpoints to fork from |
143
+ | `block` | `number | string` | Optional block number or block hash to fork from |
144
+ | `port` | `number` | Preferred local WebSocket port |
145
+ | `parachainOf` | `string` | Name of the relay chain in a multi-chain config |
146
+ | `options` | `object` | Forklift runtime options |
147
+ | `storage` | `array` | Storage overrides applied after startup |
148
+
149
+ ### `options`
150
+
151
+ `options` maps closely to the programmatic `ForkliftOptions`.
152
+
153
+ ```yaml
154
+ options:
155
+ disableOnIdle: false
156
+ buildBlockMode:
157
+ timer: 100
158
+ finalizeMode:
159
+ timer: 2000
160
+ ```
161
+
162
+ Supported values:
163
+
164
+ - `disableOnIdle: boolean`
165
+ Disables `on_idle` hooks during block production. Some runtimes might perform actions that take a long time as they perform multiple serial storage queries. Setting this option to `true` disables that hook, which can increase the speed blocks can be produced.
166
+
167
+ - `buildBlockMode`
168
+ Controls when new blocks are built after transactions arrive.
169
+
170
+ Manual mode:
171
+
172
+ ```yaml
173
+ buildBlockMode: manual
174
+ ```
175
+
176
+ Timer mode:
177
+
178
+ ```yaml
179
+ buildBlockMode:
180
+ timer: 100
181
+ ```
182
+
183
+ - `finalizeMode`
184
+ Controls when built blocks are finalized.
185
+
186
+ Manual mode:
187
+
188
+ ```yaml
189
+ finalizeMode: manual
190
+ ```
191
+
192
+ Timer mode:
193
+
194
+ ```yaml
195
+ finalizeMode:
196
+ timer: 2000
197
+ ```
198
+
199
+ Notes:
200
+
201
+ - `manual` means forklift only changes state when you explicitly drive it
202
+ - `{ timer: 0 }` is allowed and means immediate scheduling
203
+ - if `port` is omitted, forklift will choose a free port automatically
204
+
205
+ ## Storage Overrides
206
+
207
+ The `storage` section is applied after the local server has started and the initial block is available.
208
+
209
+ Forklift supports two storage override forms.
210
+
211
+ ### Raw form
212
+
213
+ Use raw SCALE-encoded keys and values directly:
214
+
215
+ ```yaml
216
+ storage:
217
+ - key: 0x1234...
218
+ value: 0xabcd...
219
+ - key: 0x5678...
220
+ value: null
221
+ ```
222
+
223
+ Use `null` to delete or clear a storage entry.
224
+
225
+ ### Decoded form
226
+
227
+ Use pallet / storage names and let the CLI encode the key and value from metadata:
228
+
229
+ ```yaml
230
+ storage:
231
+ - pallet: System
232
+ entry: Account
233
+ key:
234
+ - 14GjNs7Lw7nVbJrL8aL8m8m4vY2mQ2L9mQf8u2YpK9nQx7aD
235
+ value:
236
+ providers: 1
237
+ consumers: 0
238
+ sufficients: 0
239
+ data:
240
+ free: 100_0_000_000_000n
241
+ reserved: 0n
242
+ frozen: 0n
243
+ flags: 0
244
+ ```
245
+
246
+ Notes:
247
+
248
+ - `key` must be an array in decoded form, even if the storage entry takes a single key
249
+ - big integers can be written as strings ending in `n`, for example `1000000000000n`
250
+ - underscores are accepted in numeric strings for readability
251
+ - if a storage item, key, or value cannot be encoded against the chain metadata, forklift logs the error and skips that override
252
+
253
+ ## Programmatic API
254
+
255
+ You can also create a chain from code:
256
+
257
+ ```ts
258
+ import { forklift } from "@polkadot-api/forklift";
259
+ import { Enum } from "polkadot-api";
260
+
261
+ const polkadot = forklift(
262
+ {
263
+ type: "remote",
264
+ value: {
265
+ url: "wss://rpc.polkadot.io",
266
+ atBlock: 22000000,
267
+ },
268
+ },
269
+ {
270
+ buildBlockMode: Enum("timer", 100),
271
+ finalizeMode: Enum("timer", 2000),
272
+ disableOnIdle: false,
273
+ }
274
+ );
275
+ ```
276
+
277
+ The forklift instance then has a property `serve` which is a `JsonRpcProvider` - This is [an unopinionated](https://papi.how/providers/enhancers#enhancers) interface that serves JSON-RPC connections, and can be plugged directly into polkadot-api:
278
+
279
+ ```ts
280
+ import { forklift } from "@polkadot-api/forklift";
281
+ import { createClient } from "polkadot-api";
282
+
283
+ const polkadot = forklift(/* … */);
284
+ const client = createClient(polkadot.serve);
285
+ ```
286
+
287
+ Or, given it's a simple interface, it's simple to expose that to a WS. For instance, using bun:
288
+
289
+ ```ts
290
+ import { forklift } from "@polkadot-api/forklift";
291
+ const polkadot = forklift(/* … */);
292
+
293
+ Bun.serve({
294
+ fetch(req, server) {
295
+ // Al WS connections start with a HTTP request, we tell bun to upgrade the connection to a WS
296
+ const success = server.upgrade(req, { data: {} });
297
+ if (success) {
298
+ return undefined;
299
+ }
300
+
301
+ // handle HTTP request normally
302
+ return new Response("Nothing to see here, move along");
303
+ },
304
+ websocket: {
305
+ data: {} as any,
306
+ open(ws) {
307
+ // When the WS opens we call the JsonRpcProvider to open a connection, and wire up incoming messages from forklift to send them out to the WS
308
+ ws.data.connection = forklift.serve((msg) =>
309
+ ws.send(JSON.stringify(msg))
310
+ );
311
+ },
312
+ close(ws) {
313
+ // When it closes we just close the connection
314
+ ws.data.connection.disconnect();
315
+ },
316
+ async message(ws, message) {
317
+ // When we receive a message we just pass it down to forklift
318
+ ws.data.connection.send(JSON.parse(message as string));
319
+ },
320
+ },
321
+ });
322
+ ```
323
+
324
+ ### `Forklift` interface
325
+
326
+ ```ts
327
+ interface Forklift {
328
+ serve: JsonRpcProvider;
329
+
330
+ newBlock(opts?: Partial<NewBlockOptions>): Promise<HexString>;
331
+ changeBest(hash: HexString): Promise<void>;
332
+ changeFinalized(hash: HexString): Promise<void>;
333
+ setStorage(
334
+ hash: HexString,
335
+ changes: Record<string, Uint8Array>
336
+ ): Promise<void>;
337
+ getStorageDiff(
338
+ hash: HexString,
339
+ baseHash?: HexString
340
+ ): Promise<
341
+ Record<string, { value: Uint8Array | null; prev?: Uint8Array | null }>
342
+ >;
343
+ changeOptions(opts: Partial<ForkliftOptions>): void;
344
+ destroy(): void;
345
+ }
346
+ ```
347
+
348
+ ### Block production modes
349
+
350
+ `buildBlockMode` controls when new blocks are produced:
351
+
352
+ - `Enum("manual")`: only explicit `newBlock()` calls produce blocks
353
+ - `Enum("timer", ms)`: automatically produce a block after a transaction arrives
354
+
355
+ `finalizeMode` controls when blocks are finalized:
356
+
357
+ - `Enum("manual")`: only explicit `changeFinalized()` calls finalize blocks
358
+ - `Enum("timer", ms)`: automatically finalize a block after it is built
359
+
360
+ ### Producing forks
361
+
362
+ Pass a `parent` hash to branch from any existing block:
363
+
364
+ ```ts
365
+ const base = await f.newBlock();
366
+
367
+ const forkA = await f.newBlock({ parent: base, type: "fork" });
368
+ const forkB = await f.newBlock({ parent: base, type: "fork" });
369
+ ```
370
+
371
+ ### Storage overrides from code
372
+
373
+ ```ts
374
+ await f.setStorage(hash, {
375
+ "0x...key": new Uint8Array([...value]),
376
+ });
377
+
378
+ const diff = await f.getStorageDiff(hash);
379
+ ```
380
+
381
+ ## JSON-RPC Surface
382
+
383
+ Forklift serves a WebSocket JSON-RPC endpoint and currently includes methods in these groups:
384
+
385
+ - `archive_v1_*`
386
+ - `chainHead_v1_*`
387
+ - `chainSpec_v1_*`
388
+ - `transaction_v1_*`
389
+ - `dev_*`
390
+ - `forklift_xcm_*`
391
+
392
+ ## Acknowledgements
393
+
394
+ Forklift is heavily inspired by [@acala-network/chopsticks](https://github.com/AcalaNetwork/chopsticks) and reuses its WASM executor package, [`@acala-network/chopsticks-executor`](https://github.com/AcalaNetwork/chopsticks), for local runtime execution.