@leofcoin/chain 1.10.21 → 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.
- package/README.md +95 -0
- package/exports/browser/beacon-wire.js +2 -2
- package/exports/browser/{browser-CCfR9KOM-BGMMnVsR.js → browser-CCfR9KOM-CNWKLusu.js} +3 -3
- package/exports/browser/{browser-crypto-CH0ua22L.js → browser-crypto-B_UJguhk.js} +453 -35
- package/exports/browser/chain.js +1275 -996
- package/exports/browser/client-DbZBfpG6-C1cXgvfr.js +5943 -0
- package/exports/browser/{connection-monitor-D-krNiRH.js → connection-monitor-BzbeYGEt.js} +153 -24
- package/exports/browser/{index-dTQVgkpf-DOL2rIjp.js → index-dTQVgkpf-CkoAiRNX.js} +3 -3
- package/exports/browser/{index-DCbLvMKL.js → index-phv5KvaE.js} +238 -234
- package/exports/browser/{messages-DCePyqKC-BSvJxDWZ.js → messages-DZYCxoZI-B472zldo.js} +3 -3
- package/exports/browser/{node-browser-Ha4Lvntj.js → node-browser-BmyPBkL5.js} +55 -11
- package/exports/browser/node-browser.js +3 -3
- package/exports/browser/workers/block-worker.js +8 -9
- package/exports/browser/workers/machine-worker.js +141 -1195
- package/exports/{workers/worker-BDTWOCzS-COrAszCt.js → browser/workers/proposal.proto-8zgMfutf-B3So_tnS.js} +910 -398
- package/exports/chain.js +296 -1078
- package/exports/connection-monitor-BdFLRh-J.js +465 -0
- package/exports/connection-monitor.js +2 -324
- package/exports/{constants-Dy9i9S_o.js → constants-BQ6f-_OT.js} +1 -1
- package/exports/hash-format-state.js +3 -3
- package/exports/machine.js +550 -0
- package/exports/node.js +35 -2
- package/exports/state-checkpoint-BiQ1EXzo.js +110 -0
- package/exports/state-checkpoint.js +3 -0
- package/exports/workers/block-worker.js +8 -9
- package/exports/workers/machine-worker.js +141 -1195
- package/exports/{browser/workers/worker-BDTWOCzS-COrAszCt.js → workers/proposal.proto-8zgMfutf-B3So_tnS.js} +910 -398
- package/package.json +9 -7
- package/exports/browser/client-DKKzPSlb-C_1ug6ux.js +0 -2401
package/exports/browser/chain.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { B as BlockMessage, L as LastBlockMessage,
|
|
3
|
-
import { M as MAX_TRANSACTION_BYTES, a as MAX_BLOCK_TRANSACTION_BYTES, b as MAX_BLOCK_TRANSACTIONS, c as MultiWallet, d as createTransactionHash, e as calculateFee, f as createContractMessage, s as signTransaction, r as randombytes, g as distributeTransactionFee, h as supportsTransactionFees, i as supportsMonetaryPolicy, v as validateBlockResourceLimits, j as calculateMonetaryPolicy, k as distributeAmount, l as aggregateValidatorFees, m as MONETARY_POLICY_AUTHORITY, n as contractFactoryMessage, o as nativeTokenMessage, p as validatorsMessage, q as nameServiceMessage, t as parseUnits, u as formatUnits } from './index-DCbLvMKL.js';
|
|
1
|
+
import { C as Codec, B as BinaryFormat, p as proto, a as proto$1, b as proto$2, T as TransactionMessage, j as jsonParseBigInt, c as jsonStringifyBigInt, d as ContractMessage, e as decodeWire$1, f as encodeWire$1 } from './browser-crypto-B_UJguhk.js';
|
|
2
|
+
import { P as Protocol, B as BlockMessage, L as LastBlockMessage, a as PROTOCOL_VERSION, R as REACHED_ONE_ZERO_ZERO, C as ConnectionMonitor, b as BWMessage, S as StateMessage } from './connection-monitor-BzbeYGEt.js';
|
|
4
3
|
import { f as fromBase58 } from './index-CvKt4UDE.js';
|
|
4
|
+
import { M as MAX_TRANSACTION_BYTES, a as MAX_BLOCK_TRANSACTION_BYTES, b as MAX_BLOCK_TRANSACTIONS, c as MultiWallet, d as createTransactionHash, e as calculateFee, f as createContractMessage, s as signTransaction, r as randombytes, g as supportsTransactionFees, h as supportsMonetaryPolicy, v as validateBlockResourceLimits, i as aggregateValidatorFees, j as distributeAmount, k as MONETARY_POLICY_AUTHORITY, l as calculateMonetaryPolicy, m as contractFactoryMessage, n as nativeTokenMessage, o as validatorsMessage, p as nameServiceMessage, q as parseUnits, t as formatUnits, u as distributeTransactionFee } from './index-phv5KvaE.js';
|
|
5
5
|
import { g as getDefaultExportFromCjs } from './_commonjsHelpers-B85MJLTf.js';
|
|
6
6
|
import { q as quorumThreshold } from './quorum-S_qdgiAY.js';
|
|
7
7
|
import './sha2-BKcEJzgd.js';
|
|
8
8
|
|
|
9
|
+
const decodeResponsePayload = async (result) => {
|
|
10
|
+
if (!(result instanceof Uint8Array)) throw new TypeError("Peer response must be Uint8Array");
|
|
11
|
+
const codec = new Codec(result);
|
|
12
|
+
if (codec.name !== "peernet-response") return result;
|
|
13
|
+
const ResponseMessage = globalThis.peernet?.protos?.["peernet-response"];
|
|
14
|
+
if (!ResponseMessage) throw new Error("peernet-response codec is not registered");
|
|
15
|
+
return new ResponseMessage(result).decoded.response;
|
|
16
|
+
};
|
|
17
|
+
|
|
9
18
|
const getTargets = () => Array.from([]);
|
|
10
19
|
const isEnabled = (target) => {
|
|
11
20
|
return false;
|
|
@@ -42,7 +51,7 @@ const formatBytes = (bytes, decimals = 2) => {
|
|
|
42
51
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${byteFormats[i]}`;
|
|
43
52
|
};
|
|
44
53
|
|
|
45
|
-
class PrevoteMessage extends
|
|
54
|
+
class PrevoteMessage extends BinaryFormat {
|
|
46
55
|
get messageName() {
|
|
47
56
|
return "PrevoteMessage";
|
|
48
57
|
}
|
|
@@ -54,7 +63,7 @@ class PrevoteMessage extends FormatInterface {
|
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
|
|
57
|
-
class PrecommitMessage extends
|
|
66
|
+
class PrecommitMessage extends BinaryFormat {
|
|
58
67
|
get messageName() {
|
|
59
68
|
return "PrecommitMessage";
|
|
60
69
|
}
|
|
@@ -66,7 +75,7 @@ class PrecommitMessage extends FormatInterface {
|
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
77
|
|
|
69
|
-
class ProposalMessage extends
|
|
78
|
+
class ProposalMessage extends BinaryFormat {
|
|
70
79
|
get messageName() {
|
|
71
80
|
return "ProposalMessage";
|
|
72
81
|
}
|
|
@@ -78,10 +87,10 @@ class ProposalMessage extends FormatInterface {
|
|
|
78
87
|
}
|
|
79
88
|
}
|
|
80
89
|
|
|
81
|
-
var contractFactory$1 = "
|
|
82
|
-
var nativeToken$1 = "
|
|
83
|
-
var nameService$1 = "
|
|
84
|
-
var validators$1 = "
|
|
90
|
+
var contractFactory$1 = "B3OGRUBSARKAMWHZLZZAL5Y4SRUJOE4RIMQX4SELNLMTQVPBGFQXEF5RPPHV";
|
|
91
|
+
var nativeToken$1 = "B3OGRUBSBSWHYGNYT4O57EK7Q5ZNXYFR23CCS4EUQPQ34RU57V6ROQNLYIVN";
|
|
92
|
+
var nameService$1 = "B3OGRUBSB23VEDKVPQM5DYREJVI2XYMSQ44ZN3NQG445AZMASFMOUU4P7HLM";
|
|
93
|
+
var validators$1 = "B3OGRUBSBT6UD4G6V3KP7Z6G2DLMMOV5D5IFIZH2I3KD2CK6VFD3CYY2NCKV";
|
|
85
94
|
var addresses$1 = {
|
|
86
95
|
contractFactory: contractFactory$1,
|
|
87
96
|
nativeToken: nativeToken$1,
|
|
@@ -2915,49 +2924,46 @@ function requireSemver () {
|
|
|
2915
2924
|
var semverExports = requireSemver();
|
|
2916
2925
|
var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
|
|
2917
2926
|
|
|
2918
|
-
const
|
|
2919
|
-
|
|
2920
|
-
const requestTimeout = 3e4;
|
|
2921
|
-
const syncTimeout = 3e4;
|
|
2922
|
-
class Protocol {
|
|
2923
|
-
constructor(config) {
|
|
2924
|
-
this.resolveTimeout = 1e4;
|
|
2925
|
-
if (config?.resolveTimeout) this.resolveTimeout = config.resolveTimeout;
|
|
2926
|
-
}
|
|
2927
|
-
get limit() {
|
|
2928
|
-
return limit;
|
|
2929
|
-
}
|
|
2930
|
-
get transactionLimit() {
|
|
2931
|
-
return transactionLimit;
|
|
2932
|
-
}
|
|
2933
|
-
get requestTimeout() {
|
|
2934
|
-
return requestTimeout;
|
|
2935
|
-
}
|
|
2936
|
-
get syncTimeout() {
|
|
2937
|
-
return syncTimeout;
|
|
2938
|
-
}
|
|
2939
|
-
}
|
|
2940
|
-
|
|
2941
|
-
const waitForCanonicalTransaction = (pubsub, transactionPool, hash) => new Promise((resolve, reject) => {
|
|
2942
|
-
const completionTopic = `transaction.completed.${hash}`;
|
|
2943
|
-
const cleanup = () => {
|
|
2944
|
-
pubsub.unsubscribe("block-processed", blockProcessed);
|
|
2945
|
-
pubsub.unsubscribe(completionTopic, transactionCompleted);
|
|
2927
|
+
const createCanonicalTransactionWaiter = (pubsub, transactionPool, hash) => {
|
|
2928
|
+
let cleanup = () => {
|
|
2946
2929
|
};
|
|
2947
|
-
|
|
2948
|
-
if (!Array.isArray(block?.transactions) || !block.transactions.includes(hash)) return;
|
|
2949
|
-
cleanup();
|
|
2950
|
-
resolve(hash);
|
|
2930
|
+
let cancel = (_reason) => {
|
|
2951
2931
|
};
|
|
2952
|
-
const
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2932
|
+
const wait = new Promise((resolve, reject) => {
|
|
2933
|
+
const completionTopic = `transaction.completed.${hash}`;
|
|
2934
|
+
let settled = false;
|
|
2935
|
+
cancel = (reason) => {
|
|
2936
|
+
if (settled) return;
|
|
2937
|
+
settled = true;
|
|
2938
|
+
reject({ hash, error: reason });
|
|
2939
|
+
};
|
|
2940
|
+
cleanup = () => {
|
|
2941
|
+
pubsub.unsubscribe("block-processed", blockProcessed);
|
|
2942
|
+
pubsub.unsubscribe(completionTopic, transactionCompleted);
|
|
2943
|
+
};
|
|
2944
|
+
const blockProcessed = (block) => {
|
|
2945
|
+
if (settled || !Array.isArray(block?.transactions) || !block.transactions.includes(hash)) return;
|
|
2946
|
+
settled = true;
|
|
2947
|
+
resolve(hash);
|
|
2948
|
+
};
|
|
2949
|
+
const transactionCompleted = async (result) => {
|
|
2950
|
+
if (settled || result?.status === "fulfilled") return;
|
|
2951
|
+
settled = true;
|
|
2952
|
+
try {
|
|
2953
|
+
await transactionPool.delete(hash);
|
|
2954
|
+
reject({ hash, error: result?.error });
|
|
2955
|
+
} catch (error) {
|
|
2956
|
+
reject({ hash, error, transactionError: result?.error });
|
|
2957
|
+
}
|
|
2958
|
+
};
|
|
2959
|
+
pubsub.subscribe("block-processed", blockProcessed);
|
|
2960
|
+
pubsub.subscribe(completionTopic, transactionCompleted);
|
|
2961
|
+
});
|
|
2962
|
+
const finalized = wait.finally(() => cleanup());
|
|
2963
|
+
void finalized.catch(() => {
|
|
2964
|
+
});
|
|
2965
|
+
return { wait: finalized, cancel };
|
|
2966
|
+
};
|
|
2961
2967
|
|
|
2962
2968
|
class Transaction extends Protocol {
|
|
2963
2969
|
#pendingNonces = /* @__PURE__ */ new Map();
|
|
@@ -3158,9 +3164,10 @@ class Transaction extends Protocol {
|
|
|
3158
3164
|
await this.validateTransactionSignature(message);
|
|
3159
3165
|
await this.validateNonce(message.decoded.from, message.decoded.nonce);
|
|
3160
3166
|
const hash = await message.hash();
|
|
3167
|
+
const fee = await calculateFee(message.decoded);
|
|
3168
|
+
const waiter = createCanonicalTransactionWaiter(pubsub, transactionPoolStore, hash);
|
|
3161
3169
|
try {
|
|
3162
3170
|
let data;
|
|
3163
|
-
const wait = waitForCanonicalTransaction(pubsub, transactionPoolStore, hash);
|
|
3164
3171
|
await globalThis.transactionPoolStore.put(hash, message.encoded);
|
|
3165
3172
|
this.addPendingNonce(message.decoded.from, message.decoded.nonce);
|
|
3166
3173
|
try {
|
|
@@ -3168,12 +3175,16 @@ class Transaction extends Protocol {
|
|
|
3168
3175
|
} catch (publishError) {
|
|
3169
3176
|
console.warn("peernet publish failed: add-transaction", publishError?.message ?? publishError);
|
|
3170
3177
|
}
|
|
3171
|
-
|
|
3172
|
-
return { hash, data, fee, wait, message };
|
|
3178
|
+
return { hash, data, fee, wait: waiter.wait, message };
|
|
3173
3179
|
} catch (error) {
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3180
|
+
waiter.cancel(error);
|
|
3181
|
+
try {
|
|
3182
|
+
await transactionPoolStore.delete(hash);
|
|
3183
|
+
} catch (cleanupError) {
|
|
3184
|
+
console.warn("failed to clean up rejected transaction:", cleanupError);
|
|
3185
|
+
} finally {
|
|
3186
|
+
this.removePendingNonce(message.decoded.from, message.decoded.nonce);
|
|
3187
|
+
}
|
|
3177
3188
|
throw error;
|
|
3178
3189
|
}
|
|
3179
3190
|
}
|
|
@@ -3501,120 +3512,541 @@ class Contract extends Transaction {
|
|
|
3501
3512
|
}
|
|
3502
3513
|
|
|
3503
3514
|
class EasyWorker {
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
this.#isBrowser = true;
|
|
3541
|
-
this.worker = globalThis;
|
|
3542
|
-
}
|
|
3543
|
-
}
|
|
3544
|
-
return this;
|
|
3515
|
+
#messageEvent = "message";
|
|
3516
|
+
#errorEvent = "error";
|
|
3517
|
+
#isBrowser = false;
|
|
3518
|
+
#isWorker = false;
|
|
3519
|
+
#messageHandler = null;
|
|
3520
|
+
#errorHandler = null;
|
|
3521
|
+
worker;
|
|
3522
|
+
get isWorker() {
|
|
3523
|
+
return this.#isWorker;
|
|
3524
|
+
}
|
|
3525
|
+
constructor(url, options) {
|
|
3526
|
+
return this.#init(url, options);
|
|
3527
|
+
}
|
|
3528
|
+
#init(url, options = {}) {
|
|
3529
|
+
if (url) {
|
|
3530
|
+
if (globalThis.Worker) {
|
|
3531
|
+
this.#isBrowser = true;
|
|
3532
|
+
this.worker = new Worker(url, { ...options });
|
|
3533
|
+
} else {
|
|
3534
|
+
return new Promise((resolve, reject) => {
|
|
3535
|
+
import('child_process').then(({ fork }) => {
|
|
3536
|
+
const child = fork(url, ["easy-worker-child"], options);
|
|
3537
|
+
child.once("error", reject);
|
|
3538
|
+
this.worker = child;
|
|
3539
|
+
resolve(this);
|
|
3540
|
+
}).catch(reject);
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
} else {
|
|
3544
|
+
this.#isWorker = true;
|
|
3545
|
+
if (globalThis.process?.argv[2] === "easy-worker-child") {
|
|
3546
|
+
this.worker = process;
|
|
3547
|
+
} else {
|
|
3548
|
+
this.#isBrowser = true;
|
|
3549
|
+
this.worker = globalThis;
|
|
3550
|
+
}
|
|
3545
3551
|
}
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3552
|
+
return this;
|
|
3553
|
+
}
|
|
3554
|
+
onmessage(fn) {
|
|
3555
|
+
if (this.#isBrowser) {
|
|
3556
|
+
this.worker.onmessage = ({ data }) => fn(data);
|
|
3557
|
+
} else {
|
|
3558
|
+
if (this.#messageHandler)
|
|
3559
|
+
this.worker.off(this.#messageEvent, this.#messageHandler);
|
|
3560
|
+
this.#messageHandler = fn;
|
|
3561
|
+
this.worker.on(this.#messageEvent, fn);
|
|
3556
3562
|
}
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3563
|
+
}
|
|
3564
|
+
postMessage(message) {
|
|
3565
|
+
if (this.#isBrowser)
|
|
3566
|
+
this.worker.postMessage(message);
|
|
3567
|
+
else
|
|
3568
|
+
this.worker.send(message);
|
|
3569
|
+
}
|
|
3570
|
+
terminate() {
|
|
3571
|
+
if (this.#isBrowser)
|
|
3572
|
+
this.worker.terminate();
|
|
3573
|
+
else
|
|
3574
|
+
this.worker.kill();
|
|
3575
|
+
}
|
|
3576
|
+
onerror(fn) {
|
|
3577
|
+
if (this.#isBrowser) {
|
|
3578
|
+
this.worker.onerror = fn;
|
|
3579
|
+
} else {
|
|
3580
|
+
if (this.#errorHandler)
|
|
3581
|
+
this.worker.off(this.#errorEvent, this.#errorHandler);
|
|
3582
|
+
this.#errorHandler = fn;
|
|
3583
|
+
this.worker.on(this.#errorEvent, fn);
|
|
3562
3584
|
}
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3585
|
+
}
|
|
3586
|
+
/**
|
|
3587
|
+
*
|
|
3588
|
+
* @param {*} data
|
|
3589
|
+
* @returns {Promise} resolves result onmessage & rejects on error
|
|
3590
|
+
*/
|
|
3591
|
+
once(data) {
|
|
3592
|
+
return new Promise((resolve, reject) => {
|
|
3593
|
+
if (this.#isBrowser) {
|
|
3594
|
+
const handleMessage = ({ data: result }) => {
|
|
3595
|
+
this.worker.onerror = null;
|
|
3596
|
+
resolve(result);
|
|
3597
|
+
this.terminate();
|
|
3598
|
+
};
|
|
3599
|
+
const handleError = (error) => {
|
|
3600
|
+
this.worker.onmessage = null;
|
|
3601
|
+
reject(error);
|
|
3602
|
+
this.terminate();
|
|
3603
|
+
};
|
|
3604
|
+
this.worker.onmessage = handleMessage;
|
|
3605
|
+
this.worker.onerror = handleError;
|
|
3606
|
+
} else {
|
|
3607
|
+
const handleMessage = (result) => {
|
|
3608
|
+
this.worker.off(this.#errorEvent, handleError);
|
|
3609
|
+
resolve(result);
|
|
3610
|
+
this.terminate();
|
|
3611
|
+
};
|
|
3612
|
+
const handleError = (error) => {
|
|
3613
|
+
this.worker.off(this.#messageEvent, handleMessage);
|
|
3614
|
+
reject(error);
|
|
3615
|
+
this.terminate();
|
|
3616
|
+
};
|
|
3617
|
+
this.worker.once(this.#messageEvent, handleMessage);
|
|
3618
|
+
this.worker.once(this.#errorEvent, handleError);
|
|
3619
|
+
}
|
|
3620
|
+
this.postMessage(data);
|
|
3621
|
+
});
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
const MAX_BIGINT = (1n << 256n) - 1n;
|
|
3625
|
+
function size(value) {
|
|
3626
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
3627
|
+
throw new RangeError("Expected an unsigned safe integer");
|
|
3628
|
+
}
|
|
3629
|
+
function bytes(value) {
|
|
3630
|
+
if (!(value instanceof Uint8Array))
|
|
3631
|
+
throw new TypeError("Expected Uint8Array");
|
|
3632
|
+
}
|
|
3633
|
+
function varUintLength(value) {
|
|
3634
|
+
size(value);
|
|
3635
|
+
let length = 1;
|
|
3636
|
+
while (value >= 128) {
|
|
3637
|
+
value = Math.floor(value / 128);
|
|
3638
|
+
length++;
|
|
3639
|
+
}
|
|
3640
|
+
return length;
|
|
3641
|
+
}
|
|
3642
|
+
function varBigIntLength(value) {
|
|
3643
|
+
if (typeof value !== "bigint")
|
|
3644
|
+
throw new TypeError("Expected BigInt");
|
|
3645
|
+
if (value < 0n || value > MAX_BIGINT)
|
|
3646
|
+
throw new RangeError("Expected an unsigned 256-bit integer");
|
|
3647
|
+
let length = 1;
|
|
3648
|
+
while (value >= 0x100000000000000n) {
|
|
3649
|
+
value >>= 56n;
|
|
3650
|
+
length += 8;
|
|
3651
|
+
}
|
|
3652
|
+
while (value >= 128n) {
|
|
3653
|
+
value >>= 7n;
|
|
3654
|
+
length++;
|
|
3655
|
+
}
|
|
3656
|
+
return length;
|
|
3657
|
+
}
|
|
3658
|
+
class BinaryReader {
|
|
3659
|
+
#buffer;
|
|
3660
|
+
#position = 0;
|
|
3661
|
+
constructor(buffer) {
|
|
3662
|
+
bytes(buffer);
|
|
3663
|
+
this.#buffer = buffer;
|
|
3664
|
+
}
|
|
3665
|
+
get position() {
|
|
3666
|
+
return this.#position;
|
|
3667
|
+
}
|
|
3668
|
+
get remaining() {
|
|
3669
|
+
return this.#buffer.length - this.#position;
|
|
3670
|
+
}
|
|
3671
|
+
readVarUint(canonical = true) {
|
|
3672
|
+
let result = 0;
|
|
3673
|
+
let multiplier = 1;
|
|
3674
|
+
for (let index = 0; index < 8; index++) {
|
|
3675
|
+
const cursor = this.#position + index;
|
|
3676
|
+
if (cursor >= this.#buffer.length)
|
|
3677
|
+
throw new RangeError("Truncated varint");
|
|
3678
|
+
const byte = this.#buffer[cursor];
|
|
3679
|
+
const payload = byte & 127;
|
|
3680
|
+
if (index === 7 && payload > Math.floor((Number.MAX_SAFE_INTEGER - result) / multiplier))
|
|
3681
|
+
throw new RangeError("Varint exceeds safe integer range");
|
|
3682
|
+
result += payload * multiplier;
|
|
3683
|
+
if (byte < 128) {
|
|
3684
|
+
if (canonical && index > 0 && payload === 0)
|
|
3685
|
+
throw new RangeError("Non-canonical varint");
|
|
3686
|
+
this.#position = cursor + 1;
|
|
3687
|
+
return result;
|
|
3688
|
+
}
|
|
3689
|
+
multiplier *= 128;
|
|
3690
|
+
}
|
|
3691
|
+
throw new RangeError("Varint exceeds safe integer range");
|
|
3692
|
+
}
|
|
3693
|
+
readVarBigInt(canonical = true) {
|
|
3694
|
+
let result = 0n;
|
|
3695
|
+
for (let index = 0; index < 37; index++) {
|
|
3696
|
+
const cursor = this.#position + index;
|
|
3697
|
+
if (cursor >= this.#buffer.length)
|
|
3698
|
+
throw new RangeError("Truncated varint");
|
|
3699
|
+
const byte = this.#buffer[cursor];
|
|
3700
|
+
result |= BigInt(byte & 127) << BigInt(index * 7);
|
|
3701
|
+
if (result > MAX_BIGINT)
|
|
3702
|
+
throw new RangeError("Varint exceeds 256 bits");
|
|
3703
|
+
if (byte < 128) {
|
|
3704
|
+
if (canonical && index > 0 && byte === 0)
|
|
3705
|
+
throw new RangeError("Non-canonical varint");
|
|
3706
|
+
this.#position = cursor + 1;
|
|
3707
|
+
return result;
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
throw new RangeError("Varint exceeds 256 bits");
|
|
3711
|
+
}
|
|
3712
|
+
readBytes(length) {
|
|
3713
|
+
size(length);
|
|
3714
|
+
if (length > this.remaining)
|
|
3715
|
+
throw new RangeError("Truncated binary field");
|
|
3716
|
+
const start = this.#position;
|
|
3717
|
+
this.#position += length;
|
|
3718
|
+
return this.#buffer.subarray(start, this.#position);
|
|
3719
|
+
}
|
|
3720
|
+
readFrame(canonical = true) {
|
|
3721
|
+
const start = this.#position;
|
|
3722
|
+
try {
|
|
3723
|
+
return this.readBytes(this.readVarUint(canonical));
|
|
3724
|
+
} catch (error) {
|
|
3725
|
+
this.#position = start;
|
|
3726
|
+
throw error;
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
const MAGIC = new Uint8Array([76, 70, 67, 1]);
|
|
3731
|
+
const utf8 = new TextEncoder();
|
|
3732
|
+
const text$1 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
3733
|
+
const defaults = { maxBytes: 16 * 1024 * 1024, maxDepth: 64, maxNodes: 2e5 };
|
|
3734
|
+
const BIGINT_LIMB = 1n << 49n;
|
|
3735
|
+
const BIGINT_MASK = BIGINT_LIMB - 1n;
|
|
3736
|
+
const limitsFor = (options) => {
|
|
3737
|
+
const limits = { ...defaults, ...options };
|
|
3738
|
+
for (const value of Object.values(limits))
|
|
3739
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
3740
|
+
throw new RangeError("Invalid binary limits");
|
|
3741
|
+
return limits;
|
|
3742
|
+
};
|
|
3743
|
+
function stringLength(value, maximum) {
|
|
3744
|
+
if (value.length > maximum)
|
|
3745
|
+
throw new RangeError("Invalid binary string");
|
|
3746
|
+
let length = value.length;
|
|
3747
|
+
for (let index = 0; index < value.length; index++) {
|
|
3748
|
+
const code = value.charCodeAt(index);
|
|
3749
|
+
if (code < 128)
|
|
3750
|
+
continue;
|
|
3751
|
+
if (code < 2048) {
|
|
3752
|
+
length++;
|
|
3753
|
+
continue;
|
|
3754
|
+
}
|
|
3755
|
+
if (code >= 55296 && code <= 56319) {
|
|
3756
|
+
const low = value.charCodeAt(++index);
|
|
3757
|
+
if (!(low >= 56320 && low <= 57343))
|
|
3758
|
+
throw new RangeError("Invalid binary string");
|
|
3759
|
+
length += 2;
|
|
3760
|
+
} else {
|
|
3761
|
+
if (code >= 56320 && code <= 57343)
|
|
3762
|
+
throw new RangeError("Invalid binary string");
|
|
3763
|
+
length += 2;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
return length;
|
|
3767
|
+
}
|
|
3768
|
+
function encodeValue(value, options = {}) {
|
|
3769
|
+
return encodeValueWithPrefix(value, EMPTY, options);
|
|
3770
|
+
}
|
|
3771
|
+
const EMPTY = new Uint8Array();
|
|
3772
|
+
function encodeValueWithPrefix(value, prefix, options = {}) {
|
|
3773
|
+
if (!(prefix instanceof Uint8Array))
|
|
3774
|
+
throw new TypeError("Expected Uint8Array prefix");
|
|
3775
|
+
const limits = limitsFor(options);
|
|
3776
|
+
const plan = [];
|
|
3777
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
3778
|
+
let size2 = MAGIC.length;
|
|
3779
|
+
let nodes = 0;
|
|
3780
|
+
const reserve = (length) => {
|
|
3781
|
+
size2 += length;
|
|
3782
|
+
if (size2 > limits.maxBytes)
|
|
3783
|
+
throw new RangeError("Binary value exceeds byte limit");
|
|
3784
|
+
};
|
|
3785
|
+
const string = (value2, tag) => {
|
|
3786
|
+
const length = stringLength(value2, limits.maxBytes);
|
|
3787
|
+
reserve((tag === 7 ? 1 : 0) + varUintLength(length) + length);
|
|
3788
|
+
plan.push(tag, value2, length);
|
|
3789
|
+
};
|
|
3790
|
+
const visit = (value2, depth) => {
|
|
3791
|
+
if (++nodes > limits.maxNodes || depth > limits.maxDepth)
|
|
3792
|
+
throw new RangeError("Binary value exceeds complexity limit");
|
|
3793
|
+
if (value2 === void 0 || value2 === null || typeof value2 === "boolean") {
|
|
3794
|
+
reserve(1);
|
|
3795
|
+
plan.push(value2 === void 0 ? 0 : value2 === null ? 1 : value2 === false ? 2 : 3);
|
|
3796
|
+
return;
|
|
3797
|
+
}
|
|
3798
|
+
if (typeof value2 === "number") {
|
|
3799
|
+
if (!Number.isFinite(value2))
|
|
3800
|
+
throw new TypeError("Binary numbers must be finite");
|
|
3801
|
+
reserve(9);
|
|
3802
|
+
plan.push(4, value2);
|
|
3803
|
+
return;
|
|
3568
3804
|
}
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3805
|
+
if (typeof value2 === "bigint") {
|
|
3806
|
+
const magnitude = value2 < 0n ? -value2 : value2;
|
|
3807
|
+
reserve(1 + varBigIntLength(magnitude));
|
|
3808
|
+
plan.push(value2 < 0n ? 6 : 5, magnitude);
|
|
3809
|
+
return;
|
|
3810
|
+
}
|
|
3811
|
+
if (typeof value2 === "string")
|
|
3812
|
+
return string(value2, 7);
|
|
3813
|
+
if (value2 instanceof Uint8Array) {
|
|
3814
|
+
reserve(1 + varUintLength(value2.length) + value2.length);
|
|
3815
|
+
plan.push(8, value2, value2.length);
|
|
3816
|
+
return;
|
|
3817
|
+
}
|
|
3818
|
+
if (typeof value2 !== "object")
|
|
3819
|
+
throw new TypeError("Unsupported binary value");
|
|
3820
|
+
if (ancestors.has(value2))
|
|
3821
|
+
throw new TypeError("Cyclic binary value");
|
|
3822
|
+
ancestors.add(value2);
|
|
3823
|
+
try {
|
|
3824
|
+
if (Array.isArray(value2)) {
|
|
3825
|
+
if (value2.length > limits.maxNodes - nodes)
|
|
3826
|
+
throw new RangeError("Binary array exceeds node limit");
|
|
3827
|
+
if (Object.keys(value2).length !== value2.length || Object.getOwnPropertySymbols(value2).length)
|
|
3828
|
+
throw new TypeError("Binary arrays cannot have extra properties or holes");
|
|
3829
|
+
reserve(1 + varUintLength(value2.length));
|
|
3830
|
+
plan.push(9, value2.length);
|
|
3831
|
+
for (let index = 0; index < value2.length; index++) {
|
|
3832
|
+
const entry = Object.getOwnPropertyDescriptor(value2, String(index));
|
|
3833
|
+
if (!entry || !("value" in entry))
|
|
3834
|
+
throw new TypeError("Sparse arrays and accessors are not binary values");
|
|
3835
|
+
visit(entry.value, depth + 1);
|
|
3836
|
+
}
|
|
3837
|
+
} else {
|
|
3838
|
+
const prototype = Object.getPrototypeOf(value2);
|
|
3839
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
3840
|
+
throw new TypeError("Binary maps must be plain objects");
|
|
3841
|
+
const keys = Object.keys(value2).sort();
|
|
3842
|
+
if (Object.getOwnPropertySymbols(value2).length)
|
|
3843
|
+
throw new TypeError("Symbol keys are not binary values");
|
|
3844
|
+
if (keys.length > limits.maxNodes - nodes)
|
|
3845
|
+
throw new RangeError("Binary map exceeds node limit");
|
|
3846
|
+
reserve(1 + varUintLength(keys.length));
|
|
3847
|
+
plan.push(10, keys.length);
|
|
3848
|
+
for (const key of keys) {
|
|
3849
|
+
const entry = Object.getOwnPropertyDescriptor(value2, key);
|
|
3850
|
+
if (!("value" in entry))
|
|
3851
|
+
throw new TypeError("Accessors are not binary values");
|
|
3852
|
+
string(key, 11);
|
|
3853
|
+
visit(entry.value, depth + 1);
|
|
3572
3854
|
}
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3855
|
+
}
|
|
3856
|
+
} finally {
|
|
3857
|
+
ancestors.delete(value2);
|
|
3858
|
+
}
|
|
3859
|
+
};
|
|
3860
|
+
visit(value, 0);
|
|
3861
|
+
const capacity = prefix.length + size2;
|
|
3862
|
+
if (!Number.isSafeInteger(capacity))
|
|
3863
|
+
throw new RangeError("Binary output is too large");
|
|
3864
|
+
const output = new Uint8Array(capacity);
|
|
3865
|
+
output.set(prefix);
|
|
3866
|
+
output.set(MAGIC, prefix.length);
|
|
3867
|
+
let position = prefix.length + MAGIC.length;
|
|
3868
|
+
let view;
|
|
3869
|
+
const uint = (value2) => {
|
|
3870
|
+
while (value2 >= 128) {
|
|
3871
|
+
output[position++] = value2 % 128 | 128;
|
|
3872
|
+
value2 = Math.floor(value2 / 128);
|
|
3873
|
+
}
|
|
3874
|
+
output[position++] = value2;
|
|
3875
|
+
};
|
|
3876
|
+
for (let cursor = 0; cursor < plan.length; ) {
|
|
3877
|
+
const tag = plan[cursor++];
|
|
3878
|
+
if (tag !== 11)
|
|
3879
|
+
output[position++] = tag;
|
|
3880
|
+
if (tag < 4)
|
|
3881
|
+
continue;
|
|
3882
|
+
if (tag === 4) {
|
|
3883
|
+
view ??= new DataView(output.buffer);
|
|
3884
|
+
view.setFloat64(position, plan[cursor++], false);
|
|
3885
|
+
position += 8;
|
|
3886
|
+
} else if (tag === 5 || tag === 6) {
|
|
3887
|
+
let value2 = plan[cursor++];
|
|
3888
|
+
while (value2 >= BIGINT_LIMB) {
|
|
3889
|
+
let limb = Number(value2 & BIGINT_MASK);
|
|
3890
|
+
for (let index = 0; index < 7; index++) {
|
|
3891
|
+
output[position++] = limb % 128 | 128;
|
|
3892
|
+
limb = Math.floor(limb / 128);
|
|
3578
3893
|
}
|
|
3894
|
+
value2 >>= 49n;
|
|
3895
|
+
}
|
|
3896
|
+
uint(Number(value2));
|
|
3897
|
+
} else if (tag === 7 || tag === 11) {
|
|
3898
|
+
const value2 = plan[cursor++];
|
|
3899
|
+
const length = plan[cursor++];
|
|
3900
|
+
uint(length);
|
|
3901
|
+
if (length === value2.length) {
|
|
3902
|
+
for (let index = 0; index < length; index++)
|
|
3903
|
+
output[position++] = value2.charCodeAt(index);
|
|
3904
|
+
} else {
|
|
3905
|
+
const result = utf8.encodeInto(value2, output.subarray(position, position + length));
|
|
3906
|
+
if (result.read !== value2.length || result.written !== length)
|
|
3907
|
+
throw new Error("UTF-8 planning mismatch");
|
|
3908
|
+
position += length;
|
|
3909
|
+
}
|
|
3910
|
+
} else if (tag === 8) {
|
|
3911
|
+
const value2 = plan[cursor++];
|
|
3912
|
+
const length = plan[cursor++];
|
|
3913
|
+
if (value2.length !== length)
|
|
3914
|
+
throw new RangeError("Byte input changed while encoding");
|
|
3915
|
+
uint(length);
|
|
3916
|
+
output.set(value2, position);
|
|
3917
|
+
position += length;
|
|
3918
|
+
} else {
|
|
3919
|
+
uint(plan[cursor++]);
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
if (position !== capacity)
|
|
3923
|
+
throw new Error("Binary planning mismatch");
|
|
3924
|
+
return output;
|
|
3925
|
+
}
|
|
3926
|
+
function decodeValue(input, options = {}) {
|
|
3927
|
+
const limits = limitsFor(options);
|
|
3928
|
+
if (!(input instanceof Uint8Array))
|
|
3929
|
+
throw new TypeError("Wire payload must be Uint8Array");
|
|
3930
|
+
if (input.length > limits.maxBytes)
|
|
3931
|
+
throw new RangeError("Binary value exceeds byte limit");
|
|
3932
|
+
const reader = new BinaryReader(input);
|
|
3933
|
+
if (reader.readBytes(MAGIC.length).some((byte, index) => byte !== MAGIC[index]))
|
|
3934
|
+
throw new TypeError("Unsupported binary wire version");
|
|
3935
|
+
let nodes = 0;
|
|
3936
|
+
const string = () => text$1.decode(reader.readFrame());
|
|
3937
|
+
const read = (depth) => {
|
|
3938
|
+
if (++nodes > limits.maxNodes || depth > limits.maxDepth)
|
|
3939
|
+
throw new RangeError("Binary value exceeds complexity limit");
|
|
3940
|
+
const tag = reader.readBytes(1)[0];
|
|
3941
|
+
if (tag === 0)
|
|
3942
|
+
return void 0;
|
|
3943
|
+
if (tag === 1)
|
|
3944
|
+
return null;
|
|
3945
|
+
if (tag === 2)
|
|
3946
|
+
return false;
|
|
3947
|
+
if (tag === 3)
|
|
3948
|
+
return true;
|
|
3949
|
+
if (tag === 4) {
|
|
3950
|
+
const bytes2 = reader.readBytes(8);
|
|
3951
|
+
const value2 = new DataView(bytes2.buffer, bytes2.byteOffset, 8).getFloat64(0, false);
|
|
3952
|
+
if (!Number.isFinite(value2))
|
|
3953
|
+
throw new TypeError("Binary numbers must be finite");
|
|
3954
|
+
return value2;
|
|
3955
|
+
}
|
|
3956
|
+
if (tag === 5 || tag === 6) {
|
|
3957
|
+
const value2 = reader.readVarBigInt();
|
|
3958
|
+
if (tag === 6 && value2 === 0n)
|
|
3959
|
+
throw new TypeError("Non-canonical negative zero");
|
|
3960
|
+
return tag === 6 ? -value2 : value2;
|
|
3961
|
+
}
|
|
3962
|
+
if (tag === 7)
|
|
3963
|
+
return string();
|
|
3964
|
+
if (tag === 8)
|
|
3965
|
+
return new Uint8Array(reader.readFrame());
|
|
3966
|
+
if (tag === 9 || tag === 10) {
|
|
3967
|
+
const count = reader.readVarUint();
|
|
3968
|
+
if (count > limits.maxNodes - nodes || count > reader.remaining)
|
|
3969
|
+
throw new RangeError("Invalid binary collection size");
|
|
3970
|
+
if (tag === 9)
|
|
3971
|
+
return Array.from({ length: count }, () => read(depth + 1));
|
|
3972
|
+
const result = {};
|
|
3973
|
+
let previous;
|
|
3974
|
+
for (let index = 0; index < count; index++) {
|
|
3975
|
+
const key = string();
|
|
3976
|
+
if (previous !== void 0 && key <= previous)
|
|
3977
|
+
throw new TypeError("Non-canonical binary map keys");
|
|
3978
|
+
previous = key;
|
|
3979
|
+
Object.defineProperty(result, key, { value: read(depth + 1), enumerable: true, writable: true, configurable: true });
|
|
3980
|
+
}
|
|
3981
|
+
return result;
|
|
3579
3982
|
}
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3983
|
+
throw new TypeError("Unknown binary value tag");
|
|
3984
|
+
};
|
|
3985
|
+
const value = read(0);
|
|
3986
|
+
if (reader.remaining)
|
|
3987
|
+
throw new TypeError("Trailing binary data");
|
|
3988
|
+
return value;
|
|
3989
|
+
}
|
|
3990
|
+
const encodeWire = (channel, value, limits) => encodeValue([channel, value], limits);
|
|
3991
|
+
function decodeWire(channel, bytes2, limits) {
|
|
3992
|
+
const envelope = decodeValue(bytes2, limits);
|
|
3993
|
+
if (!Array.isArray(envelope) || envelope.length !== 2 || envelope[0] !== channel)
|
|
3994
|
+
throw new TypeError("Wrong binary message channel");
|
|
3995
|
+
return envelope[1];
|
|
3996
|
+
}
|
|
3997
|
+
class BinaryWorker {
|
|
3998
|
+
#transport;
|
|
3999
|
+
#errorHandler;
|
|
4000
|
+
constructor(url, options = {}) {
|
|
4001
|
+
const transport = new EasyWorker(url, { ...options, serialization: "advanced" });
|
|
4002
|
+
if (transport && typeof transport.then === "function")
|
|
4003
|
+
return transport.then((value) => {
|
|
4004
|
+
this.#transport = value;
|
|
4005
|
+
this.worker = value.worker;
|
|
4006
|
+
return this;
|
|
4007
|
+
});
|
|
4008
|
+
this.#transport = transport;
|
|
4009
|
+
this.worker = transport.worker;
|
|
4010
|
+
}
|
|
4011
|
+
postMessage(value) {
|
|
4012
|
+
this.#transport.postMessage(encodeWire("worker", value));
|
|
4013
|
+
}
|
|
4014
|
+
onmessage(handler) {
|
|
4015
|
+
this.#transport.onmessage((bytes2) => {
|
|
4016
|
+
let value;
|
|
4017
|
+
try {
|
|
4018
|
+
value = decodeWire("worker", bytes2);
|
|
4019
|
+
} catch (error) {
|
|
4020
|
+
if (this.#errorHandler)
|
|
4021
|
+
this.#errorHandler(error);
|
|
4022
|
+
else
|
|
4023
|
+
throw error;
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
4026
|
+
handler(value);
|
|
4027
|
+
});
|
|
4028
|
+
}
|
|
4029
|
+
onerror(handler) {
|
|
4030
|
+
this.#errorHandler = handler;
|
|
4031
|
+
this.#transport.onerror(handler);
|
|
4032
|
+
}
|
|
4033
|
+
terminate() {
|
|
4034
|
+
return this.#transport.terminate();
|
|
4035
|
+
}
|
|
4036
|
+
async once(value) {
|
|
4037
|
+
let timer;
|
|
4038
|
+
try {
|
|
4039
|
+
return await new Promise((resolve, reject) => {
|
|
4040
|
+
timer = setTimeout(() => reject(new Error("Binary worker request timed out")), 1e4);
|
|
4041
|
+
this.onerror(reject);
|
|
4042
|
+
this.onmessage(resolve);
|
|
4043
|
+
this.postMessage(value);
|
|
4044
|
+
});
|
|
4045
|
+
} finally {
|
|
4046
|
+
clearTimeout(timer);
|
|
4047
|
+
this.terminate();
|
|
3617
4048
|
}
|
|
4049
|
+
}
|
|
3618
4050
|
}
|
|
3619
4051
|
|
|
3620
4052
|
class LeofcoinError extends Error {
|
|
@@ -3636,73 +4068,400 @@ class ExecutionError extends LeofcoinError {
|
|
|
3636
4068
|
class ContractDeploymentError extends LeofcoinError {
|
|
3637
4069
|
name = "ContractDeploymentError";
|
|
3638
4070
|
}
|
|
3639
|
-
const isResolveError = (error) => error.name === "ResolveError";
|
|
3640
|
-
const isExecutionError = (error) => error.name === "ExecutionError";
|
|
3641
4071
|
|
|
3642
|
-
const
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
totalMintAmount: BigInt(0),
|
|
3659
|
-
totalTransferAmount: BigInt(0),
|
|
3660
|
-
totalTransactions: BigInt(0),
|
|
3661
|
-
totalBlocks: BigInt(0)
|
|
3662
|
-
}
|
|
3663
|
-
};
|
|
3664
|
-
this.wantList = [];
|
|
3665
|
-
this.ready = new Promise((resolve) => {
|
|
3666
|
-
this.readyResolve = resolve;
|
|
3667
|
-
});
|
|
3668
|
-
this.init(blocks);
|
|
4072
|
+
const CHAIN_ID_SCHEMA = "leofcoin:chain-identity:v1";
|
|
4073
|
+
const GENESIS_CONTRACT_NAMES = ["contractFactory", "nativeToken", "nameService", "validators"];
|
|
4074
|
+
const assertChainId = (value) => {
|
|
4075
|
+
if (typeof value !== "string" || !/^lfc:[0-9a-f]{64}$/.test(value))
|
|
4076
|
+
throw new Error("Invalid chain ID; expected lfc: followed by 64 lowercase hex characters");
|
|
4077
|
+
return value;
|
|
4078
|
+
};
|
|
4079
|
+
const createChainIdentity = async (network, contracts) => {
|
|
4080
|
+
if (typeof network !== "string" || !/^[a-z0-9][a-z0-9:-]{0,79}$/.test(network))
|
|
4081
|
+
throw new Error("Invalid genesis network namespace");
|
|
4082
|
+
const genesisContracts = {};
|
|
4083
|
+
for (const name of GENESIS_CONTRACT_NAMES) {
|
|
4084
|
+
const hash = contracts?.[name];
|
|
4085
|
+
if (typeof hash !== "string" || !/^[A-Z2-7]{32,128}$/.test(hash))
|
|
4086
|
+
throw new Error(`Invalid genesis contract hash: ${name}`);
|
|
4087
|
+
genesisContracts[name] = hash;
|
|
3669
4088
|
}
|
|
3670
|
-
|
|
3671
|
-
|
|
4089
|
+
const bytes = new TextEncoder().encode(JSON.stringify({ schema: CHAIN_ID_SCHEMA, network, genesisContracts }));
|
|
4090
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
4091
|
+
const chainId = "lfc:" + [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4092
|
+
return { schema: CHAIN_ID_SCHEMA, chainId, network, genesisContracts };
|
|
4093
|
+
};
|
|
4094
|
+
const verifyChainIdentity = async (value) => {
|
|
4095
|
+
const identity = value;
|
|
4096
|
+
if (!identity || identity.schema !== CHAIN_ID_SCHEMA)
|
|
4097
|
+
throw new Error("Unsupported chain identity response");
|
|
4098
|
+
const verified = await createChainIdentity(identity.network, identity.genesisContracts);
|
|
4099
|
+
if (verified.chainId !== assertChainId(identity.chainId))
|
|
4100
|
+
throw new Error("Chain ID does not match its genesis contracts");
|
|
4101
|
+
return verified;
|
|
4102
|
+
};
|
|
4103
|
+
const findChainPresence = async (chainId, peerIds, requestIdentity, timeoutMs = 3e3) => {
|
|
4104
|
+
assertChainId(chainId);
|
|
4105
|
+
const peers = [...new Set(peerIds)].slice(0, 16);
|
|
4106
|
+
const matches = [];
|
|
4107
|
+
let responded = 0;
|
|
4108
|
+
let unavailable = 0;
|
|
4109
|
+
await Promise.all(peers.map(async (peerId) => {
|
|
4110
|
+
let timer;
|
|
4111
|
+
try {
|
|
4112
|
+
const result = await Promise.race([
|
|
4113
|
+
Promise.resolve().then(() => requestIdentity(peerId)),
|
|
4114
|
+
new Promise((_, reject) => {
|
|
4115
|
+
timer = setTimeout(() => reject(new Error("Chain identity request timed out")), timeoutMs);
|
|
4116
|
+
})
|
|
4117
|
+
]);
|
|
4118
|
+
const identity = await verifyChainIdentity(result);
|
|
4119
|
+
responded++;
|
|
4120
|
+
if (identity.chainId === chainId)
|
|
4121
|
+
matches.push(peerId);
|
|
4122
|
+
} catch {
|
|
4123
|
+
unavailable++;
|
|
4124
|
+
} finally {
|
|
4125
|
+
clearTimeout(timer);
|
|
4126
|
+
}
|
|
4127
|
+
}));
|
|
4128
|
+
return {
|
|
4129
|
+
chainId,
|
|
4130
|
+
status: matches.length ? "found" : !peers.length || unavailable ? "unknown" : "not-observed",
|
|
4131
|
+
checkedPeers: peers.length,
|
|
4132
|
+
respondedPeers: responded,
|
|
4133
|
+
unavailablePeers: unavailable,
|
|
4134
|
+
matchingPeers: matches.sort(),
|
|
4135
|
+
scope: "reachable peers only; offline or undiscovered chains may still exist"
|
|
4136
|
+
};
|
|
4137
|
+
};
|
|
4138
|
+
|
|
4139
|
+
const validateChainLink = (localTip, incoming) => {
|
|
4140
|
+
const localHash = localTip?.hash || "0x0";
|
|
4141
|
+
const localIndex = Number(localTip?.index ?? -1);
|
|
4142
|
+
const hasCanonicalTip = localHash !== "0x0";
|
|
4143
|
+
if (hasCanonicalTip && incoming.index < localIndex) return "stale";
|
|
4144
|
+
if (hasCanonicalTip && incoming.index === localIndex) {
|
|
4145
|
+
if (incoming.hash === localHash) return "duplicate";
|
|
4146
|
+
throw new Error(`Block conflict detected at index ${incoming.index}`);
|
|
3672
4147
|
}
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
4148
|
+
const expectedIndex = hasCanonicalTip ? localIndex + 1 : 0;
|
|
4149
|
+
if (incoming.index !== expectedIndex) {
|
|
4150
|
+
throw new Error(`Unexpected block index ${incoming.index}; expected ${expectedIndex}`);
|
|
4151
|
+
}
|
|
4152
|
+
if (incoming.previousHash !== localHash) {
|
|
4153
|
+
throw new Error(
|
|
4154
|
+
`previousHash mismatch at index ${incoming.index}: expected ${localHash}, got ${incoming.previousHash}`
|
|
4155
|
+
);
|
|
4156
|
+
}
|
|
4157
|
+
return "append";
|
|
4158
|
+
};
|
|
4159
|
+
|
|
4160
|
+
const CHECKPOINT_KEYS = ["checkpoint:0", "checkpoint:1"];
|
|
4161
|
+
const counters = [
|
|
4162
|
+
"nativeCalls",
|
|
4163
|
+
"nativeMints",
|
|
4164
|
+
"nativeBurns",
|
|
4165
|
+
"nativeTransfers",
|
|
4166
|
+
"totalBurnAmount",
|
|
4167
|
+
"totalMintAmount",
|
|
4168
|
+
"totalTransferAmount",
|
|
4169
|
+
"totalTransactions",
|
|
4170
|
+
"totalBlocks"
|
|
4171
|
+
];
|
|
4172
|
+
const record = (value) => value && typeof value === "object" && !Array.isArray(value);
|
|
4173
|
+
const text = (value) => typeof value === "string" ? value : new TextDecoder("utf-8", { fatal: true }).decode(value);
|
|
4174
|
+
const digest = async (value) => [...new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4175
|
+
function validateCheckpoint(snapshot, chainId, requiredContracts) {
|
|
4176
|
+
if (!record(snapshot) || snapshot.chainId !== chainId || !record(snapshot.states) || !record(snapshot.accounts) || !record(snapshot.info))
|
|
4177
|
+
throw new Error("Invalid checkpoint or chain identity");
|
|
4178
|
+
const tip = snapshot.lastBlock;
|
|
4179
|
+
const index = Number(tip?.index);
|
|
4180
|
+
if (!["bigint", "number"].includes(typeof tip?.index) || !Number.isSafeInteger(index) || index < -1 || (index === -1 ? tip.hash !== "0x0" : typeof tip.hash !== "string" || !tip.hash || tip.hash === "0x0"))
|
|
4181
|
+
throw new Error("Invalid checkpoint tip");
|
|
4182
|
+
for (const contract of requiredContracts) if (!record(snapshot.states[contract])) throw new Error(`Incomplete checkpoint contract: ${contract}`);
|
|
4183
|
+
for (const [address, nonce] of Object.entries(snapshot.accounts))
|
|
4184
|
+
if (!address || typeof nonce !== "string" || !/^(0|[1-9]\d*)$/.test(nonce)) throw new Error("Invalid checkpoint nonce");
|
|
4185
|
+
for (const counter of counters)
|
|
4186
|
+
if (typeof snapshot.info[counter] !== "bigint" || snapshot.info[counter] < 0n) throw new Error(`Invalid checkpoint counter: ${counter}`);
|
|
4187
|
+
if (snapshot.info.totalBlocks !== BigInt(index) + 1n) throw new Error("Checkpoint block count does not match tip");
|
|
4188
|
+
return snapshot;
|
|
4189
|
+
}
|
|
4190
|
+
async function writeCheckpoint(store, snapshot, generation, requiredContracts) {
|
|
4191
|
+
if (!Number.isSafeInteger(generation) || generation < 0) throw new Error("Invalid checkpoint generation");
|
|
4192
|
+
validateCheckpoint(snapshot, snapshot.chainId, requiredContracts);
|
|
4193
|
+
const payload = JSON.stringify({ generation, snapshot }, jsonStringifyBigInt);
|
|
4194
|
+
await store.put(CHECKPOINT_KEYS[generation % 2], new TextEncoder().encode(JSON.stringify({ schema: 1, payload, checksum: await digest(payload) })));
|
|
4195
|
+
}
|
|
4196
|
+
async function readCheckpoint(store, chainId, requiredContracts) {
|
|
4197
|
+
const valid = [];
|
|
4198
|
+
let present = 0;
|
|
4199
|
+
const keys = new Set(await store.keys());
|
|
4200
|
+
for (const key of CHECKPOINT_KEYS) {
|
|
4201
|
+
if (!keys.has(key)) continue;
|
|
4202
|
+
present++;
|
|
4203
|
+
const bytes = await store.get(key);
|
|
4204
|
+
try {
|
|
4205
|
+
const envelope = JSON.parse(text(bytes));
|
|
4206
|
+
if (envelope.schema !== 1 || typeof envelope.payload !== "string" || envelope.checksum !== await digest(envelope.payload)) throw new Error("Invalid checkpoint checksum");
|
|
4207
|
+
const result = JSON.parse(envelope.payload, jsonParseBigInt);
|
|
4208
|
+
if (!Number.isSafeInteger(result.generation) || result.generation < 0 || result.generation % 2 !== CHECKPOINT_KEYS.indexOf(key)) throw new Error("Invalid checkpoint generation");
|
|
4209
|
+
validateCheckpoint(result.snapshot, chainId, requiredContracts);
|
|
4210
|
+
valid.push(result);
|
|
4211
|
+
} catch {
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
if (present && !valid.length) throw new Error("No valid state checkpoint remains; preserve this data root and rebuild in a separate root");
|
|
4215
|
+
return valid.sort((left, right) => right.generation - left.generation)[0];
|
|
4216
|
+
}
|
|
4217
|
+
async function recoveryHistory(hash, readBlock) {
|
|
4218
|
+
const reversed = [];
|
|
4219
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4220
|
+
while (hash !== "0x0") {
|
|
4221
|
+
if (!hash || seen.has(hash)) throw new Error("Invalid or cyclic recovery ancestry");
|
|
4222
|
+
seen.add(hash);
|
|
4223
|
+
const bytes = await readBlock(hash);
|
|
4224
|
+
if (!bytes) throw new Error(`Recovery block unavailable: ${hash}`);
|
|
4225
|
+
const message = new BlockMessage(bytes);
|
|
4226
|
+
if (await message.hash() !== hash) throw new Error(`Recovery block hash mismatch: ${hash}`);
|
|
4227
|
+
reversed.push({ ...message.decoded, hash, loaded: false });
|
|
4228
|
+
hash = message.decoded.previousHash;
|
|
4229
|
+
}
|
|
4230
|
+
const history = reversed.reverse();
|
|
4231
|
+
let tip = { index: -1, hash: "0x0" };
|
|
4232
|
+
for (const block of history) {
|
|
4233
|
+
const index = Number(block.index);
|
|
4234
|
+
if (!Number.isSafeInteger(index) || index < 0) throw new Error("Invalid recovery block index");
|
|
4235
|
+
if (validateChainLink(tip, { index, hash: block.hash, previousHash: block.previousHash }) !== "append") throw new Error("Invalid recovery chain link");
|
|
4236
|
+
tip = { index, hash: block.hash };
|
|
4237
|
+
}
|
|
4238
|
+
return history;
|
|
4239
|
+
}
|
|
4240
|
+
async function restoreAccountNonces(store, accounts) {
|
|
4241
|
+
await store.clear();
|
|
4242
|
+
for (const [address, nonce] of Object.entries(accounts)) await store.put(address, String(nonce));
|
|
4243
|
+
}
|
|
4244
|
+
|
|
4245
|
+
class ReadOnlyMachine {
|
|
4246
|
+
constructor(workerPath, readContract) {
|
|
4247
|
+
this.workerPath = workerPath;
|
|
4248
|
+
this.readContract = readContract;
|
|
4249
|
+
}
|
|
4250
|
+
#worker;
|
|
4251
|
+
#pending = /* @__PURE__ */ new Map();
|
|
4252
|
+
#tail = Promise.resolve();
|
|
4253
|
+
#queued = 0;
|
|
4254
|
+
#seed;
|
|
4255
|
+
#cache = /* @__PURE__ */ new Map();
|
|
4256
|
+
#cacheBytes = 0;
|
|
4257
|
+
#closed = false;
|
|
4258
|
+
read(seed, contract, method, params = [], sender = "leofcoin:read-only") {
|
|
4259
|
+
if (this.#closed) return Promise.reject(new Error("Read-only machine is closed"));
|
|
4260
|
+
if (typeof contract !== "string" || !contract || typeof method !== "string" || !method || ["constructor", "__proto__", "prototype"].includes(method) || !Array.isArray(params))
|
|
4261
|
+
return Promise.reject(new Error("Invalid read-only contract query"));
|
|
4262
|
+
if (this.#queued >= 32) return Promise.reject(new Error("Read-only query queue is full"));
|
|
4263
|
+
let key;
|
|
4264
|
+
try {
|
|
4265
|
+
key = JSON.stringify({ contract, method, params, sender }, jsonStringifyBigInt);
|
|
4266
|
+
if (new TextEncoder().encode(key).byteLength > 65536) throw new Error("Read-only query is too large");
|
|
4267
|
+
params = structuredClone(params);
|
|
4268
|
+
} catch (error) {
|
|
4269
|
+
return Promise.reject(error);
|
|
4270
|
+
}
|
|
4271
|
+
this.#queued++;
|
|
4272
|
+
const result = this.#tail.then(async () => {
|
|
4273
|
+
if (this.#closed) throw new Error("Read-only machine is closed");
|
|
4274
|
+
if (this.#seed !== seed) {
|
|
4275
|
+
this.#seed = seed;
|
|
4276
|
+
this.#cache.clear();
|
|
4277
|
+
this.#cacheBytes = 0;
|
|
4278
|
+
}
|
|
4279
|
+
if (this.#cache.has(key)) return structuredClone(this.#cache.get(key).value);
|
|
4280
|
+
try {
|
|
4281
|
+
await this.#start();
|
|
4282
|
+
const initialized = await this.#request("init", {
|
|
4283
|
+
readOnly: true,
|
|
4284
|
+
fromState: true,
|
|
4285
|
+
blocks: [],
|
|
4286
|
+
peerid: "leofcoin:read-only",
|
|
4287
|
+
lastBlock: seed.lastBlock,
|
|
4288
|
+
state: seed.states,
|
|
4289
|
+
info: seed.info
|
|
4290
|
+
});
|
|
4291
|
+
if (initialized !== true) throw new Error("Worker does not support isolated contract queries");
|
|
4292
|
+
const value = await this.#request("read", { contract, method, params, sender });
|
|
4293
|
+
const bytes = new TextEncoder().encode(JSON.stringify(value, jsonStringifyBigInt) ?? "").byteLength;
|
|
4294
|
+
if (bytes > 4 * 1024 * 1024) throw new Error("Read-only result is too large");
|
|
4295
|
+
while (this.#cache.size && (this.#cache.size >= 64 || this.#cacheBytes + bytes > 4 * 1024 * 1024)) {
|
|
4296
|
+
const oldest = this.#cache.keys().next().value;
|
|
4297
|
+
this.#cacheBytes -= this.#cache.get(oldest).bytes;
|
|
4298
|
+
this.#cache.delete(oldest);
|
|
4299
|
+
}
|
|
4300
|
+
this.#cache.set(key, { value: structuredClone(value), bytes });
|
|
4301
|
+
this.#cacheBytes += bytes;
|
|
4302
|
+
return value;
|
|
4303
|
+
} catch (error) {
|
|
4304
|
+
this.#stop();
|
|
4305
|
+
throw error;
|
|
4306
|
+
}
|
|
4307
|
+
});
|
|
4308
|
+
this.#tail = result.then(() => void 0, () => void 0);
|
|
4309
|
+
return result.finally(() => {
|
|
4310
|
+
this.#queued--;
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
async #start() {
|
|
4314
|
+
if (this.#worker) return;
|
|
4315
|
+
const worker = await new BinaryWorker(this.workerPath, { serialization: "advanced", type: "module" });
|
|
4316
|
+
if (this.#closed) {
|
|
4317
|
+
worker.terminate();
|
|
4318
|
+
throw new Error("Read-only machine is closed");
|
|
4319
|
+
}
|
|
4320
|
+
this.#worker = worker;
|
|
4321
|
+
worker.onmessage(async (message) => {
|
|
4322
|
+
if (this.#worker !== worker) return;
|
|
4323
|
+
if (message.type === "ask") {
|
|
4324
|
+
try {
|
|
4325
|
+
const input = message.question === "peers" ? [] : message.question === "contract" ? await this.readContract(message.input) : void 0;
|
|
4326
|
+
if (this.#worker === worker) worker.postMessage({ id: message.id, input });
|
|
4327
|
+
} catch {
|
|
4328
|
+
if (this.#worker === worker) worker.postMessage({ id: message.id, input: void 0 });
|
|
4329
|
+
}
|
|
4330
|
+
return;
|
|
4331
|
+
}
|
|
4332
|
+
const pending = this.#pending.get(message.id);
|
|
4333
|
+
if (!pending) return;
|
|
4334
|
+
if (message.type !== "response" && !["initError", "readError"].includes(message.type)) return;
|
|
4335
|
+
clearTimeout(pending.timer);
|
|
4336
|
+
this.#pending.delete(message.id);
|
|
4337
|
+
if (message.type === "response") pending.resolve(message.value);
|
|
4338
|
+
else pending.reject(new Error(message.message || "Read-only worker failed"));
|
|
4339
|
+
});
|
|
4340
|
+
worker.onerror(() => {
|
|
4341
|
+
if (this.#worker === worker) this.#stop();
|
|
4342
|
+
});
|
|
4343
|
+
worker.worker?.once?.("exit", () => {
|
|
4344
|
+
if (this.#worker === worker) this.#stop();
|
|
4345
|
+
});
|
|
4346
|
+
}
|
|
4347
|
+
#request(type, input) {
|
|
4348
|
+
return new Promise((resolve, reject) => {
|
|
4349
|
+
const id = crypto.randomUUID();
|
|
4350
|
+
const timer = setTimeout(() => this.#stop(new Error("Read-only query timed out")), 1e4);
|
|
4351
|
+
this.#pending.set(id, { resolve, reject, timer });
|
|
4352
|
+
try {
|
|
4353
|
+
this.#worker.postMessage({ id, type, input });
|
|
4354
|
+
} catch (error) {
|
|
4355
|
+
this.#stop(error);
|
|
4356
|
+
}
|
|
4357
|
+
});
|
|
4358
|
+
}
|
|
4359
|
+
#stop(error = new Error("Read-only worker stopped")) {
|
|
4360
|
+
const worker = this.#worker;
|
|
4361
|
+
this.#worker = void 0;
|
|
4362
|
+
for (const pending of this.#pending.values()) {
|
|
4363
|
+
clearTimeout(pending.timer);
|
|
4364
|
+
pending.reject(error);
|
|
4365
|
+
}
|
|
4366
|
+
this.#pending.clear();
|
|
4367
|
+
worker?.terminate();
|
|
4368
|
+
}
|
|
4369
|
+
close() {
|
|
4370
|
+
this.#closed = true;
|
|
4371
|
+
this.#cache.clear();
|
|
4372
|
+
this.#cacheBytes = 0;
|
|
4373
|
+
this.#seed = void 0;
|
|
4374
|
+
this.#stop();
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
|
|
4378
|
+
const genesisContracts = {
|
|
4379
|
+
contractFactory: addresses.contractFactory,
|
|
4380
|
+
nativeToken: addresses.nativeToken,
|
|
4381
|
+
nameService: addresses.nameService,
|
|
4382
|
+
validators: addresses.validators
|
|
4383
|
+
};
|
|
4384
|
+
const debug$2 = createDebugger("leofcoin/machine");
|
|
4385
|
+
class Machine {
|
|
4386
|
+
constructor(blocks = [], checkpoint) {
|
|
4387
|
+
this.states = {
|
|
4388
|
+
states: {},
|
|
4389
|
+
lastBlock: {
|
|
4390
|
+
index: -1,
|
|
4391
|
+
hash: "0x0"
|
|
4392
|
+
},
|
|
4393
|
+
accounts: {},
|
|
4394
|
+
info: {
|
|
4395
|
+
nativeCalls: BigInt(0),
|
|
4396
|
+
nativeMints: BigInt(0),
|
|
4397
|
+
nativeBurns: BigInt(0),
|
|
4398
|
+
nativeTransfers: BigInt(0),
|
|
4399
|
+
totalBurnAmount: BigInt(0),
|
|
4400
|
+
totalMintAmount: BigInt(0),
|
|
4401
|
+
totalTransferAmount: BigInt(0),
|
|
4402
|
+
totalTransactions: BigInt(0),
|
|
4403
|
+
totalBlocks: BigInt(0)
|
|
4404
|
+
}
|
|
4405
|
+
};
|
|
4406
|
+
this.wantList = [];
|
|
4407
|
+
this.ready = new Promise((resolve, reject) => {
|
|
4408
|
+
this.readyResolve = resolve;
|
|
4409
|
+
this.readyReject = reject;
|
|
4410
|
+
});
|
|
4411
|
+
this.#checkpointGeneration = -1;
|
|
4412
|
+
this.#closed = false;
|
|
4413
|
+
this.init(blocks, checkpoint).then(() => this.readyResolve(this), (error) => {
|
|
4414
|
+
this.close();
|
|
4415
|
+
this.readyReject(error);
|
|
4416
|
+
});
|
|
4417
|
+
}
|
|
4418
|
+
#checkpointGeneration;
|
|
4419
|
+
#chainId;
|
|
4420
|
+
#reader;
|
|
4421
|
+
#contractReader;
|
|
4422
|
+
#readSnapshot;
|
|
4423
|
+
#workerPath;
|
|
4424
|
+
#closed;
|
|
4425
|
+
close() {
|
|
4426
|
+
this.#closed = true;
|
|
4427
|
+
this.#reader?.close();
|
|
4428
|
+
this.#contractReader?.close();
|
|
4429
|
+
this.worker?.terminate();
|
|
4430
|
+
}
|
|
4431
|
+
async init(blocks, checkpoint) {
|
|
4432
|
+
return this.#init(blocks, checkpoint);
|
|
4433
|
+
}
|
|
4434
|
+
async #onmessage(data) {
|
|
4435
|
+
if (this.#closed) return;
|
|
4436
|
+
switch (data.type) {
|
|
4437
|
+
case "contractError": {
|
|
4438
|
+
console.warn(data.message ?? data.error);
|
|
4439
|
+
console.warn(`contract error: ${data.hash}`);
|
|
4440
|
+
break;
|
|
4441
|
+
}
|
|
4442
|
+
case "initError": {
|
|
4443
|
+
pubsub.publish(data.id, { error: new Error(`Machine initialization failed: ${data.message}`) });
|
|
4444
|
+
break;
|
|
4445
|
+
}
|
|
4446
|
+
case "executeError": {
|
|
4447
|
+
pubsub.publish(data.id, { error: data.message });
|
|
4448
|
+
break;
|
|
4449
|
+
}
|
|
4450
|
+
case "addLoadedBlockError":
|
|
4451
|
+
case "runContractError":
|
|
3701
4452
|
case "collectFeeError":
|
|
3702
4453
|
case "settleRewardsError": {
|
|
3703
4454
|
pubsub.publish(data.id, { error: new Error(data.message) });
|
|
3704
4455
|
break;
|
|
3705
4456
|
}
|
|
4457
|
+
case "snapshotError": {
|
|
4458
|
+
pubsub.publish(data.id, { error: new Error(data.message) });
|
|
4459
|
+
break;
|
|
4460
|
+
}
|
|
4461
|
+
case "getError": {
|
|
4462
|
+
pubsub.publish(data.id, { error: new Error(data.message) });
|
|
4463
|
+
break;
|
|
4464
|
+
}
|
|
3706
4465
|
case "debug": {
|
|
3707
4466
|
if (data.message.includes("loaded transactions for block:")) {
|
|
3708
4467
|
pubsub.publish("block-loaded", data.message.replace("loaded transactions for block: ", "").split(" @")[0]);
|
|
@@ -3713,13 +4472,8 @@ class Machine {
|
|
|
3713
4472
|
console.error(data.message);
|
|
3714
4473
|
break;
|
|
3715
4474
|
}
|
|
3716
|
-
case "machine-ready": {
|
|
3717
|
-
this.readyResolve(this);
|
|
3718
|
-
pubsub.publish("machine.ready", true);
|
|
3719
|
-
break;
|
|
3720
|
-
}
|
|
3721
4475
|
case "response": {
|
|
3722
|
-
pubsub.publish(data.id, data.value
|
|
4476
|
+
pubsub.publish(data.id, data.value ?? false);
|
|
3723
4477
|
break;
|
|
3724
4478
|
}
|
|
3725
4479
|
case "addToWantList": {
|
|
@@ -3727,6 +4481,16 @@ class Machine {
|
|
|
3727
4481
|
break;
|
|
3728
4482
|
}
|
|
3729
4483
|
case "ask": {
|
|
4484
|
+
if (data.question === "staticCall") {
|
|
4485
|
+
try {
|
|
4486
|
+
const { snapshot, contract, method, params, sender } = data.input;
|
|
4487
|
+
const input = await this.#contractReader.read(snapshot, contract, method, params, sender);
|
|
4488
|
+
if (!this.#closed) this.worker.postMessage({ id: data.id, input });
|
|
4489
|
+
} catch (error) {
|
|
4490
|
+
if (!this.#closed) this.worker.postMessage({ id: data.id, error: error instanceof Error ? error.message : String(error) });
|
|
4491
|
+
}
|
|
4492
|
+
break;
|
|
4493
|
+
}
|
|
3730
4494
|
if (data.question === "contract" || data.question === "transaction") {
|
|
3731
4495
|
try {
|
|
3732
4496
|
const input = await peernet.get(data.input, data.question);
|
|
@@ -3754,164 +4518,61 @@ class Machine {
|
|
|
3754
4518
|
}
|
|
3755
4519
|
async updateState(strict = false) {
|
|
3756
4520
|
try {
|
|
3757
|
-
const
|
|
3758
|
-
|
|
3759
|
-
const
|
|
3760
|
-
|
|
3761
|
-
const
|
|
3762
|
-
|
|
3763
|
-
for (const block of blocks) {
|
|
3764
|
-
if (block?.transactions) transactions = [...transactions, ...block.transactions];
|
|
3765
|
-
}
|
|
3766
|
-
transactions = await Promise.all(
|
|
3767
|
-
transactions.map(async (transaction) => new TransactionMessage(await transactionStore.get(transaction)))
|
|
3768
|
-
);
|
|
3769
|
-
const contractsToGet = transactions.reduce((set, current) => {
|
|
3770
|
-
const contract = current.decoded.to;
|
|
3771
|
-
if (!set.includes(contract)) set.push(contract);
|
|
3772
|
-
return set;
|
|
3773
|
-
}, []);
|
|
3774
|
-
const state = {};
|
|
3775
|
-
if (!contractsToGet.includes(addresses.contractFactory)) contractsToGet.push(addresses.contractFactory);
|
|
3776
|
-
if (!contractsToGet.includes(addresses.nativeToken)) contractsToGet.push(addresses.nativeToken);
|
|
3777
|
-
if (!contractsToGet.includes(addresses.nameService)) contractsToGet.push(addresses.nameService);
|
|
3778
|
-
if (!contractsToGet.includes(addresses.validators)) contractsToGet.push(addresses.validators);
|
|
3779
|
-
await Promise.all(
|
|
3780
|
-
contractsToGet.map(async (contract) => {
|
|
3781
|
-
const value = await this.#askWorker("get", { contract, method: "state", params: [] });
|
|
3782
|
-
state[contract] = value;
|
|
3783
|
-
})
|
|
3784
|
-
);
|
|
3785
|
-
let accounts = {};
|
|
3786
|
-
try {
|
|
3787
|
-
const promises = [];
|
|
3788
|
-
for (const address of await accountsStore.keys()) {
|
|
3789
|
-
promises.push(
|
|
3790
|
-
(async () => {
|
|
3791
|
-
accounts[address] = await accountsStore.get(address);
|
|
3792
|
-
})()
|
|
3793
|
-
);
|
|
3794
|
-
}
|
|
3795
|
-
await Promise.all(promises);
|
|
3796
|
-
} catch (error) {
|
|
3797
|
-
const promises = [];
|
|
3798
|
-
for (const transaction of transactions.reverse()) {
|
|
3799
|
-
const { from, to, amount, nonce } = transaction.decoded.data;
|
|
3800
|
-
if (!accounts[from]) {
|
|
3801
|
-
accounts[from] = nonce;
|
|
3802
|
-
promises.push(accountsStore.put(from, nonce));
|
|
3803
|
-
}
|
|
3804
|
-
}
|
|
3805
|
-
await Promise.all(promises);
|
|
3806
|
-
}
|
|
3807
|
-
const sortedStringify = (obj, replacer) => {
|
|
3808
|
-
const sorted = {};
|
|
3809
|
-
const keys = Object.keys(obj).sort();
|
|
3810
|
-
for (const key of keys) {
|
|
3811
|
-
sorted[key] = obj[key];
|
|
3812
|
-
}
|
|
3813
|
-
return JSON.stringify(sorted, replacer);
|
|
3814
|
-
};
|
|
3815
|
-
const tasks = [
|
|
3816
|
-
stateStore.put("lastBlock", sortedStringify(lastBlock, jsonStringifyBigInt)),
|
|
3817
|
-
stateStore.put("states", sortedStringify(state, jsonStringifyBigInt)),
|
|
3818
|
-
stateStore.put("accounts", sortedStringify(accounts, jsonStringifyBigInt)),
|
|
3819
|
-
stateStore.put(
|
|
3820
|
-
"info",
|
|
3821
|
-
sortedStringify(
|
|
3822
|
-
{
|
|
3823
|
-
nativeBurnAmount: await this.totalBurnAmount,
|
|
3824
|
-
nativeBurns: await this.nativeBurns,
|
|
3825
|
-
nativeCalls: await this.nativeCalls,
|
|
3826
|
-
nativeMints: await this.nativeMints,
|
|
3827
|
-
nativeTransfers: await this.nativeTransfers,
|
|
3828
|
-
totalBlocks: await blockStore.length,
|
|
3829
|
-
totalBurnAmount: await this.totalBurnAmount,
|
|
3830
|
-
totalMintAmount: await this.totalMintAmount,
|
|
3831
|
-
totalTransactions: await this.totalTransactions,
|
|
3832
|
-
totalTransferAmount: await this.totalTransferAmount
|
|
3833
|
-
},
|
|
3834
|
-
jsonStringifyBigInt
|
|
3835
|
-
)
|
|
3836
|
-
)
|
|
3837
|
-
// accountsStore.clear()
|
|
3838
|
-
];
|
|
3839
|
-
await Promise.all(tasks);
|
|
4521
|
+
const snapshot = await this.#askWorker("snapshot");
|
|
4522
|
+
snapshot.chainId = this.#chainId;
|
|
4523
|
+
const accounts = {};
|
|
4524
|
+
for (const address of await accountsStore.keys()) {
|
|
4525
|
+
const raw = await accountsStore.get(address);
|
|
4526
|
+
accounts[address] = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
|
|
3840
4527
|
}
|
|
4528
|
+
snapshot.accounts = accounts;
|
|
4529
|
+
const generation = this.#checkpointGeneration + 1;
|
|
4530
|
+
await writeCheckpoint(stateStore, snapshot, generation, Object.values(genesisContracts));
|
|
4531
|
+
this.#checkpointGeneration = generation;
|
|
4532
|
+
this.states = snapshot;
|
|
4533
|
+
this.#readSnapshot = structuredClone(snapshot);
|
|
3841
4534
|
} catch (error) {
|
|
3842
|
-
console.error(error);
|
|
3843
4535
|
if (strict) throw error;
|
|
4536
|
+
console.error(error);
|
|
4537
|
+
throw error;
|
|
3844
4538
|
}
|
|
3845
4539
|
}
|
|
3846
|
-
async #init(blocks) {
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
rawInfo = decode(await stateStore.get("info"));
|
|
3880
|
-
this.states.states = JSON.parse(rawStates, jsonParseBigInt);
|
|
3881
|
-
this.states.accounts = JSON.parse(rawAccounts, jsonParseBigInt);
|
|
3882
|
-
this.states.info = JSON.parse(rawInfo, jsonParseBigInt);
|
|
3883
|
-
} catch (error) {
|
|
3884
|
-
console.error(error);
|
|
3885
|
-
this.states.accounts = {};
|
|
3886
|
-
this.states.info = {
|
|
3887
|
-
nativeCalls: BigInt(0),
|
|
3888
|
-
nativeMints: BigInt(0),
|
|
3889
|
-
nativeBurns: BigInt(0),
|
|
3890
|
-
nativeTransfers: BigInt(0),
|
|
3891
|
-
totalBurnAmount: BigInt(0),
|
|
3892
|
-
totalMintAmount: BigInt(0),
|
|
3893
|
-
totalTransferAmount: BigInt(0),
|
|
3894
|
-
totalTransactions: BigInt(0),
|
|
3895
|
-
totalBlocks: BigInt(0)
|
|
3896
|
-
};
|
|
3897
|
-
rawInfo = JSON.stringify(this.states.info, jsonStringifyBigInt);
|
|
3898
|
-
await stateStore.put("info", new TextEncoder().encode(rawInfo));
|
|
3899
|
-
}
|
|
3900
|
-
}
|
|
3901
|
-
const message = {
|
|
3902
|
-
type: "init",
|
|
3903
|
-
input: {
|
|
3904
|
-
blocks,
|
|
3905
|
-
fromState: this.states.lastBlock?.index >= 0,
|
|
3906
|
-
lastBlock: rawLastBlock,
|
|
3907
|
-
state: rawStates,
|
|
3908
|
-
info: rawInfo,
|
|
3909
|
-
// @ts-ignore
|
|
3910
|
-
peerid: peernet.peerId
|
|
3911
|
-
}
|
|
3912
|
-
};
|
|
3913
|
-
this.worker.postMessage(message);
|
|
4540
|
+
async #init(blocks, checkpoint) {
|
|
4541
|
+
const identity = await createChainIdentity(globalThis.peernet?.network || "leofcoin:peach", addresses);
|
|
4542
|
+
this.#chainId = identity.chainId;
|
|
4543
|
+
if (checkpoint) {
|
|
4544
|
+
this.#checkpointGeneration = checkpoint.generation;
|
|
4545
|
+
this.states = checkpoint.snapshot;
|
|
4546
|
+
}
|
|
4547
|
+
let pre = "./";
|
|
4548
|
+
try {
|
|
4549
|
+
const { fileURLToPath } = await import('url');
|
|
4550
|
+
pre = fileURLToPath(new URL(".", import.meta.url));
|
|
4551
|
+
} catch {
|
|
4552
|
+
}
|
|
4553
|
+
this.#workerPath = pre + "workers/machine-worker.js";
|
|
4554
|
+
const readContract = (hash) => globalThis.contractStore.get(hash);
|
|
4555
|
+
this.#reader = new ReadOnlyMachine(this.#workerPath, readContract);
|
|
4556
|
+
this.#contractReader = new ReadOnlyMachine(this.#workerPath, readContract);
|
|
4557
|
+
this.worker = await new BinaryWorker(this.#workerPath, {
|
|
4558
|
+
serialization: "advanced",
|
|
4559
|
+
type: "module"
|
|
4560
|
+
});
|
|
4561
|
+
if (this.#closed) {
|
|
4562
|
+
this.worker.terminate();
|
|
4563
|
+
throw new Error("State machine is closed");
|
|
4564
|
+
}
|
|
4565
|
+
this.worker.onmessage(this.#onmessage.bind(this));
|
|
4566
|
+
const initialized = await this.#askWorker("init", {
|
|
4567
|
+
blocks,
|
|
4568
|
+
fromState: Boolean(checkpoint),
|
|
4569
|
+
lastBlock: checkpoint?.snapshot.lastBlock,
|
|
4570
|
+
state: checkpoint?.snapshot.states,
|
|
4571
|
+
info: checkpoint?.snapshot.info,
|
|
4572
|
+
peerid: peernet.peerId
|
|
3914
4573
|
});
|
|
4574
|
+
if (initialized !== true) throw new Error("Worker does not support checkpoint initialization; deploy matching chain and worker builds");
|
|
4575
|
+
this.#readSnapshot = structuredClone(checkpoint?.snapshot ?? await this.#askWorker("snapshot"));
|
|
3915
4576
|
}
|
|
3916
4577
|
async #runContract(contractMessage) {
|
|
3917
4578
|
const hash = await contractMessage.hash();
|
|
@@ -3942,6 +4603,7 @@ class Machine {
|
|
|
3942
4603
|
* @returns Promise<message>
|
|
3943
4604
|
*/
|
|
3944
4605
|
async execute(contract, method, parameters, sender, execution) {
|
|
4606
|
+
if (this.#closed) throw new Error("State machine is closed");
|
|
3945
4607
|
try {
|
|
3946
4608
|
if (contract === contractFactory && method === "registerContract") {
|
|
3947
4609
|
if (await this.has(parameters[0])) throw new Error(`duplicate contract @${parameters[0]}`);
|
|
@@ -3998,28 +4660,8 @@ ${error.message}`);
|
|
|
3998
4660
|
return this.#askWorker("settleRewards", { rewards });
|
|
3999
4661
|
}
|
|
4000
4662
|
get(contract, method, parameters) {
|
|
4001
|
-
return new
|
|
4002
|
-
|
|
4003
|
-
const timeout = setTimeout(() => {
|
|
4004
|
-
pubsub.unsubscribe(id, onmessage);
|
|
4005
|
-
reject(new Error(`Machine.get timeout for ${contract}.${method}`));
|
|
4006
|
-
}, 3e4);
|
|
4007
|
-
const onmessage = (message) => {
|
|
4008
|
-
clearTimeout(timeout);
|
|
4009
|
-
pubsub.unsubscribe(id, onmessage);
|
|
4010
|
-
resolve(message);
|
|
4011
|
-
};
|
|
4012
|
-
pubsub.subscribe(id, onmessage);
|
|
4013
|
-
this.worker.postMessage({
|
|
4014
|
-
type: "get",
|
|
4015
|
-
id,
|
|
4016
|
-
input: {
|
|
4017
|
-
contract,
|
|
4018
|
-
method,
|
|
4019
|
-
params: parameters
|
|
4020
|
-
}
|
|
4021
|
-
});
|
|
4022
|
-
});
|
|
4663
|
+
if (!this.#readSnapshot) return Promise.reject(new Error("Contract read state is not initialized"));
|
|
4664
|
+
return this.#reader.read(this.#readSnapshot, contract, method, parameters ?? []);
|
|
4023
4665
|
}
|
|
4024
4666
|
async has(address) {
|
|
4025
4667
|
return new Promise((resolve, reject) => {
|
|
@@ -4045,6 +4687,7 @@ ${error.message}`);
|
|
|
4045
4687
|
});
|
|
4046
4688
|
}
|
|
4047
4689
|
#askWorker(type, input) {
|
|
4690
|
+
if (this.#closed) return Promise.reject(new Error("State machine is closed"));
|
|
4048
4691
|
return new Promise((resolve, reject) => {
|
|
4049
4692
|
const id = randombytes(20).toString("hex");
|
|
4050
4693
|
const timeout = setTimeout(() => {
|
|
@@ -4102,19 +4745,13 @@ ${error.message}`);
|
|
|
4102
4745
|
return this.getBlocks();
|
|
4103
4746
|
}
|
|
4104
4747
|
get lastBlock() {
|
|
4105
|
-
return this
|
|
4106
|
-
debug$2("lastBlock fallback after worker timeout:", error?.message ?? error);
|
|
4107
|
-
return this.states.lastBlock;
|
|
4108
|
-
});
|
|
4748
|
+
return Promise.resolve(this.states.lastBlock);
|
|
4109
4749
|
}
|
|
4110
4750
|
getCanonicalTip() {
|
|
4111
4751
|
return this.#askWorker("lastBlock");
|
|
4112
4752
|
}
|
|
4113
4753
|
get lastBlockHeight() {
|
|
4114
|
-
return
|
|
4115
|
-
debug$2("lastBlockHeight fallback after worker timeout:", error?.message ?? error);
|
|
4116
|
-
return Number(this.states.lastBlock?.index ?? 0);
|
|
4117
|
-
});
|
|
4754
|
+
return Promise.resolve(Number(this.states.lastBlock.index));
|
|
4118
4755
|
}
|
|
4119
4756
|
getBlocks(from, to) {
|
|
4120
4757
|
return this.#askWorker("blocks", { from, to });
|
|
@@ -4125,7 +4762,7 @@ ${error.message}`);
|
|
|
4125
4762
|
async addLoadedBlock(block) {
|
|
4126
4763
|
debug$2(`adding loaded block: ${block.index}@${block.hash}`);
|
|
4127
4764
|
if (block.decoded) block = { ...block.decoded, hash: await block.hash() };
|
|
4128
|
-
return this.#askWorker("addLoadedBlock",
|
|
4765
|
+
return this.#askWorker("addLoadedBlock", block);
|
|
4129
4766
|
}
|
|
4130
4767
|
async latestTransactions() {
|
|
4131
4768
|
return this.#askWorker("latestTransactions");
|
|
@@ -4173,91 +4810,19 @@ class Jobber {
|
|
|
4173
4810
|
}
|
|
4174
4811
|
}
|
|
4175
4812
|
|
|
4176
|
-
const
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
if (claimedHash && hash !== claimedHash) {
|
|
4181
|
-
throw new Error(`lastBlock hash mismatch: expected ${claimedHash}, got ${hash}`);
|
|
4182
|
-
}
|
|
4183
|
-
return new LastBlockMessage({ hash, index: block.decoded.index });
|
|
4184
|
-
};
|
|
4185
|
-
const resolveLegacyHash = async (hash) => {
|
|
4186
|
-
const blockData = await globalThis.peernet?.get?.(hash, "block");
|
|
4187
|
-
if (!blockData) throw new Error(`unable to resolve lastBlock hash: ${hash}`);
|
|
4188
|
-
return asLastBlockMessage(blockData, hash);
|
|
4189
|
-
};
|
|
4190
|
-
const resolveLastBlockMessage = async (result, depth = 0) => {
|
|
4191
|
-
if (depth > MAX_RESPONSE_DEPTH$1) throw new Error("lastBlock response nesting exceeds limit");
|
|
4192
|
-
if (result instanceof Uint8Array) {
|
|
4193
|
-
let codec;
|
|
4194
|
-
try {
|
|
4195
|
-
codec = new Codec(result);
|
|
4196
|
-
} catch {
|
|
4197
|
-
}
|
|
4198
|
-
if (codec) {
|
|
4199
|
-
if (codec.name === "peernet-response") {
|
|
4200
|
-
const ResponseMessage = globalThis.peernet?.protos?.["peernet-response"];
|
|
4201
|
-
if (!ResponseMessage) throw new Error("peernet-response codec is not registered");
|
|
4202
|
-
const wrapped = new ResponseMessage(result);
|
|
4203
|
-
if (wrapped?.decoded?.response === void 0) throw new Error("peernet-response has no response payload");
|
|
4204
|
-
return resolveLastBlockMessage(wrapped.decoded.response, depth + 1);
|
|
4205
|
-
}
|
|
4206
|
-
if (codec.name === "last-block-message") return new LastBlockMessage(result);
|
|
4207
|
-
if (codec.name === "block-message") return asLastBlockMessage(result);
|
|
4208
|
-
}
|
|
4209
|
-
const candidate = new TextDecoder().decode(result);
|
|
4210
|
-
if (candidate) return resolveLegacyHash(candidate);
|
|
4211
|
-
}
|
|
4212
|
-
if (typeof result === "string") return resolveLegacyHash(result);
|
|
4213
|
-
if (result && typeof result === "object") {
|
|
4214
|
-
const response = result.decoded?.response ?? result.response;
|
|
4215
|
-
if (response !== void 0) return resolveLastBlockMessage(response, depth + 1);
|
|
4216
|
-
if ("hash" in result && "index" in result) return new LastBlockMessage(result);
|
|
4217
|
-
if ("previousHash" in result && "index" in result) return asLastBlockMessage(result);
|
|
4218
|
-
}
|
|
4219
|
-
throw new Error(`invalid lastBlock payload: ${typeof result}`);
|
|
4220
|
-
};
|
|
4221
|
-
|
|
4222
|
-
const MAX_RESPONSE_DEPTH = 3;
|
|
4223
|
-
const decodeResponsePayload = async (result, depth = 0) => {
|
|
4224
|
-
if (depth > MAX_RESPONSE_DEPTH) throw new Error("peernet response nesting exceeds limit");
|
|
4225
|
-
if (result instanceof Uint8Array) {
|
|
4226
|
-
let codec;
|
|
4227
|
-
try {
|
|
4228
|
-
codec = new Codec(result);
|
|
4229
|
-
} catch {
|
|
4230
|
-
}
|
|
4231
|
-
if (codec?.name === "peernet-response") {
|
|
4232
|
-
const ResponseMessage = globalThis.peernet?.protos?.["peernet-response"];
|
|
4233
|
-
if (!ResponseMessage) throw new Error("peernet-response codec is not registered");
|
|
4234
|
-
const wrapped = new ResponseMessage(result);
|
|
4235
|
-
return decodeResponsePayload(wrapped.decoded.response, depth + 1);
|
|
4236
|
-
}
|
|
4237
|
-
try {
|
|
4238
|
-
return JSON.parse(new TextDecoder().decode(result));
|
|
4239
|
-
} catch {
|
|
4240
|
-
return result;
|
|
4241
|
-
}
|
|
4242
|
-
}
|
|
4243
|
-
if (result && typeof result === "object") {
|
|
4244
|
-
const response = result.decoded?.response ?? result.response;
|
|
4245
|
-
if (response !== void 0) return decodeResponsePayload(response, depth + 1);
|
|
4246
|
-
}
|
|
4247
|
-
if (typeof result === "string") {
|
|
4248
|
-
try {
|
|
4249
|
-
return JSON.parse(result);
|
|
4250
|
-
} catch {
|
|
4251
|
-
return result;
|
|
4252
|
-
}
|
|
4253
|
-
}
|
|
4254
|
-
return result;
|
|
4813
|
+
const resolveLastBlockMessage = async (result) => {
|
|
4814
|
+
if (!(result instanceof Uint8Array) || new Codec(result).name !== "last-block-message")
|
|
4815
|
+
throw new TypeError("lastBlock response must be a binary last-block message");
|
|
4816
|
+
return new LastBlockMessage(result);
|
|
4255
4817
|
};
|
|
4256
4818
|
|
|
4257
4819
|
class CanonicalStateQueue {
|
|
4258
4820
|
#tail = Promise.resolve();
|
|
4259
4821
|
#failed = false;
|
|
4260
4822
|
#failure;
|
|
4823
|
+
get failed() {
|
|
4824
|
+
return this.#failed;
|
|
4825
|
+
}
|
|
4261
4826
|
run(operation) {
|
|
4262
4827
|
const result = this.#tail.then(async () => {
|
|
4263
4828
|
if (this.#failed) {
|
|
@@ -4283,27 +4848,50 @@ class CanonicalStateQueue {
|
|
|
4283
4848
|
}
|
|
4284
4849
|
}
|
|
4285
4850
|
|
|
4286
|
-
const
|
|
4287
|
-
const
|
|
4288
|
-
const
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
if (hasCanonicalTip && incoming.index === localIndex) {
|
|
4292
|
-
if (incoming.hash === localHash) return "duplicate";
|
|
4293
|
-
throw new Error(`Block conflict detected at index ${incoming.index}`);
|
|
4294
|
-
}
|
|
4295
|
-
const expectedIndex = hasCanonicalTip ? localIndex + 1 : 0;
|
|
4296
|
-
if (incoming.index !== expectedIndex) {
|
|
4297
|
-
throw new Error(`Unexpected block index ${incoming.index}; expected ${expectedIndex}`);
|
|
4298
|
-
}
|
|
4299
|
-
if (incoming.previousHash !== localHash) {
|
|
4300
|
-
throw new Error(
|
|
4301
|
-
`previousHash mismatch at index ${incoming.index}: expected ${localHash}, got ${incoming.previousHash}`
|
|
4302
|
-
);
|
|
4851
|
+
const resolveTransactionReference = async (expectedHash, transactionData) => {
|
|
4852
|
+
const transaction = transactionData instanceof TransactionMessage ? transactionData : new TransactionMessage(transactionData);
|
|
4853
|
+
const actualHash = await transaction.hash();
|
|
4854
|
+
if (actualHash !== expectedHash) {
|
|
4855
|
+
throw new Error(`Transaction hash mismatch: expected ${expectedHash}, got ${actualHash}`);
|
|
4303
4856
|
}
|
|
4304
|
-
return
|
|
4857
|
+
return transaction;
|
|
4305
4858
|
};
|
|
4306
4859
|
|
|
4860
|
+
function publishObserverEvent(bus, topic, payload, onError = (topic2, error) => console.warn(`[chain observer] ${topic2}:`, error)) {
|
|
4861
|
+
const report = (error) => {
|
|
4862
|
+
try {
|
|
4863
|
+
onError(topic, error);
|
|
4864
|
+
} catch {
|
|
4865
|
+
}
|
|
4866
|
+
};
|
|
4867
|
+
const invoke = (callback) => {
|
|
4868
|
+
try {
|
|
4869
|
+
const result = callback();
|
|
4870
|
+
if (result && typeof result.then === "function") Promise.resolve(result).catch(report);
|
|
4871
|
+
} catch (error) {
|
|
4872
|
+
report(error);
|
|
4873
|
+
}
|
|
4874
|
+
};
|
|
4875
|
+
try {
|
|
4876
|
+
const value = structuredClone(payload);
|
|
4877
|
+
if (!(bus.subscribers instanceof Map)) {
|
|
4878
|
+
invoke(() => bus.publish(topic, value));
|
|
4879
|
+
return;
|
|
4880
|
+
}
|
|
4881
|
+
let subscriber = bus.subscribers.get(topic);
|
|
4882
|
+
if (!subscriber) {
|
|
4883
|
+
subscriber = { handlers: [], value: void 0 };
|
|
4884
|
+
bus.subscribers.set(topic, subscriber);
|
|
4885
|
+
}
|
|
4886
|
+
const oldValue = subscriber.value;
|
|
4887
|
+
if (!bus.verbose && oldValue === value) return;
|
|
4888
|
+
subscriber.value = value;
|
|
4889
|
+
for (const handler of [...subscriber.handlers]) invoke(() => handler.bound(value, oldValue));
|
|
4890
|
+
} catch (error) {
|
|
4891
|
+
report(error);
|
|
4892
|
+
}
|
|
4893
|
+
}
|
|
4894
|
+
|
|
4307
4895
|
const debug$1 = createDebugger("leofcoin/state");
|
|
4308
4896
|
class State extends Contract {
|
|
4309
4897
|
constructor(config) {
|
|
@@ -4320,7 +4908,7 @@ class State extends Contract {
|
|
|
4320
4908
|
this.#totalSize = 0;
|
|
4321
4909
|
this.#canonicalStateQueue = new CanonicalStateQueue();
|
|
4322
4910
|
this.#loaded = false;
|
|
4323
|
-
this.#resolvingHashes = /* @__PURE__ */ new
|
|
4911
|
+
this.#resolvingHashes = /* @__PURE__ */ new Map();
|
|
4324
4912
|
this._wantList = [];
|
|
4325
4913
|
this.#chainStateHandler = () => {
|
|
4326
4914
|
return new globalThis.peernet.protos["peernet-response"]({
|
|
@@ -4337,15 +4925,6 @@ class State extends Contract {
|
|
|
4337
4925
|
response: { blocks: await globalThis.blockStore.keys() }
|
|
4338
4926
|
});
|
|
4339
4927
|
};
|
|
4340
|
-
this.#loadBlockTransactions = (transactions) => Promise.all(
|
|
4341
|
-
transactions.map(async (transaction) => new TransactionMessage(await peernet.get(transaction, "transaction")))
|
|
4342
|
-
);
|
|
4343
|
-
this.#getLastTransactions = async () => {
|
|
4344
|
-
let lastTransactions = (await Promise.all(
|
|
4345
|
-
(await this.blocks).filter((block) => block.loaded).slice(-24).map((block) => this.#loadBlockTransactions(block.transactions))
|
|
4346
|
-
)).reduce((all, transactions) => [...all, ...transactions], []);
|
|
4347
|
-
return Promise.all(lastTransactions.map((transaction) => transaction.hash()));
|
|
4348
|
-
};
|
|
4349
4928
|
}
|
|
4350
4929
|
#resolveErrored;
|
|
4351
4930
|
#lastResolvedTime;
|
|
@@ -4363,7 +4942,13 @@ class State extends Contract {
|
|
|
4363
4942
|
#machine;
|
|
4364
4943
|
#canonicalStateQueue;
|
|
4365
4944
|
withCanonicalState(operation) {
|
|
4366
|
-
return this.#canonicalStateQueue.run(operation)
|
|
4945
|
+
return this.#canonicalStateQueue.run(operation).catch((error) => {
|
|
4946
|
+
if (this.#canonicalStateQueue.failed) {
|
|
4947
|
+
this.#loaded = false;
|
|
4948
|
+
this.#syncState = "errored";
|
|
4949
|
+
}
|
|
4950
|
+
throw error;
|
|
4951
|
+
});
|
|
4367
4952
|
}
|
|
4368
4953
|
canonicalTip() {
|
|
4369
4954
|
return this.#machine ? this.#machine.getCanonicalTip() : Promise.resolve({ index: -1, hash: "0x0", previousHash: "0x0" });
|
|
@@ -4463,91 +5048,79 @@ class State extends Contract {
|
|
|
4463
5048
|
#lastBlockHandler;
|
|
4464
5049
|
#knownBlocksHandler;
|
|
4465
5050
|
async init() {
|
|
4466
|
-
debug$1("State init start");
|
|
4467
5051
|
this.jobber = new Jobber(this.resolveTimeout);
|
|
4468
5052
|
await globalThis.peernet.addRequestHandler("lastBlock", this.#lastBlockHandler);
|
|
4469
5053
|
await globalThis.peernet.addRequestHandler("knownBlocks", this.#knownBlocksHandler);
|
|
4470
5054
|
await globalThis.peernet.addRequestHandler("chainState", this.#chainStateHandler);
|
|
4471
|
-
let localBlockHash;
|
|
4472
|
-
let blockMessage;
|
|
4473
|
-
let localBlock;
|
|
4474
|
-
console.log("State init before try-catch");
|
|
4475
5055
|
try {
|
|
4476
|
-
const
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
}
|
|
4489
|
-
console.log("State init after localBlock set");
|
|
4490
|
-
} else {
|
|
4491
|
-
localBlock = { index: 0, hash: "0x0", previousHash: "0x0" };
|
|
5056
|
+
const identity = await createChainIdentity(globalThis.peernet?.network || "leofcoin:peach", addresses);
|
|
5057
|
+
const required = [addresses.contractFactory, addresses.nativeToken, addresses.nameService, addresses.validators];
|
|
5058
|
+
const checkpoint = await readCheckpoint(globalThis.stateStore, identity.chainId, required);
|
|
5059
|
+
const checkpointTip = checkpoint?.snapshot.lastBlock ?? { index: -1, hash: "0x0" };
|
|
5060
|
+
const marker = await globalThis.chainStore.has("lastBlock") ? new TextDecoder().decode(await globalThis.chainStore.get("lastBlock")) : "0x0";
|
|
5061
|
+
const readBlock = async (hash) => await globalThis.blockStore.has(hash) ? globalThis.blockStore.get(hash) : globalThis.peernet.get(hash, "block");
|
|
5062
|
+
let history = await recoveryHistory(checkpointTip.hash, readBlock);
|
|
5063
|
+
if (marker !== "0x0" && marker !== checkpointTip.hash && !history.some((block) => block.hash === marker)) {
|
|
5064
|
+
const candidate = await recoveryHistory(marker, readBlock);
|
|
5065
|
+
if (checkpointTip.hash !== "0x0" && candidate[Number(checkpointTip.index)]?.hash !== checkpointTip.hash)
|
|
5066
|
+
throw new Error("Persisted chain marker conflicts with state checkpoint");
|
|
5067
|
+
history = candidate;
|
|
4492
5068
|
}
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
this.knownBlocks = await globalThis.blockStore.keys();
|
|
4502
|
-
} catch (error) {
|
|
4503
|
-
console.log("no local known blocks found");
|
|
4504
|
-
}
|
|
4505
|
-
try {
|
|
4506
|
-
if (localBlock?.hash && localBlock.hash !== "0x0") {
|
|
4507
|
-
try {
|
|
4508
|
-
if (!await globalThis.stateStore.has("lastBlock")) {
|
|
4509
|
-
await this.resolveBlocks();
|
|
4510
|
-
}
|
|
4511
|
-
} catch {
|
|
4512
|
-
await this.resolveBlocks();
|
|
5069
|
+
if (!checkpoint && await globalThis.stateStore.has("lastBlock")) {
|
|
5070
|
+
const legacy = JSON.parse(new TextDecoder().decode(await globalThis.stateStore.get("lastBlock")));
|
|
5071
|
+
if (legacy.hash && legacy.hash !== "0x0" && legacy.hash !== history.at(-1)?.hash) {
|
|
5072
|
+
const candidate = await recoveryHistory(legacy.hash, readBlock);
|
|
5073
|
+
const common = Math.min(history.length, candidate.length);
|
|
5074
|
+
if (common && history[common - 1].hash !== candidate[common - 1].hash)
|
|
5075
|
+
throw new Error("Legacy snapshot conflicts with canonical chain marker");
|
|
5076
|
+
if (candidate.length > history.length) history = candidate;
|
|
4513
5077
|
}
|
|
4514
|
-
} else {
|
|
4515
|
-
await this.resolveBlocks();
|
|
4516
5078
|
}
|
|
4517
|
-
|
|
4518
|
-
|
|
5079
|
+
if (await globalThis.chainStore.has("recoveryTarget")) {
|
|
5080
|
+
const target = new TextDecoder().decode(await globalThis.chainStore.get("recoveryTarget"));
|
|
5081
|
+
const candidate = await recoveryHistory(target, readBlock);
|
|
5082
|
+
const common = Math.min(history.length, candidate.length);
|
|
5083
|
+
if (common && history[common - 1].hash !== candidate[common - 1].hash)
|
|
5084
|
+
throw new Error("Recovery target conflicts with canonical checkpoint history");
|
|
5085
|
+
if (candidate.length > history.length) history = candidate;
|
|
5086
|
+
}
|
|
5087
|
+
this.#blocks = history;
|
|
5088
|
+
this.#blockHashMap = new Map(history.map((block) => [block.hash, block.index]));
|
|
5089
|
+
this.knownBlocks = await globalThis.blockStore.keys();
|
|
5090
|
+
if (!checkpoint && !history.length && this.knownBlocks.length)
|
|
5091
|
+
throw new Error("Local blocks exist without a canonical recovery anchor; preserve this root and rebuild in a separate root");
|
|
5092
|
+
const prefix = history.slice(0, Number(checkpointTip.index) + 1).map((block) => ({ ...block, loaded: true }));
|
|
5093
|
+
if (history.length > prefix.length) await globalThis.chainStore.put("recoveryTarget", history.at(-1).hash);
|
|
5094
|
+
const machine = new Machine(prefix, checkpoint);
|
|
4519
5095
|
await machine.ready;
|
|
4520
5096
|
this.#machine = machine;
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
5097
|
+
await restoreAccountNonces(globalThis.accountsStore, checkpoint?.snapshot.accounts ?? {});
|
|
5098
|
+
for (const block of prefix) this.#blocks[Number(block.index)].loaded = true;
|
|
5099
|
+
await this.withCanonicalState((transition) => this.#loadBlocks(history.slice(prefix.length), transition));
|
|
5100
|
+
if (!checkpoint && !history.length) await this.#machine.updateState(true);
|
|
5101
|
+
const tip = await this.canonicalTip();
|
|
5102
|
+
await globalThis.chainStore.put("lastBlock", tip.hash);
|
|
5103
|
+
await globalThis.chainStore.delete("recoveryTarget");
|
|
4525
5104
|
this.#loaded = true;
|
|
4526
5105
|
this.#chainState = "loaded";
|
|
4527
5106
|
} catch (error) {
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
console.log(error);
|
|
5107
|
+
this.#loaded = false;
|
|
5108
|
+
this.#chainState = "loading";
|
|
5109
|
+
this.#syncState = "errored";
|
|
5110
|
+
throw error;
|
|
4533
5111
|
}
|
|
4534
5112
|
}
|
|
4535
5113
|
async updateState(message) {
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
await this.#machine.updateState(true);
|
|
4547
|
-
} catch (error) {
|
|
4548
|
-
console.error(error);
|
|
4549
|
-
throw error;
|
|
4550
|
-
}
|
|
5114
|
+
if (!this.#machine) throw new Error("Cannot checkpoint an uninitialized state machine");
|
|
5115
|
+
const hash = await message.hash();
|
|
5116
|
+
const tip = await this.canonicalTip();
|
|
5117
|
+
if (tip.hash !== hash || Number(tip.index) !== Number(message.decoded.index))
|
|
5118
|
+
throw new Error("Checkpoint target does not match canonical worker tip");
|
|
5119
|
+
await this.#machine.updateState(true);
|
|
5120
|
+
await globalThis.chainStore.put("lastBlock", hash);
|
|
5121
|
+
if (await globalThis.chainStore.has("recoveryTarget") && new TextDecoder().decode(await globalThis.chainStore.get("recoveryTarget")) === hash)
|
|
5122
|
+
await globalThis.chainStore.delete("recoveryTarget");
|
|
5123
|
+
publishObserverEvent(globalThis.pubsub, "lastBlock", message.encoded);
|
|
4551
5124
|
}
|
|
4552
5125
|
getLatestBlock() {
|
|
4553
5126
|
return this.#getLatestBlock();
|
|
@@ -4576,7 +5149,8 @@ class State extends Contract {
|
|
|
4576
5149
|
if (!exists) {
|
|
4577
5150
|
const data = await peernet.get(hash, "transaction");
|
|
4578
5151
|
if (!data) throw new Error(`missing transaction data for ${hash}`);
|
|
4579
|
-
await
|
|
5152
|
+
const transaction = await resolveTransactionReference(hash, data);
|
|
5153
|
+
await transactionStore.put(hash, transaction.encoded);
|
|
4580
5154
|
}
|
|
4581
5155
|
const inPool = await transactionPoolStore.has(hash);
|
|
4582
5156
|
if (inPool) await transactionPoolStore.delete(hash);
|
|
@@ -4584,84 +5158,61 @@ class State extends Contract {
|
|
|
4584
5158
|
);
|
|
4585
5159
|
}
|
|
4586
5160
|
async #resolveBlock(hash) {
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
}
|
|
4598
|
-
if (this.#blocks[index]) {
|
|
4599
|
-
const previousHash = this.#blocks[index].previousHash;
|
|
4600
|
-
if (previousHash === localHash) return;
|
|
4601
|
-
if (previousHash !== "0x0") {
|
|
4602
|
-
return this.resolveBlock(previousHash);
|
|
4603
|
-
} else {
|
|
4604
|
-
return;
|
|
5161
|
+
const localHash = (await this.canonicalTip()).hash;
|
|
5162
|
+
const visited = /* @__PURE__ */ new Set();
|
|
5163
|
+
while (hash !== "0x0" && hash !== localHash) {
|
|
5164
|
+
if (visited.has(hash)) throw new ResolveError(`cyclic block ancestry at ${hash}`);
|
|
5165
|
+
visited.add(hash);
|
|
5166
|
+
const cachedIndex = this.#blockHashMap.get(hash);
|
|
5167
|
+
const cached = this.#blocks[cachedIndex];
|
|
5168
|
+
if (cached) {
|
|
5169
|
+
hash = cached.previousHash;
|
|
5170
|
+
continue;
|
|
4605
5171
|
}
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
5172
|
+
try {
|
|
5173
|
+
const block = await this.getAndPutBlock(hash);
|
|
5174
|
+
await this.#resolveTransactions(block.decoded.transactions);
|
|
5175
|
+
const index = block.decoded.index;
|
|
5176
|
+
const size = block.encoded.byteLength;
|
|
5177
|
+
this.#totalSize += size;
|
|
5178
|
+
this.#blocks[index] = { hash, ...block.decoded };
|
|
5179
|
+
this.#blockHashMap.set(hash, index);
|
|
5180
|
+
debug$1(`resolved block: ${hash} @${index} ${formatBytes(size)}`);
|
|
5181
|
+
publishObserverEvent(globalThis.pubsub, "block-resolved", { hash, index });
|
|
5182
|
+
this.#lastResolved = this.#blocks[index];
|
|
5183
|
+
this.#lastResolvedTime = Date.now();
|
|
5184
|
+
hash = block.decoded.previousHash;
|
|
5185
|
+
} catch (error) {
|
|
5186
|
+
throw new ResolveError(`block: ${hash}`, { cause: error });
|
|
4612
5187
|
}
|
|
4613
|
-
promises.push(this.#resolveTransactions(block.decoded.transactions));
|
|
4614
|
-
await Promise.all(promises);
|
|
4615
|
-
index = block.decoded.index;
|
|
4616
|
-
const size = block.encoded.length > 0 ? block.encoded.length : block.encoded.byteLength;
|
|
4617
|
-
this.#totalSize += size;
|
|
4618
|
-
this.#blocks[index] = { hash, ...block.decoded };
|
|
4619
|
-
this.#blockHashMap.set(hash, index);
|
|
4620
|
-
debug$1(`resolved block: ${hash} @${index} ${formatBytes(size)}`);
|
|
4621
|
-
globalThis.pubsub.publish("block-resolved", { hash, index });
|
|
4622
|
-
this.#lastResolved = this.#blocks[index];
|
|
4623
|
-
this.#lastResolvedTime = Date.now();
|
|
4624
|
-
} catch (error) {
|
|
4625
|
-
throw new ResolveError(`block: ${hash}@${index}`, { cause: error });
|
|
4626
5188
|
}
|
|
4627
|
-
return;
|
|
4628
5189
|
}
|
|
4629
5190
|
async resolveBlock(hash) {
|
|
4630
5191
|
if (!hash) throw new Error(`expected hash, got: ${hash}`);
|
|
4631
5192
|
if (hash === "0x0") return;
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
const isEntering = this.#resolvingHashes.size === 1;
|
|
5193
|
+
const existing = this.#resolvingHashes.get(hash);
|
|
5194
|
+
if (existing) return existing;
|
|
4635
5195
|
this.#resolving = true;
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
} else {
|
|
4641
|
-
await this.#resolveBlock(hash);
|
|
4642
|
-
}
|
|
4643
|
-
try {
|
|
4644
|
-
const lastBlockHash = await globalThis.stateStore.get("lastBlock");
|
|
4645
|
-
if (lastBlockHash === hash) {
|
|
5196
|
+
const resolution = Promise.resolve().then(async () => {
|
|
5197
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
5198
|
+
try {
|
|
5199
|
+
await this.#resolveBlock(hash);
|
|
4646
5200
|
this.#resolveErrored = false;
|
|
4647
5201
|
return;
|
|
5202
|
+
} catch (error) {
|
|
5203
|
+
console.error(`block resolution attempt ${attempt + 1} failed for ${hash}`, error);
|
|
5204
|
+
if (attempt === 2) {
|
|
5205
|
+
if (!this.wantList.includes(hash)) this.wantList.push(hash);
|
|
5206
|
+
throw new ResolveError(`block: ${hash}`, { cause: error });
|
|
5207
|
+
}
|
|
4648
5208
|
}
|
|
4649
|
-
} catch (error) {
|
|
4650
|
-
}
|
|
4651
|
-
} catch (error) {
|
|
4652
|
-
console.log({ error });
|
|
4653
|
-
this.#resolveErrorCount += 1;
|
|
4654
|
-
if (this.#resolveErrorCount < 3) {
|
|
4655
|
-
this.#resolvingHashes.delete(hash);
|
|
4656
|
-
return this.resolveBlock(hash);
|
|
4657
5209
|
}
|
|
4658
|
-
|
|
4659
|
-
this.wantList.push(hash);
|
|
4660
|
-
throw new ResolveError(`block: ${hash}`, { cause: error });
|
|
4661
|
-
} finally {
|
|
5210
|
+
}).finally(() => {
|
|
4662
5211
|
this.#resolvingHashes.delete(hash);
|
|
4663
5212
|
if (this.#resolvingHashes.size === 0) this.#resolving = false;
|
|
4664
|
-
}
|
|
5213
|
+
});
|
|
5214
|
+
this.#resolvingHashes.set(hash, resolution);
|
|
5215
|
+
return resolution;
|
|
4665
5216
|
}
|
|
4666
5217
|
async resolveBlocks() {
|
|
4667
5218
|
if (this.#chainSyncing || this.#resolving) {
|
|
@@ -4764,7 +5315,7 @@ class State extends Contract {
|
|
|
4764
5315
|
const localIndex = localBlock ? Number(localBlock.index) : -1;
|
|
4765
5316
|
const remoteIndex = Number(lastBlock.index);
|
|
4766
5317
|
const remoteBlockHash = lastBlock.hash;
|
|
4767
|
-
globalThis.pubsub
|
|
5318
|
+
publishObserverEvent(globalThis.pubsub, "sync-progress", {
|
|
4768
5319
|
phase: "syncing",
|
|
4769
5320
|
startIndex: localIndex,
|
|
4770
5321
|
localIndex,
|
|
@@ -4798,26 +5349,9 @@ class State extends Contract {
|
|
|
4798
5349
|
return;
|
|
4799
5350
|
}
|
|
4800
5351
|
if (localStateHash !== remoteBlockHash) {
|
|
4801
|
-
if (this.wantList.length > 0) {
|
|
4802
|
-
debug$1(`Fetching ${this.wantList.length} blocks before resolving`);
|
|
4803
|
-
const getBatch = async (batch) => {
|
|
4804
|
-
const blocks2 = await Promise.all(
|
|
4805
|
-
batch.map(
|
|
4806
|
-
(hash) => this.getAndPutBlock(hash).catch((e) => {
|
|
4807
|
-
console.warn(`failed to fetch block ${hash}`, e);
|
|
4808
|
-
})
|
|
4809
|
-
)
|
|
4810
|
-
);
|
|
4811
|
-
const transactions = blocks2.filter((block) => Boolean(block)).flatMap((block) => block.decoded.transactions);
|
|
4812
|
-
return this.#resolveTransactions(transactions);
|
|
4813
|
-
};
|
|
4814
|
-
for (let i = 0; i < this.wantList.length; i += 50) {
|
|
4815
|
-
const batch = this.wantList.slice(i, i + 50);
|
|
4816
|
-
await getBatch(batch);
|
|
4817
|
-
}
|
|
4818
|
-
}
|
|
4819
5352
|
debug$1(`Resolving remote block: ${remoteBlockHash} @${remoteIndex} (differs from local state)`);
|
|
4820
5353
|
await this.resolveBlock(remoteBlockHash);
|
|
5354
|
+
await globalThis.chainStore.put("recoveryTarget", remoteBlockHash);
|
|
4821
5355
|
const blocksSynced = remoteIndex - localIndex;
|
|
4822
5356
|
debug$1(`Resolved ${blocksSynced} new block(s)`);
|
|
4823
5357
|
const blocks = this.#blocks;
|
|
@@ -4837,7 +5371,7 @@ class State extends Contract {
|
|
|
4837
5371
|
} else {
|
|
4838
5372
|
debug$1(`Block already in local state. Remote hash: ${remoteBlockHash} matches local state`);
|
|
4839
5373
|
}
|
|
4840
|
-
globalThis.pubsub
|
|
5374
|
+
publishObserverEvent(globalThis.pubsub, "sync-progress", {
|
|
4841
5375
|
phase: "synced",
|
|
4842
5376
|
startIndex: localIndex,
|
|
4843
5377
|
localIndex: remoteIndex,
|
|
@@ -4925,112 +5459,23 @@ class State extends Contract {
|
|
|
4925
5459
|
}
|
|
4926
5460
|
return latest;
|
|
4927
5461
|
}
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
// todo throw error
|
|
4931
|
-
async #_executeTransaction(transaction, validators, feesEnabled, burnBasisPoints, blockTimestamp) {
|
|
4932
|
-
try {
|
|
4933
|
-
const hash = await transaction.hash();
|
|
4934
|
-
if (feesEnabled) {
|
|
4935
|
-
const fee = BigInt(await calculateFee(transaction.decoded));
|
|
4936
|
-
const { payments, burned } = distributeTransactionFee(fee, hash, validators, burnBasisPoints);
|
|
4937
|
-
await this.#machine.collectFee(transaction.decoded.from, payments, burned);
|
|
4938
|
-
}
|
|
4939
|
-
await this.#machine.execute(
|
|
4940
|
-
transaction.decoded.to,
|
|
4941
|
-
transaction.decoded.method,
|
|
4942
|
-
transaction.decoded.params,
|
|
4943
|
-
transaction.decoded.from,
|
|
4944
|
-
{ seed: hash, timestamp: Number(blockTimestamp) }
|
|
4945
|
-
);
|
|
4946
|
-
} catch (error) {
|
|
4947
|
-
console.log(error);
|
|
4948
|
-
await globalThis.transactionPoolStore.delete(await transaction.hash());
|
|
4949
|
-
console.log("removing invalid transaction");
|
|
4950
|
-
if (isExecutionError(error)) {
|
|
4951
|
-
console.log(error);
|
|
4952
|
-
}
|
|
4953
|
-
console.log(error);
|
|
4954
|
-
throw error;
|
|
4955
|
-
}
|
|
5462
|
+
async applyRecoveredBlock(block, transition) {
|
|
5463
|
+
throw new Error("Canonical block application is not implemented");
|
|
4956
5464
|
}
|
|
4957
|
-
/**
|
|
4958
|
-
*
|
|
4959
|
-
* @param {Block[]} blocks
|
|
4960
|
-
*/
|
|
4961
5465
|
async #loadBlocks(blocks, transition) {
|
|
4962
5466
|
this.#chainState = "loading";
|
|
4963
|
-
if (blocks.some((block) => !block)) {
|
|
4964
|
-
throw new Error("missing block data during load; chain resolution incomplete");
|
|
4965
|
-
}
|
|
4966
|
-
const poolTransactionKeys = new Set(await globalThis.transactionPoolStore.keys());
|
|
4967
|
-
debug$1(`pool transactions: ${poolTransactionKeys.size}`);
|
|
4968
|
-
debug$1(`loading ${blocks.length} blocks`);
|
|
4969
5467
|
for (const block of blocks) {
|
|
4970
|
-
if (block
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
}
|
|
4982
|
-
debug$1(`loading block: ${Number(block.index)} ${block.hash}`);
|
|
4983
|
-
let transactions = await this.#loadBlockTransactions(block.transactions || []);
|
|
4984
|
-
const validators = block.validators.map(({ address }) => address);
|
|
4985
|
-
debug$1(`loading transactions: ${transactions.length} for block ${block.index}`);
|
|
4986
|
-
transition.markMutating();
|
|
4987
|
-
for (const transaction of transactions) {
|
|
4988
|
-
const hash = await transaction.hash();
|
|
4989
|
-
if (poolTransactionKeys.has(hash)) await globalThis.transactionPoolStore.delete(hash);
|
|
4990
|
-
}
|
|
4991
|
-
transactions = transactions.sort((a, b) => {
|
|
4992
|
-
if (a.decoded.priority !== b.decoded.priority) return a.decoded.priority ? -1 : 1;
|
|
4993
|
-
const left = BigInt(a.decoded.nonce);
|
|
4994
|
-
const right = BigInt(b.decoded.nonce);
|
|
4995
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
4996
|
-
});
|
|
4997
|
-
debug$1(`executing ${transactions.length} transactions for block ${block.index}`);
|
|
4998
|
-
const feesEnabled = supportsTransactionFees(block.protocolVersion);
|
|
4999
|
-
const monetaryPolicyEnabled = supportsMonetaryPolicy(block.protocolVersion);
|
|
5000
|
-
if (monetaryPolicyEnabled) await validateBlockResourceLimits(transactions);
|
|
5001
|
-
const policy = monetaryPolicyEnabled ? calculateMonetaryPolicy(
|
|
5002
|
-
BigInt(await this.#machine.get(nativeToken, "totalSupply")),
|
|
5003
|
-
BigInt(await this.#machine.get(nativeToken, "targetSupply"))
|
|
5004
|
-
) : { subsidy: 0n, burnBasisPoints: 1000n };
|
|
5005
|
-
for (const transaction of transactions) {
|
|
5006
|
-
await this.#_executeTransaction(
|
|
5007
|
-
transaction,
|
|
5008
|
-
validators,
|
|
5009
|
-
feesEnabled,
|
|
5010
|
-
policy.burnBasisPoints,
|
|
5011
|
-
block.timestamp
|
|
5012
|
-
);
|
|
5013
|
-
}
|
|
5014
|
-
if (monetaryPolicyEnabled && policy.subsidy > 0n) {
|
|
5015
|
-
await this.#machine.settleRewards([
|
|
5016
|
-
...distributeAmount(policy.subsidy, validators, Number(block.index) % validators.length)
|
|
5017
|
-
]);
|
|
5018
|
-
}
|
|
5019
|
-
debug$1(`executed transactions for block ${block.index}`);
|
|
5020
|
-
const registered = await this.#machine.addLoadedBlock({ ...block, loaded: true });
|
|
5021
|
-
if (registered !== true) throw new Error(`Loaded block registration failed after execution: ${block.hash}`);
|
|
5022
|
-
this.#blocks[block.index].loaded = true;
|
|
5023
|
-
if (Number(block.index) === 0) this.#loaded = true;
|
|
5024
|
-
debug$1(`loaded block: ${block.hash} @${Number(block.index)}`);
|
|
5025
|
-
globalThis.pubsub.publish("block-loaded", { ...block });
|
|
5026
|
-
} catch (error) {
|
|
5027
|
-
console.error(error);
|
|
5028
|
-
for (const transaction of block.transactions) {
|
|
5029
|
-
this.wantList.push(transaction);
|
|
5030
|
-
}
|
|
5031
|
-
throw error;
|
|
5032
|
-
}
|
|
5033
|
-
}
|
|
5468
|
+
if (!block) throw new Error("Missing block data during recovery");
|
|
5469
|
+
if (block.loaded) continue;
|
|
5470
|
+
const { hash, loaded, ...header } = block;
|
|
5471
|
+
const message = new BlockMessage(header);
|
|
5472
|
+
if (await message.hash() !== block.hash) throw new Error("Recovery block hash changed before application");
|
|
5473
|
+
await this.applyRecoveredBlock(message, transition);
|
|
5474
|
+
const tip = await this.canonicalTip();
|
|
5475
|
+
if (tip.hash !== block.hash || Number(tip.index) !== Number(block.index))
|
|
5476
|
+
throw new Error("Recovery did not apply the expected canonical block");
|
|
5477
|
+
block.loaded = true;
|
|
5478
|
+
publishObserverEvent(globalThis.pubsub, "block-loaded", { ...block });
|
|
5034
5479
|
}
|
|
5035
5480
|
this.#chainState = "loaded";
|
|
5036
5481
|
return true;
|
|
@@ -5090,18 +5535,16 @@ class State extends Contract {
|
|
|
5090
5535
|
return this.syncChain(latest);
|
|
5091
5536
|
}
|
|
5092
5537
|
async triggerLoad() {
|
|
5093
|
-
if (this.#
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
await machine.ready;
|
|
5097
|
-
this.#machine = machine;
|
|
5098
|
-
}
|
|
5538
|
+
if (!this.#machine) throw new Error("Restart the node to initialize recovery");
|
|
5539
|
+
const tip = await this.canonicalTip();
|
|
5540
|
+
return this.withCanonicalState((transition) => this.#loadBlocks(this.#blocks.slice(Number(tip.index) + 1), transition));
|
|
5099
5541
|
}
|
|
5100
5542
|
}
|
|
5101
5543
|
|
|
5102
5544
|
class VersionControl extends State {
|
|
5103
5545
|
constructor(config) {
|
|
5104
5546
|
super(config);
|
|
5547
|
+
this.version = PROTOCOL_VERSION;
|
|
5105
5548
|
}
|
|
5106
5549
|
#currentVersion = PROTOCOL_VERSION;
|
|
5107
5550
|
#reachedOneZeroZero = REACHED_ONE_ZERO_ZERO;
|
|
@@ -5171,15 +5614,6 @@ const validateBlockEconomics = (block, calculatedFees, validatorFees = /* @__PUR
|
|
|
5171
5614
|
}
|
|
5172
5615
|
};
|
|
5173
5616
|
|
|
5174
|
-
const resolveTransactionReference = async (expectedHash, transactionData) => {
|
|
5175
|
-
const transaction = transactionData instanceof TransactionMessage ? transactionData : new TransactionMessage(transactionData);
|
|
5176
|
-
const actualHash = await transaction.hash();
|
|
5177
|
-
if (actualHash !== expectedHash) {
|
|
5178
|
-
throw new Error(`Transaction hash mismatch: expected ${expectedHash}, got ${actualHash}`);
|
|
5179
|
-
}
|
|
5180
|
-
return transaction;
|
|
5181
|
-
};
|
|
5182
|
-
|
|
5183
5617
|
const consensusSignableData = (validatorsAddress, type, message) => ({
|
|
5184
5618
|
from: String(message.from),
|
|
5185
5619
|
to: validatorsAddress,
|
|
@@ -5254,80 +5688,13 @@ const assertHashFormatState = async (chainStore, stateStore) => {
|
|
|
5254
5688
|
const parsed = /^(\d+)\.(\d+)\.\d+$/.exec(version);
|
|
5255
5689
|
const current = PROTOCOL_VERSION.split(".");
|
|
5256
5690
|
if (parsed && parsed[1] === current[0] && parsed[2] === current[1]) return;
|
|
5257
|
-
throw new Error(`stored protocol ${version} is incompatible with canonical hash protocol ${PROTOCOL_VERSION};
|
|
5691
|
+
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)`);
|
|
5258
5692
|
}
|
|
5259
|
-
if (await chainStore.has("lastBlock") || await stateStore?.has("lastBlock")) {
|
|
5693
|
+
if (await chainStore.has("lastBlock") || await chainStore.has("recoveryTarget") || await stateStore?.has("lastBlock") || await stateStore?.has("checkpoint:0") || await stateStore?.has("checkpoint:1")) {
|
|
5260
5694
|
throw new Error("existing chainstate has no protocol version; explicit canonical hash migration required");
|
|
5261
5695
|
}
|
|
5262
5696
|
};
|
|
5263
5697
|
|
|
5264
|
-
const CHAIN_ID_SCHEMA = "leofcoin:chain-identity:v1";
|
|
5265
|
-
const GENESIS_CONTRACT_NAMES = ["contractFactory", "nativeToken", "nameService", "validators"];
|
|
5266
|
-
const assertChainId = (value) => {
|
|
5267
|
-
if (typeof value !== "string" || !/^lfc:[0-9a-f]{64}$/.test(value))
|
|
5268
|
-
throw new Error("Invalid chain ID; expected lfc: followed by 64 lowercase hex characters");
|
|
5269
|
-
return value;
|
|
5270
|
-
};
|
|
5271
|
-
const createChainIdentity = async (network, contracts) => {
|
|
5272
|
-
if (typeof network !== "string" || !/^[a-z0-9][a-z0-9:-]{0,79}$/.test(network))
|
|
5273
|
-
throw new Error("Invalid genesis network namespace");
|
|
5274
|
-
const genesisContracts = {};
|
|
5275
|
-
for (const name of GENESIS_CONTRACT_NAMES) {
|
|
5276
|
-
const hash = contracts?.[name];
|
|
5277
|
-
if (typeof hash !== "string" || !/^[A-Z2-7]{32,128}$/.test(hash))
|
|
5278
|
-
throw new Error(`Invalid genesis contract hash: ${name}`);
|
|
5279
|
-
genesisContracts[name] = hash;
|
|
5280
|
-
}
|
|
5281
|
-
const bytes = new TextEncoder().encode(JSON.stringify({ schema: CHAIN_ID_SCHEMA, network, genesisContracts }));
|
|
5282
|
-
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
5283
|
-
const chainId = "lfc:" + [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
5284
|
-
return { schema: CHAIN_ID_SCHEMA, chainId, network, genesisContracts };
|
|
5285
|
-
};
|
|
5286
|
-
const verifyChainIdentity = async (value) => {
|
|
5287
|
-
const identity = value;
|
|
5288
|
-
if (!identity || identity.schema !== CHAIN_ID_SCHEMA)
|
|
5289
|
-
throw new Error("Unsupported chain identity response");
|
|
5290
|
-
const verified = await createChainIdentity(identity.network, identity.genesisContracts);
|
|
5291
|
-
if (verified.chainId !== assertChainId(identity.chainId))
|
|
5292
|
-
throw new Error("Chain ID does not match its genesis contracts");
|
|
5293
|
-
return verified;
|
|
5294
|
-
};
|
|
5295
|
-
const findChainPresence = async (chainId, peerIds, requestIdentity, timeoutMs = 3e3) => {
|
|
5296
|
-
assertChainId(chainId);
|
|
5297
|
-
const peers = [...new Set(peerIds)].slice(0, 16);
|
|
5298
|
-
const matches = [];
|
|
5299
|
-
let responded = 0;
|
|
5300
|
-
let unavailable = 0;
|
|
5301
|
-
await Promise.all(peers.map(async (peerId) => {
|
|
5302
|
-
let timer;
|
|
5303
|
-
try {
|
|
5304
|
-
const result = await Promise.race([
|
|
5305
|
-
Promise.resolve().then(() => requestIdentity(peerId)),
|
|
5306
|
-
new Promise((_, reject) => {
|
|
5307
|
-
timer = setTimeout(() => reject(new Error("Chain identity request timed out")), timeoutMs);
|
|
5308
|
-
})
|
|
5309
|
-
]);
|
|
5310
|
-
const identity = await verifyChainIdentity(result);
|
|
5311
|
-
responded++;
|
|
5312
|
-
if (identity.chainId === chainId)
|
|
5313
|
-
matches.push(peerId);
|
|
5314
|
-
} catch {
|
|
5315
|
-
unavailable++;
|
|
5316
|
-
} finally {
|
|
5317
|
-
clearTimeout(timer);
|
|
5318
|
-
}
|
|
5319
|
-
}));
|
|
5320
|
-
return {
|
|
5321
|
-
chainId,
|
|
5322
|
-
status: matches.length ? "found" : !peers.length || unavailable ? "unknown" : "not-observed",
|
|
5323
|
-
checkedPeers: peers.length,
|
|
5324
|
-
respondedPeers: responded,
|
|
5325
|
-
unavailablePeers: unavailable,
|
|
5326
|
-
matchingPeers: matches.sort(),
|
|
5327
|
-
scope: "reachable peers only; offline or undiscovered chains may still exist"
|
|
5328
|
-
};
|
|
5329
|
-
};
|
|
5330
|
-
|
|
5331
5698
|
const debug = createDebugger("leofcoin/chain");
|
|
5332
5699
|
class Chain extends VersionControl {
|
|
5333
5700
|
constructor(config) {
|
|
@@ -5927,10 +6294,10 @@ class Chain extends VersionControl {
|
|
|
5927
6294
|
});
|
|
5928
6295
|
});
|
|
5929
6296
|
globalThis.peernet.subscribe("add-block", this.#addBlock.bind(this));
|
|
5930
|
-
globalThis.peernet.subscribe("invalid-transaction", this.#invalidTransaction
|
|
6297
|
+
globalThis.peernet.subscribe("invalid-transaction", (bytes) => this.#invalidTransaction(decodeWire$1("invalid-transaction", bytes)));
|
|
5931
6298
|
globalThis.peernet.subscribe("send-transaction", this.#sendTransaction.bind(this));
|
|
5932
6299
|
globalThis.peernet.subscribe("add-transaction", this.#addTransaction.bind(this));
|
|
5933
|
-
globalThis.peernet.subscribe("validator:timeout", this.#validatorTimeout
|
|
6300
|
+
globalThis.peernet.subscribe("validator:timeout", (bytes) => this.#validatorTimeout(decodeWire$1("validator:timeout", bytes)));
|
|
5934
6301
|
globalThis.peernet.subscribe("consensus:propose", this.#handleProposal.bind(this));
|
|
5935
6302
|
globalThis.peernet.subscribe("consensus:prevote", this.#handlePrevote.bind(this));
|
|
5936
6303
|
globalThis.peernet.subscribe("consensus:precommit", this.#handlePrecommit.bind(this));
|
|
@@ -5976,52 +6343,7 @@ class Chain extends VersionControl {
|
|
|
5976
6343
|
let response = await peer.request(node.encoded);
|
|
5977
6344
|
console.log(`raw response for request ${request}:`, response);
|
|
5978
6345
|
if (response === void 0 || response === null) return response;
|
|
5979
|
-
|
|
5980
|
-
if (payload === void 0 || payload === null) return payload;
|
|
5981
|
-
if (payload instanceof Uint8Array || typeof Buffer !== "undefined" && Buffer.isBuffer(payload)) {
|
|
5982
|
-
let bytes = payload instanceof Uint8Array ? payload : new Uint8Array(payload);
|
|
5983
|
-
for (let i = 0; i < 3; i += 1) {
|
|
5984
|
-
try {
|
|
5985
|
-
const codec = new Codec(bytes);
|
|
5986
|
-
if (codec.name !== "peernet-response") break;
|
|
5987
|
-
const wrapped = await new globalThis.peernet.protos["peernet-response"](bytes);
|
|
5988
|
-
if (wrapped?.decoded?.response === void 0) break;
|
|
5989
|
-
const next = wrapped.decoded.response;
|
|
5990
|
-
if (next instanceof Uint8Array || typeof Buffer !== "undefined" && Buffer.isBuffer(next)) {
|
|
5991
|
-
bytes = next instanceof Uint8Array ? next : new Uint8Array(next);
|
|
5992
|
-
continue;
|
|
5993
|
-
}
|
|
5994
|
-
return normalizeResponse(next);
|
|
5995
|
-
} catch {
|
|
5996
|
-
break;
|
|
5997
|
-
}
|
|
5998
|
-
}
|
|
5999
|
-
try {
|
|
6000
|
-
return JSON.parse(new TextDecoder().decode(bytes));
|
|
6001
|
-
} catch {
|
|
6002
|
-
return bytes;
|
|
6003
|
-
}
|
|
6004
|
-
}
|
|
6005
|
-
if (payload && typeof payload === "object") {
|
|
6006
|
-
const objectPayload = payload;
|
|
6007
|
-
const decoded = objectPayload.decoded;
|
|
6008
|
-
if ("decoded" in objectPayload && decoded && "response" in decoded) {
|
|
6009
|
-
return normalizeResponse(decoded.response);
|
|
6010
|
-
}
|
|
6011
|
-
if ("response" in objectPayload) {
|
|
6012
|
-
return normalizeResponse(objectPayload.response);
|
|
6013
|
-
}
|
|
6014
|
-
}
|
|
6015
|
-
if (typeof payload === "string") {
|
|
6016
|
-
try {
|
|
6017
|
-
return JSON.parse(payload);
|
|
6018
|
-
} catch {
|
|
6019
|
-
return payload;
|
|
6020
|
-
}
|
|
6021
|
-
}
|
|
6022
|
-
return payload;
|
|
6023
|
-
};
|
|
6024
|
-
return await normalizeResponse(response);
|
|
6346
|
+
return decodeResponsePayload(response);
|
|
6025
6347
|
} catch (error) {
|
|
6026
6348
|
const peerId = peer?.peerId || peer?.id || peer?.address || "unknown";
|
|
6027
6349
|
debug(`peernet request failed: ${request} -> ${peerId}:`, error?.message ?? error);
|
|
@@ -6029,60 +6351,11 @@ class Chain extends VersionControl {
|
|
|
6029
6351
|
}
|
|
6030
6352
|
}
|
|
6031
6353
|
async #decodeKnownBlocksResponse(response) {
|
|
6032
|
-
if (!response) return null;
|
|
6033
|
-
|
|
6034
|
-
return { blocks: response };
|
|
6035
|
-
}
|
|
6036
|
-
if (Array.isArray(response.blocks)) {
|
|
6037
|
-
return { blocks: response.blocks };
|
|
6038
|
-
}
|
|
6039
|
-
if (response.response && Array.isArray(response.response.blocks)) {
|
|
6040
|
-
return { blocks: response.response.blocks };
|
|
6041
|
-
}
|
|
6042
|
-
if (typeof response === "string") {
|
|
6043
|
-
try {
|
|
6044
|
-
const parsed = JSON.parse(response);
|
|
6045
|
-
if (Array.isArray(parsed)) return { blocks: parsed };
|
|
6046
|
-
if (parsed && Array.isArray(parsed.blocks)) return { blocks: parsed.blocks };
|
|
6047
|
-
if (parsed?.response && Array.isArray(parsed.response.blocks)) return { blocks: parsed.response.blocks };
|
|
6048
|
-
} catch {
|
|
6049
|
-
return null;
|
|
6050
|
-
}
|
|
6051
|
-
return null;
|
|
6052
|
-
}
|
|
6053
|
-
if (!(response instanceof Uint8Array)) return null;
|
|
6054
|
-
let payload = response;
|
|
6055
|
-
for (let i = 0; i < 3; i++) {
|
|
6056
|
-
if (!(payload instanceof Uint8Array)) break;
|
|
6057
|
-
try {
|
|
6058
|
-
const nestedResponse = await new globalThis.peernet.protos["peernet-response"](payload);
|
|
6059
|
-
payload = nestedResponse?.decoded?.response;
|
|
6060
|
-
} catch {
|
|
6061
|
-
break;
|
|
6062
|
-
}
|
|
6063
|
-
}
|
|
6064
|
-
if (Array.isArray(payload)) return { blocks: payload };
|
|
6065
|
-
if (payload && Array.isArray(payload.blocks)) return { blocks: payload.blocks };
|
|
6066
|
-
if (payload?.response && Array.isArray(payload.response.blocks)) return { blocks: payload.response.blocks };
|
|
6067
|
-
try {
|
|
6068
|
-
const decoded = new TextDecoder().decode(response);
|
|
6069
|
-
const parsed = JSON.parse(decoded);
|
|
6070
|
-
if (Array.isArray(parsed)) return { blocks: parsed };
|
|
6071
|
-
if (parsed && Array.isArray(parsed.blocks)) return { blocks: parsed.blocks };
|
|
6072
|
-
if (parsed?.response && Array.isArray(parsed.response.blocks)) return { blocks: parsed.response.blocks };
|
|
6073
|
-
} catch {
|
|
6074
|
-
return null;
|
|
6075
|
-
}
|
|
6076
|
-
return null;
|
|
6354
|
+
if (!Array.isArray(response) || response.length > 1e5 || response.some((hash) => typeof hash !== "string")) return null;
|
|
6355
|
+
return { blocks: response };
|
|
6077
6356
|
}
|
|
6078
6357
|
async getPeerTransactionPool(peer) {
|
|
6079
6358
|
let transactionsInPool = await this.#makeRequest(peer, "transactionPool");
|
|
6080
|
-
if (transactionsInPool instanceof Uint8Array) {
|
|
6081
|
-
transactionsInPool = new TextDecoder().decode(transactionsInPool);
|
|
6082
|
-
}
|
|
6083
|
-
if (typeof transactionsInPool === "string") {
|
|
6084
|
-
transactionsInPool = JSON.parse(transactionsInPool);
|
|
6085
|
-
}
|
|
6086
6359
|
if (!Array.isArray(transactionsInPool) || transactionsInPool.some((hash) => typeof hash !== "string")) {
|
|
6087
6360
|
throw new Error("transactionPool response must be an array of transaction hashes");
|
|
6088
6361
|
}
|
|
@@ -6207,13 +6480,6 @@ class Chain extends VersionControl {
|
|
|
6207
6480
|
}
|
|
6208
6481
|
}
|
|
6209
6482
|
}
|
|
6210
|
-
if (this.wantList.length > 0) {
|
|
6211
|
-
const promises = await Promise.allSettled(this.wantList.map((hash) => peernet.get(hash, "block")));
|
|
6212
|
-
for (let i = promises.length - 1; i >= 0; i -= 1) {
|
|
6213
|
-
if (promises[i].status === "fulfilled") this.wantList.splice(i, 1);
|
|
6214
|
-
}
|
|
6215
|
-
if (this.wantList.length === 0) await this.triggerSync();
|
|
6216
|
-
}
|
|
6217
6483
|
try {
|
|
6218
6484
|
let stateInfo = await this.#makeRequest(peer, "stateInfo");
|
|
6219
6485
|
if (stateInfo instanceof Uint8Array) {
|
|
@@ -6227,7 +6493,6 @@ class Chain extends VersionControl {
|
|
|
6227
6493
|
debug(
|
|
6228
6494
|
`sync finished with peer ${peerId}: state=${this.syncState} localNow=${(await this.lastBlock)?.index ?? -1}`
|
|
6229
6495
|
);
|
|
6230
|
-
this.machine.states.info = stateInfo;
|
|
6231
6496
|
} catch (error) {
|
|
6232
6497
|
const peerName = peer?.peerId || peer?.id || peer?.address || peerId || "unknown";
|
|
6233
6498
|
debug(`stateInfo/syncChain failed: ${peerName}:`, error?.message ?? error);
|
|
@@ -6265,7 +6530,7 @@ class Chain extends VersionControl {
|
|
|
6265
6530
|
} catch (error) {
|
|
6266
6531
|
await transactionPoolStore.delete(hash);
|
|
6267
6532
|
try {
|
|
6268
|
-
globalThis.peernet.publish("invalid-transaction", hash);
|
|
6533
|
+
globalThis.peernet.publish("invalid-transaction", encodeWire$1("invalid-transaction", hash));
|
|
6269
6534
|
} catch (publishError) {
|
|
6270
6535
|
debug("peernet publish failed: invalid-transaction", publishError?.message ?? publishError);
|
|
6271
6536
|
}
|
|
@@ -6276,7 +6541,13 @@ class Chain extends VersionControl {
|
|
|
6276
6541
|
async #addBlock(block) {
|
|
6277
6542
|
return this.withCanonicalState((transition) => this.#applyBlock(block, transition));
|
|
6278
6543
|
}
|
|
6279
|
-
|
|
6544
|
+
applyRecoveredBlock(block, transition) {
|
|
6545
|
+
return this.#applyBlock(block, transition, false);
|
|
6546
|
+
}
|
|
6547
|
+
#recordValidatorSnapshot(index) {
|
|
6548
|
+
return this.machine.execute(addresses.validators, "recordValidatorSnapshot", [index], globalThis.peernet.selectedAccount);
|
|
6549
|
+
}
|
|
6550
|
+
async #applyBlock(block, transition, schedule = true) {
|
|
6280
6551
|
const receivedEncoded = block instanceof BlockMessage ? block.encoded : block;
|
|
6281
6552
|
const blockMessage = await new BlockMessage(block);
|
|
6282
6553
|
const hash = await blockMessage.hash();
|
|
@@ -6310,13 +6581,14 @@ class Chain extends VersionControl {
|
|
|
6310
6581
|
console.log(`[chain] \u2705 Block data integrity verified: ${hash}`);
|
|
6311
6582
|
await this.#validateBlockValidators(blockMessage);
|
|
6312
6583
|
const transactions = await this.#resolveBlockTransactions(blockMessage);
|
|
6584
|
+
await globalThis.blockStore.put(hash, blockMessage.encoded);
|
|
6585
|
+
if (!await globalThis.chainStore.has("recoveryTarget")) await globalThis.chainStore.put("recoveryTarget", hash);
|
|
6313
6586
|
transition.markMutating();
|
|
6314
6587
|
await Promise.all(
|
|
6315
6588
|
blockMessage.decoded.transactions.map(async (transactionHash) => {
|
|
6316
6589
|
if (await transactionPoolStore.has(transactionHash)) await transactionPoolStore.delete(transactionHash);
|
|
6317
6590
|
})
|
|
6318
6591
|
);
|
|
6319
|
-
await globalThis.blockStore.put(hash, blockMessage.encoded);
|
|
6320
6592
|
this.#blocks[blockIndex] = {
|
|
6321
6593
|
hash,
|
|
6322
6594
|
...blockMessage.decoded
|
|
@@ -6350,9 +6622,6 @@ class Chain extends VersionControl {
|
|
|
6350
6622
|
}
|
|
6351
6623
|
const noncesByAddress = {};
|
|
6352
6624
|
for (let transaction of transactions) {
|
|
6353
|
-
globalThis.pubsub.publish("transaction-processed", transaction.encoded);
|
|
6354
|
-
if (transaction.decoded.to === globalThis.peernet.selectedAccount)
|
|
6355
|
-
globalThis.pubsub.publish("account-transaction-processed", transaction.encoded);
|
|
6356
6625
|
if (!noncesByAddress[transaction.decoded.from] || noncesByAddress?.[transaction.decoded.from] < transaction.decoded.nonce) {
|
|
6357
6626
|
noncesByAddress[transaction.decoded.from] = transaction.decoded.nonce;
|
|
6358
6627
|
}
|
|
@@ -6370,14 +6639,19 @@ class Chain extends VersionControl {
|
|
|
6370
6639
|
const registered = await this.machine.addLoadedBlock({ ...blockMessage.decoded, loaded: true, hash });
|
|
6371
6640
|
if (registered !== true) throw new Error(`Block registration failed after execution: ${hash}`);
|
|
6372
6641
|
try {
|
|
6373
|
-
await this
|
|
6642
|
+
await this.#recordValidatorSnapshot(blockMessage.decoded.index);
|
|
6374
6643
|
} catch (snapshotError) {
|
|
6375
6644
|
debug(`failed to record validator snapshot: ${snapshotError?.message ?? snapshotError}`);
|
|
6376
6645
|
}
|
|
6377
6646
|
await this.updateState(blockMessage);
|
|
6378
6647
|
debug(`added block: ${hash}`);
|
|
6379
|
-
|
|
6380
|
-
|
|
6648
|
+
for (const transaction of transactions) {
|
|
6649
|
+
publishObserverEvent(globalThis.pubsub, "transaction-processed", transaction.encoded);
|
|
6650
|
+
if (transaction.decoded.to === globalThis.peernet.selectedAccount)
|
|
6651
|
+
publishObserverEvent(globalThis.pubsub, "account-transaction-processed", transaction.encoded);
|
|
6652
|
+
}
|
|
6653
|
+
publishObserverEvent(globalThis.pubsub, "block-processed", blockMessage.decoded);
|
|
6654
|
+
if (schedule) await this.#handleEpochBoundary(blockIndex);
|
|
6381
6655
|
}
|
|
6382
6656
|
async participate(address) {
|
|
6383
6657
|
try {
|
|
@@ -6425,15 +6699,14 @@ class Chain extends VersionControl {
|
|
|
6425
6699
|
async #handleTransaction(transaction, latestTransactions, block, feePayments = { payments: [], burned: 0n }) {
|
|
6426
6700
|
await this.validateTransactionSignature(transaction);
|
|
6427
6701
|
const hash = await transaction.hash();
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
}
|
|
6702
|
+
if (latestTransactions.includes(hash)) throw new Error(`Duplicate canonical transaction: ${hash}`);
|
|
6703
|
+
const nonce = BigInt(transaction.decoded.nonce);
|
|
6704
|
+
let committed = 0n;
|
|
6705
|
+
if (await globalThis.accountsStore.has(transaction.decoded.from)) {
|
|
6706
|
+
const raw = await globalThis.accountsStore.get(transaction.decoded.from);
|
|
6707
|
+
committed = BigInt(typeof raw === "string" ? raw : new TextDecoder().decode(raw));
|
|
6708
|
+
}
|
|
6709
|
+
if (nonce <= committed) throw new Error(`Transaction nonce already committed: ${hash}`);
|
|
6437
6710
|
try {
|
|
6438
6711
|
const result = await this.#executeTransaction({
|
|
6439
6712
|
...transaction.decoded,
|
|
@@ -6450,7 +6723,7 @@ class Chain extends VersionControl {
|
|
|
6450
6723
|
console.log("vvvvvv");
|
|
6451
6724
|
console.log({ e });
|
|
6452
6725
|
console.log(hash);
|
|
6453
|
-
peernet.publish("invalid-transaction", hash);
|
|
6726
|
+
peernet.publish("invalid-transaction", encodeWire$1("invalid-transaction", hash));
|
|
6454
6727
|
await globalThis.transactionPoolStore.delete(e.hash);
|
|
6455
6728
|
if (block) throw e;
|
|
6456
6729
|
}
|
|
@@ -6615,7 +6888,7 @@ class Chain extends VersionControl {
|
|
|
6615
6888
|
} catch (e) {
|
|
6616
6889
|
if (hash) {
|
|
6617
6890
|
try {
|
|
6618
|
-
await globalThis.peernet.publish("invalid-transaction", hash);
|
|
6891
|
+
await globalThis.peernet.publish("invalid-transaction", encodeWire$1("invalid-transaction", hash));
|
|
6619
6892
|
} catch (publishError) {
|
|
6620
6893
|
debug("peernet publish failed: invalid-transaction", publishError?.message ?? publishError);
|
|
6621
6894
|
}
|
|
@@ -6629,7 +6902,8 @@ class Chain extends VersionControl {
|
|
|
6629
6902
|
* error is thrown on error so undefined data doesn't mean there is an error...
|
|
6630
6903
|
**/
|
|
6631
6904
|
async sendTransaction(transaction) {
|
|
6632
|
-
const
|
|
6905
|
+
const { hash: ignoredHash, ...payload } = transaction instanceof TransactionMessage ? transaction.decoded : transaction;
|
|
6906
|
+
const transactionMessage = await new TransactionMessage(payload);
|
|
6633
6907
|
const event = await super.sendTransaction(transactionMessage);
|
|
6634
6908
|
await this.#sendTransaction(transactionMessage.encoded);
|
|
6635
6909
|
try {
|
|
@@ -6669,7 +6943,7 @@ class Chain extends VersionControl {
|
|
|
6669
6943
|
* @returns
|
|
6670
6944
|
*/
|
|
6671
6945
|
internalCall(sender, contract, method, parameters) {
|
|
6672
|
-
|
|
6946
|
+
throw new Error("Direct sender impersonation is disabled; submit a signed transaction with sendTransaction()");
|
|
6673
6947
|
}
|
|
6674
6948
|
/**
|
|
6675
6949
|
*
|
|
@@ -6678,18 +6952,23 @@ class Chain extends VersionControl {
|
|
|
6678
6952
|
* @param {Array} parameters
|
|
6679
6953
|
* @returns
|
|
6680
6954
|
*/
|
|
6681
|
-
call(
|
|
6682
|
-
|
|
6955
|
+
async call(transaction, ...legacy) {
|
|
6956
|
+
const signed = transaction instanceof TransactionMessage ? transaction.decoded : transaction;
|
|
6957
|
+
if (legacy.length || !signed || typeof signed !== "object" || typeof signed.signature !== "string" || !signed.signature)
|
|
6958
|
+
throw new Error("call() requires a signed transaction object; use staticCall() for reads or sendTransaction() for writes");
|
|
6959
|
+
return this.sendTransaction(structuredClone(signed));
|
|
6683
6960
|
}
|
|
6684
6961
|
staticCall(contract, method, parameters) {
|
|
6685
6962
|
return this.machine.get(contract, method, parameters);
|
|
6686
6963
|
}
|
|
6687
|
-
mint(
|
|
6688
|
-
|
|
6964
|
+
mint(...args) {
|
|
6965
|
+
throw new Error("Native minting is restricted to the monetary protocol");
|
|
6689
6966
|
}
|
|
6690
|
-
transfer(
|
|
6691
|
-
|
|
6692
|
-
|
|
6967
|
+
async transfer(transaction, ...legacy) {
|
|
6968
|
+
const signed = transaction instanceof TransactionMessage ? transaction.decoded : transaction;
|
|
6969
|
+
if (legacy.length || signed?.to !== addresses.nativeToken || signed?.method !== "transfer")
|
|
6970
|
+
throw new Error("transfer() requires a signed native-token transfer transaction");
|
|
6971
|
+
return this.call(signed);
|
|
6693
6972
|
}
|
|
6694
6973
|
balanceOf(address) {
|
|
6695
6974
|
return this.staticCall(addresses.nativeToken, "balanceOf", [address]);
|
|
@@ -6716,7 +6995,7 @@ class Chain extends VersionControl {
|
|
|
6716
6995
|
* @example chain.lookup('myCoolContractName') // qmqsfddfdgfg...
|
|
6717
6996
|
*/
|
|
6718
6997
|
lookup(name) {
|
|
6719
|
-
return this.
|
|
6998
|
+
return this.staticCall(addresses.nameService, "lookup", [name]);
|
|
6720
6999
|
}
|
|
6721
7000
|
#monitorPeerConnections() {
|
|
6722
7001
|
setInterval(() => {
|