@acala-network/chopsticks 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.
- package/README.md +52 -0
- package/chopsticks.js +2 -0
- package/dist/api.d.ts +43 -0
- package/dist/api.js +78 -0
- package/dist/blockchain/block.d.ts +38 -0
- package/dist/blockchain/block.js +178 -0
- package/dist/blockchain/head-state.d.ts +13 -0
- package/dist/blockchain/head-state.js +57 -0
- package/dist/blockchain/index.d.ts +35 -0
- package/dist/blockchain/index.js +115 -0
- package/dist/blockchain/inherents.d.ts +26 -0
- package/dist/blockchain/inherents.js +96 -0
- package/dist/blockchain/storage-layer.d.ts +32 -0
- package/dist/blockchain/storage-layer.js +168 -0
- package/dist/blockchain/txpool.d.ts +13 -0
- package/dist/blockchain/txpool.js +165 -0
- package/dist/db/entities.d.ts +5 -0
- package/dist/db/entities.js +34 -0
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.js +41 -0
- package/dist/executor.d.ts +16 -0
- package/dist/executor.js +25 -0
- package/dist/executor.test.d.ts +1 -0
- package/dist/executor.test.js +58 -0
- package/dist/genesis-provider.d.ts +42 -0
- package/dist/genesis-provider.js +141 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +208 -0
- package/dist/logger.d.ts +7 -0
- package/dist/logger.js +25 -0
- package/dist/rpc/dev.d.ts +3 -0
- package/dist/rpc/dev.js +33 -0
- package/dist/rpc/exec.d.ts +3 -0
- package/dist/rpc/exec.js +44 -0
- package/dist/rpc/index.d.ts +5 -0
- package/dist/rpc/index.js +25 -0
- package/dist/rpc/shared.d.ts +30 -0
- package/dist/rpc/shared.js +20 -0
- package/dist/rpc/substrate/author.d.ts +3 -0
- package/dist/rpc/substrate/author.js +42 -0
- package/dist/rpc/substrate/chain.d.ts +5 -0
- package/dist/rpc/substrate/chain.js +62 -0
- package/dist/rpc/substrate/index.d.ts +3 -0
- package/dist/rpc/substrate/index.js +20 -0
- package/dist/rpc/substrate/state.d.ts +3 -0
- package/dist/rpc/substrate/state.js +80 -0
- package/dist/rpc/substrate/system.d.ts +3 -0
- package/dist/rpc/substrate/system.js +27 -0
- package/dist/schema/index.d.ts +183 -0
- package/dist/schema/index.js +29 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +148 -0
- package/dist/task.d.ts +38 -0
- package/dist/task.js +66 -0
- package/dist/utils/import-storage.d.ts +4 -0
- package/dist/utils/import-storage.js +43 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.js +32 -0
- package/dist/utils/set-storage.d.ts +6 -0
- package/dist/utils/set-storage.js +57 -0
- package/package.json +91 -0
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Chopsticks
|
|
2
|
+
|
|
3
|
+
Create parallel reality of your Substrate network.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Make sure you have setup Rust environment (>= 1.64).
|
|
8
|
+
|
|
9
|
+
- Clone repository with submodules ([smoldot](https://github.com/paritytech/smoldot))
|
|
10
|
+
- `git clone --recurse-submodules https://github.com/AcalaNetwork/chopsticks.git && cd chopsticks`
|
|
11
|
+
- Install deps
|
|
12
|
+
- `yarn`
|
|
13
|
+
- Build wasm
|
|
14
|
+
- `yarn build-wasm`
|
|
15
|
+
|
|
16
|
+
## Run
|
|
17
|
+
|
|
18
|
+
- Replay latest block
|
|
19
|
+
- `yarn start run-block --endpoint=wss://acala-rpc-2.aca-api.network/ws`
|
|
20
|
+
- This will replay the last block and print out the changed storages
|
|
21
|
+
- Use option `--output-path=<file_path>` to print out JSON file
|
|
22
|
+
- Run a test node
|
|
23
|
+
- `yarn start dev --endpoint=wss://acala-rpc-2.aca-api.network/ws`
|
|
24
|
+
- You have a test node running at `ws://localhost:8000`
|
|
25
|
+
- You can use [Polkadot.js Apps](https://polkadot.js.org/apps/) to connect to this node
|
|
26
|
+
- Submit any transaction to produce a new block in the in parallel reality
|
|
27
|
+
- (Optional) Pre-define/override storage using option `--import-storage=storage.[json/yaml]`. See example storage below.
|
|
28
|
+
```json5
|
|
29
|
+
// prettier-ignore
|
|
30
|
+
{
|
|
31
|
+
"Sudo": {
|
|
32
|
+
"Key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
|
|
33
|
+
},
|
|
34
|
+
"TechnicalCommittee": {
|
|
35
|
+
"Members": ["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"]
|
|
36
|
+
},
|
|
37
|
+
"Tokens": {
|
|
38
|
+
"Accounts": [
|
|
39
|
+
[
|
|
40
|
+
["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", { "token": "KAR" }],
|
|
41
|
+
{
|
|
42
|
+
"free": 1000000000000000,
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
- Run Kusama fork
|
|
50
|
+
- Edit configs/kusama.yml if needed. (e.g. update the block number)
|
|
51
|
+
- `yarn start dev --config=configs/kusama.yml`
|
|
52
|
+
|
package/chopsticks.js
ADDED
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ExtDef } from '@polkadot/types/extrinsic/signedExtensions/types';
|
|
2
|
+
import { HexString } from '@polkadot/util/types';
|
|
3
|
+
import { ProviderInterface } from '@polkadot/rpc-provider/types';
|
|
4
|
+
type ChainProperties = {
|
|
5
|
+
ss58Format?: number;
|
|
6
|
+
tokenDecimals?: number[];
|
|
7
|
+
tokenSymbol?: string[];
|
|
8
|
+
};
|
|
9
|
+
type Header = {
|
|
10
|
+
parentHash: HexString;
|
|
11
|
+
number: HexString;
|
|
12
|
+
stateRoot: HexString;
|
|
13
|
+
extrinsicsRoot: HexString;
|
|
14
|
+
digest: {
|
|
15
|
+
logs: HexString[];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
type SignedBlock = {
|
|
19
|
+
block: {
|
|
20
|
+
header: Header;
|
|
21
|
+
extrinsics: HexString[];
|
|
22
|
+
};
|
|
23
|
+
justifications?: HexString[];
|
|
24
|
+
};
|
|
25
|
+
export declare class Api {
|
|
26
|
+
#private;
|
|
27
|
+
readonly signedExtensions: ExtDef;
|
|
28
|
+
constructor(provider: ProviderInterface, signedExtensions?: ExtDef);
|
|
29
|
+
disconnect(): Promise<void>;
|
|
30
|
+
get isReady(): Promise<void>;
|
|
31
|
+
get chain(): Promise<string>;
|
|
32
|
+
get chainProperties(): Promise<ChainProperties>;
|
|
33
|
+
getSystemName(): Promise<string>;
|
|
34
|
+
getSystemProperties(): Promise<ChainProperties>;
|
|
35
|
+
getSystemChain(): Promise<string>;
|
|
36
|
+
getMetadata(hash?: string): Promise<string>;
|
|
37
|
+
getBlockHash(blockNumber?: number): Promise<string>;
|
|
38
|
+
getHeader(hash?: string): Promise<Header>;
|
|
39
|
+
getBlock(hash?: string): Promise<SignedBlock>;
|
|
40
|
+
getStorage(key: string, hash?: string): Promise<string>;
|
|
41
|
+
getKeysPaged(prefix: string, pageSize: number, startKey: string, hash?: string): Promise<string[]>;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Api = void 0;
|
|
4
|
+
class Api {
|
|
5
|
+
#provider;
|
|
6
|
+
#isReady;
|
|
7
|
+
#chain;
|
|
8
|
+
#chainProperties;
|
|
9
|
+
signedExtensions;
|
|
10
|
+
constructor(provider, signedExtensions) {
|
|
11
|
+
this.#provider = provider;
|
|
12
|
+
this.signedExtensions = signedExtensions || {};
|
|
13
|
+
this.#isReady = new Promise((resolve, reject) => {
|
|
14
|
+
if (this.#provider.isConnected) {
|
|
15
|
+
setTimeout(resolve, 500);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
this.#provider.on('connected', () => {
|
|
19
|
+
setTimeout(resolve, 500);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
this.#provider.on('error', reject);
|
|
23
|
+
});
|
|
24
|
+
this.#provider.on('disconnected', () => {
|
|
25
|
+
// TODO: reconnect
|
|
26
|
+
console.warn('Api disconnected');
|
|
27
|
+
});
|
|
28
|
+
this.#chain = this.#isReady.then(() => this.getSystemChain());
|
|
29
|
+
this.#chainProperties = this.#isReady.then(() => this.getSystemProperties());
|
|
30
|
+
this.#provider.connect();
|
|
31
|
+
}
|
|
32
|
+
async disconnect() {
|
|
33
|
+
return this.#provider.disconnect();
|
|
34
|
+
}
|
|
35
|
+
get isReady() {
|
|
36
|
+
return this.#isReady;
|
|
37
|
+
}
|
|
38
|
+
get chain() {
|
|
39
|
+
return this.#chain;
|
|
40
|
+
}
|
|
41
|
+
get chainProperties() {
|
|
42
|
+
return this.#chainProperties;
|
|
43
|
+
}
|
|
44
|
+
async getSystemName() {
|
|
45
|
+
return this.#provider.send('system_name', []);
|
|
46
|
+
}
|
|
47
|
+
async getSystemProperties() {
|
|
48
|
+
return this.#provider.send('system_properties', []);
|
|
49
|
+
}
|
|
50
|
+
async getSystemChain() {
|
|
51
|
+
return this.#provider.send('system_chain', []);
|
|
52
|
+
}
|
|
53
|
+
async getMetadata(hash) {
|
|
54
|
+
return this.#provider.send('state_getMetadata', hash ? [hash] : []);
|
|
55
|
+
}
|
|
56
|
+
async getBlockHash(blockNumber) {
|
|
57
|
+
return this.#provider.send('chain_getBlockHash', Number.isInteger(blockNumber) ? [blockNumber] : []);
|
|
58
|
+
}
|
|
59
|
+
async getHeader(hash) {
|
|
60
|
+
return this.#provider.send('chain_getHeader', hash ? [hash] : []);
|
|
61
|
+
}
|
|
62
|
+
async getBlock(hash) {
|
|
63
|
+
return this.#provider.send('chain_getBlock', hash ? [hash] : []);
|
|
64
|
+
}
|
|
65
|
+
async getStorage(key, hash) {
|
|
66
|
+
if (hash) {
|
|
67
|
+
return this.#provider.send('state_getStorageAt', [key, hash]);
|
|
68
|
+
}
|
|
69
|
+
return this.#provider.send('state_getStorage', [key]);
|
|
70
|
+
}
|
|
71
|
+
async getKeysPaged(prefix, pageSize, startKey, hash) {
|
|
72
|
+
if (hash) {
|
|
73
|
+
return this.#provider.send('state_getKeysPagedAt', [prefix, pageSize, startKey, hash]);
|
|
74
|
+
}
|
|
75
|
+
return this.#provider.send('state_getKeysPaged', [prefix, pageSize, startKey]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.Api = Api;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Header } from '@polkadot/types/interfaces';
|
|
2
|
+
import { DecoratedMeta } from '@polkadot/types/metadata/decorate/types';
|
|
3
|
+
import { TypeRegistry } from '@polkadot/types';
|
|
4
|
+
import type { HexString } from '@polkadot/util/types';
|
|
5
|
+
import { Blockchain } from '.';
|
|
6
|
+
import { StorageLayer, StorageLayerProvider } from './storage-layer';
|
|
7
|
+
import { TaskResponseCall } from '../task';
|
|
8
|
+
import type { RuntimeVersion } from '../executor';
|
|
9
|
+
export declare class Block {
|
|
10
|
+
#private;
|
|
11
|
+
readonly number: number;
|
|
12
|
+
readonly hash: string;
|
|
13
|
+
constructor(chain: Blockchain, number: number, hash: string, parentBlock?: Block, block?: {
|
|
14
|
+
header: Header;
|
|
15
|
+
extrinsics: string[];
|
|
16
|
+
storage?: StorageLayerProvider;
|
|
17
|
+
});
|
|
18
|
+
get header(): Header | Promise<Header>;
|
|
19
|
+
get extrinsics(): string[] | Promise<string[]>;
|
|
20
|
+
get parentBlock(): undefined | Block | Promise<Block | undefined>;
|
|
21
|
+
get storage(): StorageLayerProvider;
|
|
22
|
+
get(key: string): Promise<string | undefined>;
|
|
23
|
+
getKeysPaged(options: {
|
|
24
|
+
prefix?: string;
|
|
25
|
+
startKey?: string;
|
|
26
|
+
pageSize: number;
|
|
27
|
+
}): Promise<string[]>;
|
|
28
|
+
pushStorageLayer(): StorageLayer;
|
|
29
|
+
popStorageLayer(): void;
|
|
30
|
+
storageDiff(): Promise<Record<string, string>>;
|
|
31
|
+
get wasm(): Promise<`0x${string}`>;
|
|
32
|
+
setWasm(wasm: HexString): void;
|
|
33
|
+
get registry(): Promise<TypeRegistry>;
|
|
34
|
+
get runtimeVersion(): Promise<RuntimeVersion>;
|
|
35
|
+
get metadata(): Promise<HexString>;
|
|
36
|
+
get meta(): Promise<DecoratedMeta>;
|
|
37
|
+
call(method: string, args: string): Promise<TaskResponseCall['Call']>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Block = void 0;
|
|
4
|
+
const types_1 = require("@polkadot/types");
|
|
5
|
+
const metadata_1 = require("@polkadot/types/metadata");
|
|
6
|
+
const util_1 = require("@polkadot/types-known/util");
|
|
7
|
+
const util_2 = require("@polkadot/util");
|
|
8
|
+
const storage_layer_1 = require("./storage-layer");
|
|
9
|
+
const shared_1 = require("../rpc/shared");
|
|
10
|
+
const executor_1 = require("../executor");
|
|
11
|
+
class Block {
|
|
12
|
+
number;
|
|
13
|
+
hash;
|
|
14
|
+
#chain;
|
|
15
|
+
#header;
|
|
16
|
+
#parentBlock;
|
|
17
|
+
#extrinsics;
|
|
18
|
+
#wasm;
|
|
19
|
+
#runtimeVersion;
|
|
20
|
+
#metadata;
|
|
21
|
+
#registry;
|
|
22
|
+
#meta;
|
|
23
|
+
#baseStorage;
|
|
24
|
+
#storages;
|
|
25
|
+
constructor(chain, number, hash, parentBlock, block) {
|
|
26
|
+
this.number = number;
|
|
27
|
+
this.hash = hash;
|
|
28
|
+
this.#chain = chain;
|
|
29
|
+
this.#parentBlock = parentBlock;
|
|
30
|
+
this.#header = block?.header;
|
|
31
|
+
this.#extrinsics = block?.extrinsics;
|
|
32
|
+
this.#baseStorage = block?.storage ?? new storage_layer_1.RemoteStorageLayer(chain.api, hash, chain.db);
|
|
33
|
+
this.#storages = [];
|
|
34
|
+
this.#registry = parentBlock?.registry;
|
|
35
|
+
}
|
|
36
|
+
get header() {
|
|
37
|
+
if (!this.#header) {
|
|
38
|
+
this.#header = Promise.all([this.registry, this.#chain.api.getHeader(this.hash)]).then(([registry, header]) => registry.createType('Header', header));
|
|
39
|
+
}
|
|
40
|
+
return this.#header;
|
|
41
|
+
}
|
|
42
|
+
get extrinsics() {
|
|
43
|
+
if (!this.#extrinsics) {
|
|
44
|
+
this.#extrinsics = this.#chain.api.getBlock(this.hash).then((b) => b.block.extrinsics);
|
|
45
|
+
}
|
|
46
|
+
return this.#extrinsics;
|
|
47
|
+
}
|
|
48
|
+
get parentBlock() {
|
|
49
|
+
if (this.number === 0) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
if (!this.#parentBlock) {
|
|
53
|
+
this.#parentBlock = Promise.resolve(this.header).then((h) => this.#chain.getBlock(h.parentHash.toHex()));
|
|
54
|
+
}
|
|
55
|
+
return this.#parentBlock;
|
|
56
|
+
}
|
|
57
|
+
get storage() {
|
|
58
|
+
return this.#storages[this.#storages.length - 1] ?? this.#baseStorage;
|
|
59
|
+
}
|
|
60
|
+
async get(key) {
|
|
61
|
+
const val = await this.storage.get(key, true);
|
|
62
|
+
switch (val) {
|
|
63
|
+
case "Deleted" /* StorageValueKind.Deleted */:
|
|
64
|
+
return undefined;
|
|
65
|
+
default:
|
|
66
|
+
return val;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async getKeysPaged(options) {
|
|
70
|
+
const layer = new storage_layer_1.StorageLayer(this.storage);
|
|
71
|
+
await layer.fold();
|
|
72
|
+
const prefix = options.prefix ?? '0x';
|
|
73
|
+
const startKey = options.startKey ?? prefix;
|
|
74
|
+
const pageSize = options.pageSize;
|
|
75
|
+
return layer.getKeysPaged(prefix, pageSize, startKey);
|
|
76
|
+
}
|
|
77
|
+
pushStorageLayer() {
|
|
78
|
+
const layer = new storage_layer_1.StorageLayer(this.storage);
|
|
79
|
+
this.#storages.push(layer);
|
|
80
|
+
return layer;
|
|
81
|
+
}
|
|
82
|
+
popStorageLayer() {
|
|
83
|
+
this.#storages.pop();
|
|
84
|
+
}
|
|
85
|
+
async storageDiff() {
|
|
86
|
+
const storage = {};
|
|
87
|
+
for (const layer of this.#storages) {
|
|
88
|
+
await layer.mergeInto(storage);
|
|
89
|
+
}
|
|
90
|
+
return storage;
|
|
91
|
+
}
|
|
92
|
+
get wasm() {
|
|
93
|
+
const getWasm = async () => {
|
|
94
|
+
const wasmKey = (0, util_2.stringToHex)(':code');
|
|
95
|
+
const wasm = await this.get(wasmKey);
|
|
96
|
+
if (!wasm) {
|
|
97
|
+
throw new Error('No wasm found');
|
|
98
|
+
}
|
|
99
|
+
return wasm;
|
|
100
|
+
};
|
|
101
|
+
if (!this.#wasm) {
|
|
102
|
+
this.#wasm = getWasm();
|
|
103
|
+
}
|
|
104
|
+
return this.#wasm;
|
|
105
|
+
}
|
|
106
|
+
setWasm(wasm) {
|
|
107
|
+
const wasmKey = (0, util_2.stringToHex)(':code');
|
|
108
|
+
this.pushStorageLayer().set(wasmKey, wasm);
|
|
109
|
+
this.#wasm = Promise.resolve(wasm);
|
|
110
|
+
this.#runtimeVersion = undefined;
|
|
111
|
+
this.#registry = undefined;
|
|
112
|
+
this.#meta = undefined;
|
|
113
|
+
this.#metadata = undefined;
|
|
114
|
+
}
|
|
115
|
+
get registry() {
|
|
116
|
+
if (!this.#registry) {
|
|
117
|
+
this.#registry = Promise.all([
|
|
118
|
+
this.metadata,
|
|
119
|
+
this.#chain.api.chainProperties,
|
|
120
|
+
this.#chain.api.chain,
|
|
121
|
+
this.runtimeVersion,
|
|
122
|
+
]).then(([data, properties, chain, version]) => {
|
|
123
|
+
const registry = new types_1.TypeRegistry(this.hash);
|
|
124
|
+
registry.setChainProperties(registry.createType('ChainProperties', properties));
|
|
125
|
+
registry.register((0, util_1.getSpecTypes)(registry, chain, version.specName, version.specVersion));
|
|
126
|
+
registry.setHasher((0, util_1.getSpecHasher)(registry, chain, version.specName));
|
|
127
|
+
registry.setMetadata(new types_1.Metadata(registry, data), undefined, (0, util_2.objectSpread)({}, (0, util_1.getSpecExtensions)(registry, chain, version.specName), this.#chain.api.signedExtensions));
|
|
128
|
+
return registry;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return this.#registry;
|
|
132
|
+
}
|
|
133
|
+
get runtimeVersion() {
|
|
134
|
+
if (!this.#runtimeVersion) {
|
|
135
|
+
this.#runtimeVersion = this.wasm.then(executor_1.getRuntimeVersion);
|
|
136
|
+
}
|
|
137
|
+
return this.#runtimeVersion;
|
|
138
|
+
}
|
|
139
|
+
get metadata() {
|
|
140
|
+
if (!this.#metadata) {
|
|
141
|
+
this.#metadata = this.wasm.then(executor_1.getMetadata);
|
|
142
|
+
}
|
|
143
|
+
return this.#metadata;
|
|
144
|
+
}
|
|
145
|
+
get meta() {
|
|
146
|
+
if (!this.#meta) {
|
|
147
|
+
this.#meta = Promise.all([this.registry, this.metadata]).then(([registry, metadataStr]) => {
|
|
148
|
+
const metadata = new types_1.Metadata(registry, metadataStr);
|
|
149
|
+
return (0, metadata_1.expandMetadata)(registry, metadata);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return this.#meta;
|
|
153
|
+
}
|
|
154
|
+
async call(method, args) {
|
|
155
|
+
const wasm = await this.wasm;
|
|
156
|
+
const res = await new Promise((resolve, reject) => {
|
|
157
|
+
this.#chain.tasks.addAndRunTask({
|
|
158
|
+
Call: {
|
|
159
|
+
blockHash: this.hash,
|
|
160
|
+
wasm,
|
|
161
|
+
calls: [[method, args]],
|
|
162
|
+
},
|
|
163
|
+
}, (r) => {
|
|
164
|
+
if ('Call' in r) {
|
|
165
|
+
resolve(r.Call);
|
|
166
|
+
}
|
|
167
|
+
else if ('Error' in r) {
|
|
168
|
+
reject(new shared_1.ResponseError(1, r.Error));
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
reject(new shared_1.ResponseError(1, 'Unexpected response'));
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
return res;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
exports.Block = Block;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Block } from './block';
|
|
2
|
+
export declare const randomId: () => string;
|
|
3
|
+
export declare class HeadState {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(head: Block);
|
|
6
|
+
subscribeHead(cb: (block: Block) => void): string;
|
|
7
|
+
unsubscribeHead(id: string): void;
|
|
8
|
+
subscribeStorage(keys: string[], cb: (block: Block, pairs: [string, string][]) => void): Promise<string>;
|
|
9
|
+
unsubscribeStorage(id: string): void;
|
|
10
|
+
subscrubeRuntimeVersion(cb: (block: Block) => void): string;
|
|
11
|
+
unsubscribeRuntimeVersion(id: string): void;
|
|
12
|
+
setHead(head: Block): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HeadState = exports.randomId = void 0;
|
|
4
|
+
const randomId = () => Math.random().toString(36).substring(2);
|
|
5
|
+
exports.randomId = randomId;
|
|
6
|
+
class HeadState {
|
|
7
|
+
#headListeners = {};
|
|
8
|
+
#storageListeners = {};
|
|
9
|
+
#oldValues = {};
|
|
10
|
+
#head;
|
|
11
|
+
constructor(head) {
|
|
12
|
+
this.#head = head;
|
|
13
|
+
}
|
|
14
|
+
subscribeHead(cb) {
|
|
15
|
+
const id = (0, exports.randomId)();
|
|
16
|
+
this.#headListeners[id] = cb;
|
|
17
|
+
return id;
|
|
18
|
+
}
|
|
19
|
+
unsubscribeHead(id) {
|
|
20
|
+
delete this.#headListeners[id];
|
|
21
|
+
}
|
|
22
|
+
async subscribeStorage(keys, cb) {
|
|
23
|
+
const id = (0, exports.randomId)();
|
|
24
|
+
this.#storageListeners[id] = [keys, cb];
|
|
25
|
+
for (const key of keys) {
|
|
26
|
+
this.#oldValues[key] = await this.#head.get(key);
|
|
27
|
+
}
|
|
28
|
+
return id;
|
|
29
|
+
}
|
|
30
|
+
unsubscribeStorage(id) {
|
|
31
|
+
delete this.#storageListeners[id];
|
|
32
|
+
}
|
|
33
|
+
subscrubeRuntimeVersion(cb) {
|
|
34
|
+
// TODO: actually subscribe
|
|
35
|
+
void cb;
|
|
36
|
+
return (0, exports.randomId)();
|
|
37
|
+
}
|
|
38
|
+
unsubscribeRuntimeVersion(id) {
|
|
39
|
+
// TODO: actually unsubscribe
|
|
40
|
+
void id;
|
|
41
|
+
}
|
|
42
|
+
async setHead(head) {
|
|
43
|
+
this.#head = head;
|
|
44
|
+
for (const cb of Object.values(this.#headListeners)) {
|
|
45
|
+
cb(head);
|
|
46
|
+
}
|
|
47
|
+
const diff = await this.#head.storageDiff();
|
|
48
|
+
for (const [keys, cb] of Object.values(this.#storageListeners)) {
|
|
49
|
+
const changed = keys.filter((key) => diff[key]).map((key) => [key, diff[key]]);
|
|
50
|
+
if (changed.length > 0) {
|
|
51
|
+
cb(head, changed);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
Object.assign(this.#oldValues, diff);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.HeadState = HeadState;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DataSource } from 'typeorm';
|
|
2
|
+
import { Header } from '@polkadot/types/interfaces';
|
|
3
|
+
import { Api } from '../api';
|
|
4
|
+
import { Block } from './block';
|
|
5
|
+
import { BuildBlockMode } from './txpool';
|
|
6
|
+
import { HeadState } from './head-state';
|
|
7
|
+
import { InherentProvider } from './inherents';
|
|
8
|
+
import { TaskManager } from '../task';
|
|
9
|
+
export interface Options {
|
|
10
|
+
api: Api;
|
|
11
|
+
tasks: TaskManager;
|
|
12
|
+
buildBlockMode?: BuildBlockMode;
|
|
13
|
+
inherentProvider: InherentProvider;
|
|
14
|
+
db?: DataSource;
|
|
15
|
+
header: {
|
|
16
|
+
number: number;
|
|
17
|
+
hash: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare class Blockchain {
|
|
21
|
+
#private;
|
|
22
|
+
readonly api: Api;
|
|
23
|
+
readonly tasks: TaskManager;
|
|
24
|
+
readonly db: DataSource | undefined;
|
|
25
|
+
readonly headState: HeadState;
|
|
26
|
+
constructor({ api, tasks, buildBlockMode, inherentProvider, db, header }: Options);
|
|
27
|
+
get head(): Block;
|
|
28
|
+
getBlockAt(number?: number): Promise<Block | undefined>;
|
|
29
|
+
getBlock(hash?: string): Promise<Block | undefined>;
|
|
30
|
+
newTempBlock(parent: Block, header: Header): Block;
|
|
31
|
+
unregisterBlock(block: Block): void;
|
|
32
|
+
setHead(block: Block): void;
|
|
33
|
+
submitExtrinsic(extrinsic: string): Promise<string>;
|
|
34
|
+
newBlock(): Promise<Block>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Blockchain = void 0;
|
|
4
|
+
const util_crypto_1 = require("@polkadot/util-crypto");
|
|
5
|
+
const util_1 = require("@polkadot/util");
|
|
6
|
+
const block_1 = require("./block");
|
|
7
|
+
const txpool_1 = require("./txpool");
|
|
8
|
+
const head_state_1 = require("./head-state");
|
|
9
|
+
const shared_1 = require("../rpc/shared");
|
|
10
|
+
const logger_1 = require("../logger");
|
|
11
|
+
const logger = logger_1.defaultLogger.child({ name: 'blockchain' });
|
|
12
|
+
class Blockchain {
|
|
13
|
+
api;
|
|
14
|
+
tasks;
|
|
15
|
+
db;
|
|
16
|
+
#txpool;
|
|
17
|
+
#head;
|
|
18
|
+
#blocksByNumber = [];
|
|
19
|
+
#blocksByHash = {};
|
|
20
|
+
headState;
|
|
21
|
+
constructor({ api, tasks, buildBlockMode, inherentProvider, db, header }) {
|
|
22
|
+
this.api = api;
|
|
23
|
+
this.tasks = tasks;
|
|
24
|
+
this.db = db;
|
|
25
|
+
this.#head = new block_1.Block(this, header.number, header.hash);
|
|
26
|
+
this.#registerBlock(this.#head);
|
|
27
|
+
this.#txpool = new txpool_1.TxPool(this, inherentProvider, buildBlockMode);
|
|
28
|
+
this.headState = new head_state_1.HeadState(this.#head);
|
|
29
|
+
}
|
|
30
|
+
#registerBlock(block) {
|
|
31
|
+
this.#blocksByNumber[block.number] = block;
|
|
32
|
+
this.#blocksByHash[block.hash] = block;
|
|
33
|
+
}
|
|
34
|
+
get head() {
|
|
35
|
+
return this.#head;
|
|
36
|
+
}
|
|
37
|
+
async getBlockAt(number) {
|
|
38
|
+
if (number === undefined) {
|
|
39
|
+
return this.head;
|
|
40
|
+
}
|
|
41
|
+
if (number > this.#head.number) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
if (!this.#blocksByNumber[number]) {
|
|
45
|
+
const hash = await this.api.getBlockHash(number);
|
|
46
|
+
const block = new block_1.Block(this, number, hash);
|
|
47
|
+
this.#registerBlock(block);
|
|
48
|
+
}
|
|
49
|
+
return this.#blocksByNumber[number];
|
|
50
|
+
}
|
|
51
|
+
async getBlock(hash) {
|
|
52
|
+
await this.api.isReady;
|
|
53
|
+
if (hash == null) {
|
|
54
|
+
hash = this.head.hash;
|
|
55
|
+
}
|
|
56
|
+
if (!this.#blocksByHash[hash]) {
|
|
57
|
+
try {
|
|
58
|
+
const registry = await this.head.registry;
|
|
59
|
+
const header = registry.createType('Header', await this.api.getHeader(hash));
|
|
60
|
+
const block = new block_1.Block(this, header.number.toNumber(), hash);
|
|
61
|
+
this.#registerBlock(block);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
logger.debug(`getBlock(${hash}) failed: ${e}`);
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return this.#blocksByHash[hash];
|
|
69
|
+
}
|
|
70
|
+
newTempBlock(parent, header) {
|
|
71
|
+
const number = parent.number + 1;
|
|
72
|
+
const hash = '0x' +
|
|
73
|
+
Math.round(Math.random() * 100000000)
|
|
74
|
+
.toString(16)
|
|
75
|
+
.padEnd(64, '0');
|
|
76
|
+
const block = new block_1.Block(this, number, hash, parent, { header, extrinsics: [], storage: parent.storage });
|
|
77
|
+
this.#blocksByHash[hash] = block;
|
|
78
|
+
return block;
|
|
79
|
+
}
|
|
80
|
+
unregisterBlock(block) {
|
|
81
|
+
if (block.hash === this.head.hash) {
|
|
82
|
+
throw new Error('Cannot unregister head block');
|
|
83
|
+
}
|
|
84
|
+
if (this.#blocksByNumber[block.number]?.hash === block.hash) {
|
|
85
|
+
delete this.#blocksByNumber[block.number];
|
|
86
|
+
}
|
|
87
|
+
delete this.#blocksByHash[block.hash];
|
|
88
|
+
}
|
|
89
|
+
setHead(block) {
|
|
90
|
+
logger.debug({
|
|
91
|
+
number: block.number,
|
|
92
|
+
hash: block.hash,
|
|
93
|
+
}, 'setHead');
|
|
94
|
+
this.#head = block;
|
|
95
|
+
this.#registerBlock(block);
|
|
96
|
+
this.headState.setHead(block);
|
|
97
|
+
}
|
|
98
|
+
async submitExtrinsic(extrinsic) {
|
|
99
|
+
const source = '0x02'; // External
|
|
100
|
+
const args = (0, util_1.u8aToHex)((0, util_1.u8aConcat)(source, extrinsic, this.head.hash));
|
|
101
|
+
const res = await this.head.call('TaggedTransactionQueue_validate_transaction', args);
|
|
102
|
+
const registry = await this.head.registry;
|
|
103
|
+
const validity = registry.createType('TransactionValidity', res.result);
|
|
104
|
+
if (validity.isOk) {
|
|
105
|
+
this.#txpool.submitExtrinsic(extrinsic);
|
|
106
|
+
return (0, util_crypto_1.blake2AsHex)(extrinsic, 256);
|
|
107
|
+
}
|
|
108
|
+
throw new shared_1.ResponseError(1, `Extrinsic is invalid: ${validity.asErr.toString()}`);
|
|
109
|
+
}
|
|
110
|
+
async newBlock() {
|
|
111
|
+
await this.#txpool.buildBlock();
|
|
112
|
+
return this.#head;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.Blockchain = Blockchain;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Block } from './block';
|
|
2
|
+
import { DecoratedMeta } from '@polkadot/types/metadata/decorate/types';
|
|
3
|
+
import { TaskManager } from '../task';
|
|
4
|
+
export interface CreateInherents {
|
|
5
|
+
createInherents(meta: DecoratedMeta, timestamp: number, parent: Block): Promise<string[]>;
|
|
6
|
+
}
|
|
7
|
+
export interface InherentProvider extends CreateInherents {
|
|
8
|
+
getTimestamp(): number;
|
|
9
|
+
}
|
|
10
|
+
export declare class SetTimestamp implements InherentProvider {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(getTimestamp?: () => number);
|
|
13
|
+
createInherents(meta: DecoratedMeta, timestamp: number, _parent: Block): Promise<string[]>;
|
|
14
|
+
getTimestamp(): number;
|
|
15
|
+
}
|
|
16
|
+
export declare class InherentProviders implements InherentProvider {
|
|
17
|
+
#private;
|
|
18
|
+
constructor(base: InherentProvider, providers: CreateInherents[]);
|
|
19
|
+
createInherents(meta: DecoratedMeta, timestamp: number, parent: Block): Promise<string[]>;
|
|
20
|
+
getTimestamp(): number;
|
|
21
|
+
}
|
|
22
|
+
export declare class SetValidationData implements CreateInherents {
|
|
23
|
+
#private;
|
|
24
|
+
constructor(tasks: TaskManager, expectedIndex: number);
|
|
25
|
+
createInherents(meta: DecoratedMeta, _timestamp: number, parent: Block): Promise<string[]>;
|
|
26
|
+
}
|