@leofcoin/chain 1.10.22 → 1.11.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 (28) hide show
  1. package/README.md +74 -0
  2. package/exports/browser/beacon-wire.js +2 -2
  3. package/exports/browser/{browser-CCfR9KOM-UO9mzy-j.js → browser-CCfR9KOM-CNWKLusu.js} +3 -3
  4. package/exports/browser/{browser-crypto-CH0ua22L.js → browser-crypto-B_UJguhk.js} +453 -35
  5. package/exports/browser/chain.js +1234 -885
  6. package/exports/browser/{client-DbZBfpG6-CYDyUGUC.js → client-DbZBfpG6-C1cXgvfr.js} +4 -4
  7. package/exports/browser/{connection-monitor-DMGAhcC0.js → connection-monitor-BzbeYGEt.js} +46 -26
  8. package/exports/browser/{index-dTQVgkpf-C6SJ0ZYW.js → index-dTQVgkpf-CkoAiRNX.js} +3 -3
  9. package/exports/browser/{index-dxD17Fom.js → index-phv5KvaE.js} +7 -7
  10. package/exports/browser/{messages-DZYCxoZI-D6kIiLKF.js → messages-DZYCxoZI-B472zldo.js} +3 -3
  11. package/exports/browser/{node-browser-BMfelxXV.js → node-browser-BmyPBkL5.js} +36 -8
  12. package/exports/browser/node-browser.js +3 -3
  13. package/exports/browser/workers/block-worker.js +8 -9
  14. package/exports/browser/workers/machine-worker.js +141 -1195
  15. package/exports/{workers/worker-BDTWOCzS-COrAszCt.js → browser/workers/proposal.proto-8zgMfutf-B3So_tnS.js} +910 -398
  16. package/exports/chain.js +248 -960
  17. package/exports/{connection-monitor-h2L8vsS7.js → connection-monitor-BdFLRh-J.js} +33 -3
  18. package/exports/connection-monitor.js +1 -1
  19. package/exports/{constants-Dy9i9S_o.js → constants-BQ6f-_OT.js} +1 -1
  20. package/exports/hash-format-state.js +3 -3
  21. package/exports/machine.js +550 -0
  22. package/exports/node.js +33 -2
  23. package/exports/state-checkpoint-BiQ1EXzo.js +110 -0
  24. package/exports/state-checkpoint.js +3 -0
  25. package/exports/workers/block-worker.js +8 -9
  26. package/exports/workers/machine-worker.js +141 -1195
  27. package/exports/{browser/workers/worker-BDTWOCzS-COrAszCt.js → workers/proposal.proto-8zgMfutf-B3So_tnS.js} +910 -398
  28. package/package.json +7 -5
@@ -25,7 +25,35 @@ class Protocol {
25
25
 
26
26
  const INITIAL_RECONNECT_DELAY = 5e3;
27
27
  const MAX_RECONNECT_DELAY = 3e4;
28
+ const isChainStore = (store) => store === "block" || store === "transaction" || store === "transactionPool";
28
29
  const guardProviderDiscovery = (peernet) => {
30
+ const handleData = peernet.handleData.bind(peernet);
31
+ peernet.handleData = async (peer, id, proto) => {
32
+ const { hash, store: storeName } = proto.decoded;
33
+ if (!isChainStore(storeName)) return handleData(peer, id, proto);
34
+ let data = new Uint8Array();
35
+ try {
36
+ const store = globalThis[`${storeName}Store`];
37
+ if (store && !("private" in store && store.private) && await store.has(hash)) {
38
+ const bytes = await store.get(hash);
39
+ if (!(bytes instanceof Uint8Array) || bytes.length === 0) {
40
+ throw new Error("local content is unavailable");
41
+ }
42
+ data = bytes;
43
+ } else {
44
+ console.warn(`Requested content unavailable in ${storeName}: ${hash}`);
45
+ }
46
+ } catch (error) {
47
+ console.error(`Local chain data read failed in ${storeName}: ${hash}`, error);
48
+ }
49
+ try {
50
+ const response = new peernet.protos["peernet-data-response"]({ hash, data });
51
+ const message = await peernet.prepareMessage(response);
52
+ await peernet.sendMessage(peer, id, message.encoded);
53
+ } catch (error) {
54
+ console.error(`Incoming chain data response failed in ${storeName}: ${hash}`, error);
55
+ }
56
+ };
29
57
  peernet.walk = async (hash) => {
30
58
  if (!hash) throw new Error("hash expected for provider discovery");
31
59
  const request = new peernet.protos["peernet-dht"]({ hash });
@@ -42,7 +70,8 @@ const guardProviderDiscovery = (peernet) => {
42
70
  timeout = setTimeout(() => reject(new Error("provider discovery timed out")), requestTimeout);
43
71
  })
44
72
  ]);
45
- const bytes = result instanceof Uint8Array ? result : new Uint8Array(Object.values(result));
73
+ if (!(result instanceof Uint8Array)) throw new TypeError("Peer response must be Uint8Array");
74
+ const bytes = result;
46
75
  if (new Codec(bytes).name !== "peernet-dht-response") throw new Error("invalid provider response codec");
47
76
  const response = new peernet.protos["peernet-dht-response"](bytes).decoded;
48
77
  if (response.hash !== hash) throw new Error("provider response hash mismatch");
@@ -68,7 +97,7 @@ const guardProviderDiscovery = (peernet) => {
68
97
  const requestData = peernet.requestData.bind(peernet);
69
98
  peernet.requestData = async (hash, store, options) => {
70
99
  const storeName = typeof store === "string" ? store : store?.name;
71
- if (!["block", "transaction", "transactionPool"].includes(storeName)) {
100
+ if (!isChainStore(storeName)) {
72
101
  return requestData(hash, store, options);
73
102
  }
74
103
  const probe = await peernet.prepareMessage(new peernet.protos["peernet-dht"]({ hash, store: storeName }));
@@ -81,7 +110,8 @@ const guardProviderDiscovery = (peernet) => {
81
110
  try {
82
111
  const fetch = async () => {
83
112
  const availability = await peer.request(probe.encoded);
84
- const probeBytes = availability instanceof Uint8Array ? availability : new Uint8Array(Object.values(availability));
113
+ if (!(availability instanceof Uint8Array)) throw new TypeError("Peer response must be Uint8Array");
114
+ const probeBytes = availability;
85
115
  if (new Codec(probeBytes).name !== "peernet-dht-response") throw new Error("invalid provider response codec");
86
116
  const provider = new peernet.protos["peernet-dht-response"](probeBytes).decoded;
87
117
  if (provider.hash !== hash || !provider.has) throw new Error("peer does not provide content in requested store");
@@ -1,2 +1,2 @@
1
1
  import '@leofcoin/codec-format-interface';
2
- export { C as default, g as guardPeerMessages, a as guardProviderDiscovery, i as isSendablePeer } from './connection-monitor-h2L8vsS7.js';
2
+ export { C as default, g as guardPeerMessages, a as guardProviderDiscovery, i as isSendablePeer } from './connection-monitor-BdFLRh-J.js';
@@ -1,6 +1,6 @@
1
1
  import networks from '@leofcoin/networks';
2
2
 
3
- const PROTOCOL_VERSION = "0.4.0";
3
+ const PROTOCOL_VERSION = "0.5.0";
4
4
  const REACHED_ONE_ZERO_ZERO = false;
5
5
  const DEFAULT_NODE_OPTIONS = {
6
6
  autoStart: false,
@@ -1,4 +1,4 @@
1
- import { P as PROTOCOL_VERSION } from './constants-Dy9i9S_o.js';
1
+ import { P as PROTOCOL_VERSION } from './constants-BQ6f-_OT.js';
2
2
  import '@leofcoin/networks';
3
3
 
4
4
  const assertHashFormatState = async (chainStore, stateStore) => {
@@ -7,9 +7,9 @@ const assertHashFormatState = async (chainStore, stateStore) => {
7
7
  const parsed = /^(\d+)\.(\d+)\.\d+$/.exec(version);
8
8
  const current = PROTOCOL_VERSION.split(".");
9
9
  if (parsed && parsed[1] === current[0] && parsed[2] === current[1]) return;
10
- throw new Error(`stored protocol ${version} is incompatible with canonical hash protocol ${PROTOCOL_VERSION}; back up existing data and use a separate migrated data root`);
10
+ throw new Error(`stored protocol ${version} is incompatible with canonical hash protocol ${PROTOCOL_VERSION}; explicit ledger migration or a testnet reset is required (preserve the wallet identity)`);
11
11
  }
12
- if (await chainStore.has("lastBlock") || await stateStore?.has("lastBlock")) {
12
+ if (await chainStore.has("lastBlock") || await chainStore.has("recoveryTarget") || await stateStore?.has("lastBlock") || await stateStore?.has("checkpoint:0") || await stateStore?.has("checkpoint:1")) {
13
13
  throw new Error("existing chainstate has no protocol version; explicit canonical hash migration required");
14
14
  }
15
15
  };
@@ -0,0 +1,550 @@
1
+ import addresses, { contractFactory } from '@leofcoin/addresses';
2
+ import { randombytes } from '@leofcoin/crypto';
3
+ import EasyWorker from '@leofcoin/workers/binary-worker';
4
+ import { ContractMessage } from '@leofcoin/messages';
5
+ import { ContractDeploymentError, ExecutionError } from '@leofcoin/errors';
6
+ import { createDebugger } from '@vandeurenglenn/debug';
7
+ import { createChainIdentity } from '@leofcoin/lib/chain-identity';
8
+ import { w as writeCheckpoint } from './state-checkpoint-BiQ1EXzo.js';
9
+ import { jsonStringifyBigInt } from '@leofcoin/utils';
10
+
11
+ class ReadOnlyMachine {
12
+ constructor(workerPath, readContract) {
13
+ this.workerPath = workerPath;
14
+ this.readContract = readContract;
15
+ }
16
+ #worker;
17
+ #pending = /* @__PURE__ */ new Map();
18
+ #tail = Promise.resolve();
19
+ #queued = 0;
20
+ #seed;
21
+ #cache = /* @__PURE__ */ new Map();
22
+ #cacheBytes = 0;
23
+ #closed = false;
24
+ read(seed, contract, method, params = [], sender = "leofcoin:read-only") {
25
+ if (this.#closed) return Promise.reject(new Error("Read-only machine is closed"));
26
+ if (typeof contract !== "string" || !contract || typeof method !== "string" || !method || ["constructor", "__proto__", "prototype"].includes(method) || !Array.isArray(params))
27
+ return Promise.reject(new Error("Invalid read-only contract query"));
28
+ if (this.#queued >= 32) return Promise.reject(new Error("Read-only query queue is full"));
29
+ let key;
30
+ try {
31
+ key = JSON.stringify({ contract, method, params, sender }, jsonStringifyBigInt);
32
+ if (new TextEncoder().encode(key).byteLength > 65536) throw new Error("Read-only query is too large");
33
+ params = structuredClone(params);
34
+ } catch (error) {
35
+ return Promise.reject(error);
36
+ }
37
+ this.#queued++;
38
+ const result = this.#tail.then(async () => {
39
+ if (this.#closed) throw new Error("Read-only machine is closed");
40
+ if (this.#seed !== seed) {
41
+ this.#seed = seed;
42
+ this.#cache.clear();
43
+ this.#cacheBytes = 0;
44
+ }
45
+ if (this.#cache.has(key)) return structuredClone(this.#cache.get(key).value);
46
+ try {
47
+ await this.#start();
48
+ const initialized = await this.#request("init", {
49
+ readOnly: true,
50
+ fromState: true,
51
+ blocks: [],
52
+ peerid: "leofcoin:read-only",
53
+ lastBlock: seed.lastBlock,
54
+ state: seed.states,
55
+ info: seed.info
56
+ });
57
+ if (initialized !== true) throw new Error("Worker does not support isolated contract queries");
58
+ const value = await this.#request("read", { contract, method, params, sender });
59
+ const bytes = new TextEncoder().encode(JSON.stringify(value, jsonStringifyBigInt) ?? "").byteLength;
60
+ if (bytes > 4 * 1024 * 1024) throw new Error("Read-only result is too large");
61
+ while (this.#cache.size && (this.#cache.size >= 64 || this.#cacheBytes + bytes > 4 * 1024 * 1024)) {
62
+ const oldest = this.#cache.keys().next().value;
63
+ this.#cacheBytes -= this.#cache.get(oldest).bytes;
64
+ this.#cache.delete(oldest);
65
+ }
66
+ this.#cache.set(key, { value: structuredClone(value), bytes });
67
+ this.#cacheBytes += bytes;
68
+ return value;
69
+ } catch (error) {
70
+ this.#stop();
71
+ throw error;
72
+ }
73
+ });
74
+ this.#tail = result.then(() => void 0, () => void 0);
75
+ return result.finally(() => {
76
+ this.#queued--;
77
+ });
78
+ }
79
+ async #start() {
80
+ if (this.#worker) return;
81
+ const worker = await new EasyWorker(this.workerPath, { serialization: "advanced", type: "module" });
82
+ if (this.#closed) {
83
+ worker.terminate();
84
+ throw new Error("Read-only machine is closed");
85
+ }
86
+ this.#worker = worker;
87
+ worker.onmessage(async (message) => {
88
+ if (this.#worker !== worker) return;
89
+ if (message.type === "ask") {
90
+ try {
91
+ const input = message.question === "peers" ? [] : message.question === "contract" ? await this.readContract(message.input) : void 0;
92
+ if (this.#worker === worker) worker.postMessage({ id: message.id, input });
93
+ } catch {
94
+ if (this.#worker === worker) worker.postMessage({ id: message.id, input: void 0 });
95
+ }
96
+ return;
97
+ }
98
+ const pending = this.#pending.get(message.id);
99
+ if (!pending) return;
100
+ if (message.type !== "response" && !["initError", "readError"].includes(message.type)) return;
101
+ clearTimeout(pending.timer);
102
+ this.#pending.delete(message.id);
103
+ if (message.type === "response") pending.resolve(message.value);
104
+ else pending.reject(new Error(message.message || "Read-only worker failed"));
105
+ });
106
+ worker.onerror(() => {
107
+ if (this.#worker === worker) this.#stop();
108
+ });
109
+ worker.worker?.once?.("exit", () => {
110
+ if (this.#worker === worker) this.#stop();
111
+ });
112
+ }
113
+ #request(type, input) {
114
+ return new Promise((resolve, reject) => {
115
+ const id = crypto.randomUUID();
116
+ const timer = setTimeout(() => this.#stop(new Error("Read-only query timed out")), 1e4);
117
+ this.#pending.set(id, { resolve, reject, timer });
118
+ try {
119
+ this.#worker.postMessage({ id, type, input });
120
+ } catch (error) {
121
+ this.#stop(error);
122
+ }
123
+ });
124
+ }
125
+ #stop(error = new Error("Read-only worker stopped")) {
126
+ const worker = this.#worker;
127
+ this.#worker = void 0;
128
+ for (const pending of this.#pending.values()) {
129
+ clearTimeout(pending.timer);
130
+ pending.reject(error);
131
+ }
132
+ this.#pending.clear();
133
+ worker?.terminate();
134
+ }
135
+ close() {
136
+ this.#closed = true;
137
+ this.#cache.clear();
138
+ this.#cacheBytes = 0;
139
+ this.#seed = void 0;
140
+ this.#stop();
141
+ }
142
+ }
143
+
144
+ const genesisContracts = {
145
+ contractFactory: addresses.contractFactory,
146
+ nativeToken: addresses.nativeToken,
147
+ nameService: addresses.nameService,
148
+ validators: addresses.validators
149
+ };
150
+ const debug = createDebugger("leofcoin/machine");
151
+ class Machine {
152
+ constructor(blocks = [], checkpoint) {
153
+ this.states = {
154
+ states: {},
155
+ lastBlock: {
156
+ index: -1,
157
+ hash: "0x0"
158
+ },
159
+ accounts: {},
160
+ info: {
161
+ nativeCalls: BigInt(0),
162
+ nativeMints: BigInt(0),
163
+ nativeBurns: BigInt(0),
164
+ nativeTransfers: BigInt(0),
165
+ totalBurnAmount: BigInt(0),
166
+ totalMintAmount: BigInt(0),
167
+ totalTransferAmount: BigInt(0),
168
+ totalTransactions: BigInt(0),
169
+ totalBlocks: BigInt(0)
170
+ }
171
+ };
172
+ this.wantList = [];
173
+ this.ready = new Promise((resolve, reject) => {
174
+ this.readyResolve = resolve;
175
+ this.readyReject = reject;
176
+ });
177
+ this.#checkpointGeneration = -1;
178
+ this.#closed = false;
179
+ this.init(blocks, checkpoint).then(() => this.readyResolve(this), (error) => {
180
+ this.close();
181
+ this.readyReject(error);
182
+ });
183
+ }
184
+ #checkpointGeneration;
185
+ #chainId;
186
+ #reader;
187
+ #contractReader;
188
+ #readSnapshot;
189
+ #workerPath;
190
+ #closed;
191
+ close() {
192
+ this.#closed = true;
193
+ this.#reader?.close();
194
+ this.#contractReader?.close();
195
+ this.worker?.terminate();
196
+ }
197
+ async init(blocks, checkpoint) {
198
+ return this.#init(blocks, checkpoint);
199
+ }
200
+ async #onmessage(data) {
201
+ if (this.#closed) return;
202
+ switch (data.type) {
203
+ case "contractError": {
204
+ console.warn(data.message ?? data.error);
205
+ console.warn(`contract error: ${data.hash}`);
206
+ break;
207
+ }
208
+ case "initError": {
209
+ pubsub.publish(data.id, { error: new Error(`Machine initialization failed: ${data.message}`) });
210
+ break;
211
+ }
212
+ case "executeError": {
213
+ pubsub.publish(data.id, { error: data.message });
214
+ break;
215
+ }
216
+ case "addLoadedBlockError":
217
+ case "runContractError":
218
+ case "collectFeeError":
219
+ case "settleRewardsError": {
220
+ pubsub.publish(data.id, { error: new Error(data.message) });
221
+ break;
222
+ }
223
+ case "snapshotError": {
224
+ pubsub.publish(data.id, { error: new Error(data.message) });
225
+ break;
226
+ }
227
+ case "getError": {
228
+ pubsub.publish(data.id, { error: new Error(data.message) });
229
+ break;
230
+ }
231
+ case "debug": {
232
+ if (data.message.includes("loaded transactions for block:")) {
233
+ pubsub.publish("block-loaded", data.message.replace("loaded transactions for block: ", "").split(" @")[0]);
234
+ }
235
+ break;
236
+ }
237
+ case "error": {
238
+ console.error(data.message);
239
+ break;
240
+ }
241
+ case "response": {
242
+ pubsub.publish(data.id, data.value ?? false);
243
+ break;
244
+ }
245
+ case "addToWantList": {
246
+ if (!this.wantList.includes(data.hash)) this.wantList.push(data.hash);
247
+ break;
248
+ }
249
+ case "ask": {
250
+ if (data.question === "staticCall") {
251
+ try {
252
+ const { snapshot, contract, method, params, sender } = data.input;
253
+ const input = await this.#contractReader.read(snapshot, contract, method, params, sender);
254
+ if (!this.#closed) this.worker.postMessage({ id: data.id, input });
255
+ } catch (error) {
256
+ if (!this.#closed) this.worker.postMessage({ id: data.id, error: error instanceof Error ? error.message : String(error) });
257
+ }
258
+ break;
259
+ }
260
+ if (data.question === "contract" || data.question === "transaction") {
261
+ try {
262
+ const input = await peernet.get(data.input, data.question);
263
+ if (input === void 0) {
264
+ this.worker.postMessage({ id: data.id, error: `could not get ${data.question} @${data.input}` });
265
+ this.wantList.push(data.input);
266
+ } else {
267
+ this.worker.postMessage({ id: data.id, input });
268
+ }
269
+ } catch (error) {
270
+ console.error(error);
271
+ this.worker.postMessage({ id: data.id, error: `could not get ${data.question} @${data.input}` });
272
+ this.wantList.push(data.input);
273
+ }
274
+ } else if (data.question === "peers") {
275
+ this.worker.postMessage({
276
+ id: data.id,
277
+ input: peernet.connections ? Object.entries(peernet.connections).map(([id, peer]) => peer.toJSON()) : []
278
+ });
279
+ } else {
280
+ this.worker.postMessage({ id: data.id, input: data.input });
281
+ }
282
+ }
283
+ }
284
+ }
285
+ async updateState(strict = false) {
286
+ try {
287
+ const snapshot = await this.#askWorker("snapshot");
288
+ snapshot.chainId = this.#chainId;
289
+ const accounts = {};
290
+ for (const address of await accountsStore.keys()) {
291
+ const raw = await accountsStore.get(address);
292
+ accounts[address] = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
293
+ }
294
+ snapshot.accounts = accounts;
295
+ const generation = this.#checkpointGeneration + 1;
296
+ await writeCheckpoint(stateStore, snapshot, generation, Object.values(genesisContracts));
297
+ this.#checkpointGeneration = generation;
298
+ this.states = snapshot;
299
+ this.#readSnapshot = structuredClone(snapshot);
300
+ } catch (error) {
301
+ if (strict) throw error;
302
+ console.error(error);
303
+ throw error;
304
+ }
305
+ }
306
+ async #init(blocks, checkpoint) {
307
+ const identity = await createChainIdentity(globalThis.peernet?.network || "leofcoin:peach", addresses);
308
+ this.#chainId = identity.chainId;
309
+ if (checkpoint) {
310
+ this.#checkpointGeneration = checkpoint.generation;
311
+ this.states = checkpoint.snapshot;
312
+ }
313
+ let pre = "./";
314
+ try {
315
+ const { fileURLToPath } = await import('url');
316
+ pre = fileURLToPath(new URL(".", import.meta.url));
317
+ } catch {
318
+ }
319
+ this.#workerPath = pre + "workers/machine-worker.js";
320
+ const readContract = (hash) => globalThis.contractStore.get(hash);
321
+ this.#reader = new ReadOnlyMachine(this.#workerPath, readContract);
322
+ this.#contractReader = new ReadOnlyMachine(this.#workerPath, readContract);
323
+ this.worker = await new EasyWorker(this.#workerPath, {
324
+ serialization: "advanced",
325
+ type: "module"
326
+ });
327
+ if (this.#closed) {
328
+ this.worker.terminate();
329
+ throw new Error("State machine is closed");
330
+ }
331
+ this.worker.onmessage(this.#onmessage.bind(this));
332
+ const initialized = await this.#askWorker("init", {
333
+ blocks,
334
+ fromState: Boolean(checkpoint),
335
+ lastBlock: checkpoint?.snapshot.lastBlock,
336
+ state: checkpoint?.snapshot.states,
337
+ info: checkpoint?.snapshot.info,
338
+ peerid: peernet.peerId
339
+ });
340
+ if (initialized !== true) throw new Error("Worker does not support checkpoint initialization; deploy matching chain and worker builds");
341
+ this.#readSnapshot = structuredClone(checkpoint?.snapshot ?? await this.#askWorker("snapshot"));
342
+ }
343
+ async #runContract(contractMessage) {
344
+ const hash = await contractMessage.hash();
345
+ return new Promise((resolve, reject) => {
346
+ const id = randombytes(20).toString("hex");
347
+ const onmessage = (message) => {
348
+ pubsub.unsubscribe(id, onmessage);
349
+ if (message?.error) reject(message.error);
350
+ else resolve(message);
351
+ };
352
+ pubsub.subscribe(id, onmessage);
353
+ this.worker.postMessage({
354
+ type: "run",
355
+ id,
356
+ input: {
357
+ decoded: contractMessage.decoded,
358
+ encoded: contractMessage.encoded,
359
+ hash
360
+ }
361
+ });
362
+ });
363
+ }
364
+ /**
365
+ *
366
+ * @param {Address} contract
367
+ * @param {String} method
368
+ * @param {Array} parameters
369
+ * @returns Promise<message>
370
+ */
371
+ async execute(contract, method, parameters, sender, execution) {
372
+ if (this.#closed) throw new Error("State machine is closed");
373
+ try {
374
+ if (contract === contractFactory && method === "registerContract") {
375
+ if (await this.has(parameters[0])) throw new Error(`duplicate contract @${parameters[0]}`);
376
+ let message;
377
+ if (!await globalThis.contractStore.has(parameters[0])) {
378
+ message = await peernet.get(parameters[0], "contract");
379
+ if (!message) throw new Error(`contract ${parameters[0]} not available`);
380
+ message = await new ContractMessage(message);
381
+ await globalThis.contractStore.put(await message.hash(), message.encoded);
382
+ }
383
+ if (!message) {
384
+ message = await globalThis.contractStore.get(parameters[0]);
385
+ message = await new ContractMessage(message);
386
+ }
387
+ if (!await this.has(await message.hash())) await this.#runContract(message);
388
+ }
389
+ } catch (error) {
390
+ throw new ContractDeploymentError(`contract deployment failed for ${parameters[0]}
391
+ ${error.message}`);
392
+ }
393
+ return new Promise((resolve, reject) => {
394
+ const id = randombytes(20).toString("hex");
395
+ const timeout = setTimeout(() => {
396
+ pubsub.unsubscribe(id, onmessage);
397
+ reject(new Error(`Machine.execute timeout for ${contract}.${method}`));
398
+ }, 3e4);
399
+ const onmessage = (message) => {
400
+ clearTimeout(timeout);
401
+ pubsub.unsubscribe(id, onmessage);
402
+ if (message?.error) reject(new ExecutionError(message.error));
403
+ else resolve(message);
404
+ };
405
+ pubsub.subscribe(id, onmessage);
406
+ this.worker.postMessage({
407
+ type: "execute",
408
+ id,
409
+ input: {
410
+ to: contract,
411
+ contract,
412
+ method,
413
+ params: parameters,
414
+ sender,
415
+ executionSeed: execution?.seed,
416
+ executionTimestamp: execution?.timestamp,
417
+ executionRandomness: execution?.randomness
418
+ }
419
+ });
420
+ });
421
+ }
422
+ collectFee(from, payments, burned) {
423
+ return this.#askWorker("collectFee", { from, payments, burned });
424
+ }
425
+ settleRewards(rewards) {
426
+ return this.#askWorker("settleRewards", { rewards });
427
+ }
428
+ get(contract, method, parameters) {
429
+ if (!this.#readSnapshot) return Promise.reject(new Error("Contract read state is not initialized"));
430
+ return this.#reader.read(this.#readSnapshot, contract, method, parameters ?? []);
431
+ }
432
+ async has(address) {
433
+ return new Promise((resolve, reject) => {
434
+ const id = randombytes(20).toString("hex");
435
+ const timeout = setTimeout(() => {
436
+ pubsub.unsubscribe(id, onmessage);
437
+ reject(new Error(`Machine.has timeout for ${address}`));
438
+ }, 1e4);
439
+ const onmessage = (message) => {
440
+ clearTimeout(timeout);
441
+ pubsub.unsubscribe(id, onmessage);
442
+ if (message?.error) reject(message.error);
443
+ else resolve(message);
444
+ };
445
+ pubsub.subscribe(id, onmessage);
446
+ this.worker.postMessage({
447
+ type: "has",
448
+ id,
449
+ input: {
450
+ address
451
+ }
452
+ });
453
+ });
454
+ }
455
+ #askWorker(type, input) {
456
+ if (this.#closed) return Promise.reject(new Error("State machine is closed"));
457
+ return new Promise((resolve, reject) => {
458
+ const id = randombytes(20).toString("hex");
459
+ const timeout = setTimeout(() => {
460
+ pubsub.unsubscribe(id, onmessage);
461
+ reject(new Error(`Machine.#askWorker timeout for ${type}`));
462
+ }, 3e4);
463
+ const onmessage = (message) => {
464
+ clearTimeout(timeout);
465
+ pubsub.unsubscribe(id, onmessage);
466
+ if (message?.error) reject(message.error);
467
+ else resolve(message);
468
+ };
469
+ pubsub.subscribe(id, onmessage);
470
+ this.worker.postMessage({
471
+ type,
472
+ id,
473
+ input
474
+ });
475
+ });
476
+ }
477
+ get contracts() {
478
+ return this.#askWorker("contracts");
479
+ }
480
+ get totalContracts() {
481
+ return this.#askWorker("totalContracts");
482
+ }
483
+ get nativeCalls() {
484
+ return this.#askWorker("nativeCalls");
485
+ }
486
+ get nativeMints() {
487
+ return this.#askWorker("nativeMints");
488
+ }
489
+ get nativeBurns() {
490
+ return this.#askWorker("nativeBurns");
491
+ }
492
+ get nativeTransfers() {
493
+ return this.#askWorker("nativeTransfers");
494
+ }
495
+ get totalBurnAmount() {
496
+ return this.#askWorker("totalBurnAmount");
497
+ }
498
+ get totalMintAmount() {
499
+ return this.#askWorker("totalMintAmount");
500
+ }
501
+ get totalTransferAmount() {
502
+ return this.#askWorker("totalTransferAmount");
503
+ }
504
+ get totalTransactions() {
505
+ return this.#askWorker("totalTransactions");
506
+ }
507
+ get totalBlocks() {
508
+ return this.#askWorker("totalBlocks");
509
+ }
510
+ get blocks() {
511
+ return this.getBlocks();
512
+ }
513
+ get lastBlock() {
514
+ return Promise.resolve(this.states.lastBlock);
515
+ }
516
+ getCanonicalTip() {
517
+ return this.#askWorker("lastBlock");
518
+ }
519
+ get lastBlockHeight() {
520
+ return Promise.resolve(Number(this.states.lastBlock.index));
521
+ }
522
+ getBlocks(from, to) {
523
+ return this.#askWorker("blocks", { from, to });
524
+ }
525
+ getBlock(index) {
526
+ return this.#askWorker("block", index);
527
+ }
528
+ async addLoadedBlock(block) {
529
+ debug(`adding loaded block: ${block.index}@${block.hash}`);
530
+ if (block.decoded) block = { ...block.decoded, hash: await block.hash() };
531
+ return this.#askWorker("addLoadedBlock", block);
532
+ }
533
+ async latestTransactions() {
534
+ return this.#askWorker("latestTransactions");
535
+ }
536
+ async delete(hash) {
537
+ return globalThis.contractStore.delete(hash);
538
+ }
539
+ /**
540
+ *
541
+ * @returns Promise
542
+ */
543
+ async deleteAll() {
544
+ let hashes = await globalThis.contractStore.keys();
545
+ hashes = Object.keys(hashes).map((hash) => this.delete(hash));
546
+ return Promise.all(hashes);
547
+ }
548
+ }
549
+
550
+ export { Machine as default };