@leofcoin/chain 1.10.22 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/exports/browser/beacon-wire.js +2 -2
- package/exports/browser/{browser-CCfR9KOM-UO9mzy-j.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 +1234 -885
- package/exports/browser/{client-DbZBfpG6-CYDyUGUC.js → client-DbZBfpG6-C1cXgvfr.js} +4 -4
- package/exports/browser/{connection-monitor-DMGAhcC0.js → connection-monitor-BzbeYGEt.js} +46 -26
- package/exports/browser/{index-dTQVgkpf-C6SJ0ZYW.js → index-dTQVgkpf-CkoAiRNX.js} +3 -3
- package/exports/browser/{index-dxD17Fom.js → index-phv5KvaE.js} +7 -7
- package/exports/browser/{messages-DZYCxoZI-D6kIiLKF.js → messages-DZYCxoZI-B472zldo.js} +3 -3
- package/exports/browser/{node-browser-BMfelxXV.js → node-browser-BmyPBkL5.js} +36 -8
- 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 +248 -960
- package/exports/{connection-monitor-h2L8vsS7.js → connection-monitor-BdFLRh-J.js} +33 -3
- package/exports/connection-monitor.js +1 -1
- 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 +33 -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 +7 -5
package/exports/browser/chain.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
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-dxD17Fom.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,26 +2924,46 @@ function requireSemver () {
|
|
|
2915
2924
|
var semverExports = requireSemver();
|
|
2916
2925
|
var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
|
|
2917
2926
|
|
|
2918
|
-
const
|
|
2919
|
-
|
|
2920
|
-
const cleanup = () => {
|
|
2921
|
-
pubsub.unsubscribe("block-processed", blockProcessed);
|
|
2922
|
-
pubsub.unsubscribe(completionTopic, transactionCompleted);
|
|
2927
|
+
const createCanonicalTransactionWaiter = (pubsub, transactionPool, hash) => {
|
|
2928
|
+
let cleanup = () => {
|
|
2923
2929
|
};
|
|
2924
|
-
|
|
2925
|
-
if (!Array.isArray(block?.transactions) || !block.transactions.includes(hash)) return;
|
|
2926
|
-
cleanup();
|
|
2927
|
-
resolve(hash);
|
|
2930
|
+
let cancel = (_reason) => {
|
|
2928
2931
|
};
|
|
2929
|
-
const
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
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
|
+
};
|
|
2938
2967
|
|
|
2939
2968
|
class Transaction extends Protocol {
|
|
2940
2969
|
#pendingNonces = /* @__PURE__ */ new Map();
|
|
@@ -3135,9 +3164,10 @@ class Transaction extends Protocol {
|
|
|
3135
3164
|
await this.validateTransactionSignature(message);
|
|
3136
3165
|
await this.validateNonce(message.decoded.from, message.decoded.nonce);
|
|
3137
3166
|
const hash = await message.hash();
|
|
3167
|
+
const fee = await calculateFee(message.decoded);
|
|
3168
|
+
const waiter = createCanonicalTransactionWaiter(pubsub, transactionPoolStore, hash);
|
|
3138
3169
|
try {
|
|
3139
3170
|
let data;
|
|
3140
|
-
const wait = waitForCanonicalTransaction(pubsub, transactionPoolStore, hash);
|
|
3141
3171
|
await globalThis.transactionPoolStore.put(hash, message.encoded);
|
|
3142
3172
|
this.addPendingNonce(message.decoded.from, message.decoded.nonce);
|
|
3143
3173
|
try {
|
|
@@ -3145,12 +3175,16 @@ class Transaction extends Protocol {
|
|
|
3145
3175
|
} catch (publishError) {
|
|
3146
3176
|
console.warn("peernet publish failed: add-transaction", publishError?.message ?? publishError);
|
|
3147
3177
|
}
|
|
3148
|
-
|
|
3149
|
-
return { hash, data, fee, wait, message };
|
|
3178
|
+
return { hash, data, fee, wait: waiter.wait, message };
|
|
3150
3179
|
} catch (error) {
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
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
|
+
}
|
|
3154
3188
|
throw error;
|
|
3155
3189
|
}
|
|
3156
3190
|
}
|
|
@@ -3478,120 +3512,541 @@ class Contract extends Transaction {
|
|
|
3478
3512
|
}
|
|
3479
3513
|
|
|
3480
3514
|
class EasyWorker {
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
this.#isBrowser = true;
|
|
3518
|
-
this.worker = globalThis;
|
|
3519
|
-
}
|
|
3520
|
-
}
|
|
3521
|
-
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
|
+
}
|
|
3522
3551
|
}
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
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);
|
|
3533
3562
|
}
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
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);
|
|
3539
3584
|
}
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
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;
|
|
3545
3727
|
}
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
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;
|
|
3804
|
+
}
|
|
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);
|
|
3549
3836
|
}
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
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);
|
|
3555
3854
|
}
|
|
3855
|
+
}
|
|
3856
|
+
} finally {
|
|
3857
|
+
ancestors.delete(value2);
|
|
3556
3858
|
}
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
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);
|
|
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;
|
|
3982
|
+
}
|
|
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();
|
|
3594
4048
|
}
|
|
4049
|
+
}
|
|
3595
4050
|
}
|
|
3596
4051
|
|
|
3597
4052
|
class LeofcoinError extends Error {
|
|
@@ -3613,90 +4068,412 @@ class ExecutionError extends LeofcoinError {
|
|
|
3613
4068
|
class ContractDeploymentError extends LeofcoinError {
|
|
3614
4069
|
name = "ContractDeploymentError";
|
|
3615
4070
|
}
|
|
3616
|
-
const isResolveError = (error) => error.name === "ResolveError";
|
|
3617
|
-
const isExecutionError = (error) => error.name === "ExecutionError";
|
|
3618
4071
|
|
|
3619
|
-
const
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
totalMintAmount: BigInt(0),
|
|
3636
|
-
totalTransferAmount: BigInt(0),
|
|
3637
|
-
totalTransactions: BigInt(0),
|
|
3638
|
-
totalBlocks: BigInt(0)
|
|
3639
|
-
}
|
|
3640
|
-
};
|
|
3641
|
-
this.wantList = [];
|
|
3642
|
-
this.ready = new Promise((resolve) => {
|
|
3643
|
-
this.readyResolve = resolve;
|
|
3644
|
-
});
|
|
3645
|
-
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;
|
|
3646
4088
|
}
|
|
3647
|
-
|
|
3648
|
-
|
|
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}`);
|
|
3649
4147
|
}
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
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 });
|
|
3686
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}`);
|
|
3687
4440
|
break;
|
|
3688
4441
|
}
|
|
3689
|
-
case "
|
|
3690
|
-
|
|
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":
|
|
4452
|
+
case "collectFeeError":
|
|
4453
|
+
case "settleRewardsError": {
|
|
4454
|
+
pubsub.publish(data.id, { error: new Error(data.message) });
|
|
3691
4455
|
break;
|
|
3692
4456
|
}
|
|
3693
|
-
case "
|
|
3694
|
-
|
|
3695
|
-
|
|
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
|
+
}
|
|
4465
|
+
case "debug": {
|
|
4466
|
+
if (data.message.includes("loaded transactions for block:")) {
|
|
4467
|
+
pubsub.publish("block-loaded", data.message.replace("loaded transactions for block: ", "").split(" @")[0]);
|
|
4468
|
+
}
|
|
4469
|
+
break;
|
|
4470
|
+
}
|
|
4471
|
+
case "error": {
|
|
4472
|
+
console.error(data.message);
|
|
3696
4473
|
break;
|
|
3697
4474
|
}
|
|
3698
4475
|
case "response": {
|
|
3699
|
-
pubsub.publish(data.id, data.value
|
|
4476
|
+
pubsub.publish(data.id, data.value ?? false);
|
|
3700
4477
|
break;
|
|
3701
4478
|
}
|
|
3702
4479
|
case "addToWantList": {
|
|
@@ -3704,6 +4481,16 @@ class Machine {
|
|
|
3704
4481
|
break;
|
|
3705
4482
|
}
|
|
3706
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
|
+
}
|
|
3707
4494
|
if (data.question === "contract" || data.question === "transaction") {
|
|
3708
4495
|
try {
|
|
3709
4496
|
const input = await peernet.get(data.input, data.question);
|
|
@@ -3731,164 +4518,61 @@ class Machine {
|
|
|
3731
4518
|
}
|
|
3732
4519
|
async updateState(strict = false) {
|
|
3733
4520
|
try {
|
|
3734
|
-
const
|
|
3735
|
-
|
|
3736
|
-
const
|
|
3737
|
-
|
|
3738
|
-
const
|
|
3739
|
-
|
|
3740
|
-
for (const block of blocks) {
|
|
3741
|
-
if (block?.transactions) transactions = [...transactions, ...block.transactions];
|
|
3742
|
-
}
|
|
3743
|
-
transactions = await Promise.all(
|
|
3744
|
-
transactions.map(async (transaction) => new TransactionMessage(await transactionStore.get(transaction)))
|
|
3745
|
-
);
|
|
3746
|
-
const contractsToGet = transactions.reduce((set, current) => {
|
|
3747
|
-
const contract = current.decoded.to;
|
|
3748
|
-
if (!set.includes(contract)) set.push(contract);
|
|
3749
|
-
return set;
|
|
3750
|
-
}, []);
|
|
3751
|
-
const state = {};
|
|
3752
|
-
if (!contractsToGet.includes(addresses.contractFactory)) contractsToGet.push(addresses.contractFactory);
|
|
3753
|
-
if (!contractsToGet.includes(addresses.nativeToken)) contractsToGet.push(addresses.nativeToken);
|
|
3754
|
-
if (!contractsToGet.includes(addresses.nameService)) contractsToGet.push(addresses.nameService);
|
|
3755
|
-
if (!contractsToGet.includes(addresses.validators)) contractsToGet.push(addresses.validators);
|
|
3756
|
-
await Promise.all(
|
|
3757
|
-
contractsToGet.map(async (contract) => {
|
|
3758
|
-
const value = await this.#askWorker("get", { contract, method: "state", params: [] });
|
|
3759
|
-
state[contract] = value;
|
|
3760
|
-
})
|
|
3761
|
-
);
|
|
3762
|
-
let accounts = {};
|
|
3763
|
-
try {
|
|
3764
|
-
const promises = [];
|
|
3765
|
-
for (const address of await accountsStore.keys()) {
|
|
3766
|
-
promises.push(
|
|
3767
|
-
(async () => {
|
|
3768
|
-
accounts[address] = await accountsStore.get(address);
|
|
3769
|
-
})()
|
|
3770
|
-
);
|
|
3771
|
-
}
|
|
3772
|
-
await Promise.all(promises);
|
|
3773
|
-
} catch (error) {
|
|
3774
|
-
const promises = [];
|
|
3775
|
-
for (const transaction of transactions.reverse()) {
|
|
3776
|
-
const { from, to, amount, nonce } = transaction.decoded.data;
|
|
3777
|
-
if (!accounts[from]) {
|
|
3778
|
-
accounts[from] = nonce;
|
|
3779
|
-
promises.push(accountsStore.put(from, nonce));
|
|
3780
|
-
}
|
|
3781
|
-
}
|
|
3782
|
-
await Promise.all(promises);
|
|
3783
|
-
}
|
|
3784
|
-
const sortedStringify = (obj, replacer) => {
|
|
3785
|
-
const sorted = {};
|
|
3786
|
-
const keys = Object.keys(obj).sort();
|
|
3787
|
-
for (const key of keys) {
|
|
3788
|
-
sorted[key] = obj[key];
|
|
3789
|
-
}
|
|
3790
|
-
return JSON.stringify(sorted, replacer);
|
|
3791
|
-
};
|
|
3792
|
-
const tasks = [
|
|
3793
|
-
stateStore.put("lastBlock", sortedStringify(lastBlock, jsonStringifyBigInt)),
|
|
3794
|
-
stateStore.put("states", sortedStringify(state, jsonStringifyBigInt)),
|
|
3795
|
-
stateStore.put("accounts", sortedStringify(accounts, jsonStringifyBigInt)),
|
|
3796
|
-
stateStore.put(
|
|
3797
|
-
"info",
|
|
3798
|
-
sortedStringify(
|
|
3799
|
-
{
|
|
3800
|
-
nativeBurnAmount: await this.totalBurnAmount,
|
|
3801
|
-
nativeBurns: await this.nativeBurns,
|
|
3802
|
-
nativeCalls: await this.nativeCalls,
|
|
3803
|
-
nativeMints: await this.nativeMints,
|
|
3804
|
-
nativeTransfers: await this.nativeTransfers,
|
|
3805
|
-
totalBlocks: await blockStore.length,
|
|
3806
|
-
totalBurnAmount: await this.totalBurnAmount,
|
|
3807
|
-
totalMintAmount: await this.totalMintAmount,
|
|
3808
|
-
totalTransactions: await this.totalTransactions,
|
|
3809
|
-
totalTransferAmount: await this.totalTransferAmount
|
|
3810
|
-
},
|
|
3811
|
-
jsonStringifyBigInt
|
|
3812
|
-
)
|
|
3813
|
-
)
|
|
3814
|
-
// accountsStore.clear()
|
|
3815
|
-
];
|
|
3816
|
-
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);
|
|
3817
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);
|
|
3818
4534
|
} catch (error) {
|
|
3819
|
-
console.error(error);
|
|
3820
4535
|
if (strict) throw error;
|
|
4536
|
+
console.error(error);
|
|
4537
|
+
throw error;
|
|
3821
4538
|
}
|
|
3822
4539
|
}
|
|
3823
|
-
async #init(blocks) {
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
rawInfo = decode(await stateStore.get("info"));
|
|
3857
|
-
this.states.states = JSON.parse(rawStates, jsonParseBigInt);
|
|
3858
|
-
this.states.accounts = JSON.parse(rawAccounts, jsonParseBigInt);
|
|
3859
|
-
this.states.info = JSON.parse(rawInfo, jsonParseBigInt);
|
|
3860
|
-
} catch (error) {
|
|
3861
|
-
console.error(error);
|
|
3862
|
-
this.states.accounts = {};
|
|
3863
|
-
this.states.info = {
|
|
3864
|
-
nativeCalls: BigInt(0),
|
|
3865
|
-
nativeMints: BigInt(0),
|
|
3866
|
-
nativeBurns: BigInt(0),
|
|
3867
|
-
nativeTransfers: BigInt(0),
|
|
3868
|
-
totalBurnAmount: BigInt(0),
|
|
3869
|
-
totalMintAmount: BigInt(0),
|
|
3870
|
-
totalTransferAmount: BigInt(0),
|
|
3871
|
-
totalTransactions: BigInt(0),
|
|
3872
|
-
totalBlocks: BigInt(0)
|
|
3873
|
-
};
|
|
3874
|
-
rawInfo = JSON.stringify(this.states.info, jsonStringifyBigInt);
|
|
3875
|
-
await stateStore.put("info", new TextEncoder().encode(rawInfo));
|
|
3876
|
-
}
|
|
3877
|
-
}
|
|
3878
|
-
const message = {
|
|
3879
|
-
type: "init",
|
|
3880
|
-
input: {
|
|
3881
|
-
blocks,
|
|
3882
|
-
fromState: this.states.lastBlock?.index >= 0,
|
|
3883
|
-
lastBlock: rawLastBlock,
|
|
3884
|
-
state: rawStates,
|
|
3885
|
-
info: rawInfo,
|
|
3886
|
-
// @ts-ignore
|
|
3887
|
-
peerid: peernet.peerId
|
|
3888
|
-
}
|
|
3889
|
-
};
|
|
3890
|
-
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
|
|
3891
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"));
|
|
3892
4576
|
}
|
|
3893
4577
|
async #runContract(contractMessage) {
|
|
3894
4578
|
const hash = await contractMessage.hash();
|
|
@@ -3919,6 +4603,7 @@ class Machine {
|
|
|
3919
4603
|
* @returns Promise<message>
|
|
3920
4604
|
*/
|
|
3921
4605
|
async execute(contract, method, parameters, sender, execution) {
|
|
4606
|
+
if (this.#closed) throw new Error("State machine is closed");
|
|
3922
4607
|
try {
|
|
3923
4608
|
if (contract === contractFactory && method === "registerContract") {
|
|
3924
4609
|
if (await this.has(parameters[0])) throw new Error(`duplicate contract @${parameters[0]}`);
|
|
@@ -3975,28 +4660,8 @@ ${error.message}`);
|
|
|
3975
4660
|
return this.#askWorker("settleRewards", { rewards });
|
|
3976
4661
|
}
|
|
3977
4662
|
get(contract, method, parameters) {
|
|
3978
|
-
return new
|
|
3979
|
-
|
|
3980
|
-
const timeout = setTimeout(() => {
|
|
3981
|
-
pubsub.unsubscribe(id, onmessage);
|
|
3982
|
-
reject(new Error(`Machine.get timeout for ${contract}.${method}`));
|
|
3983
|
-
}, 3e4);
|
|
3984
|
-
const onmessage = (message) => {
|
|
3985
|
-
clearTimeout(timeout);
|
|
3986
|
-
pubsub.unsubscribe(id, onmessage);
|
|
3987
|
-
resolve(message);
|
|
3988
|
-
};
|
|
3989
|
-
pubsub.subscribe(id, onmessage);
|
|
3990
|
-
this.worker.postMessage({
|
|
3991
|
-
type: "get",
|
|
3992
|
-
id,
|
|
3993
|
-
input: {
|
|
3994
|
-
contract,
|
|
3995
|
-
method,
|
|
3996
|
-
params: parameters
|
|
3997
|
-
}
|
|
3998
|
-
});
|
|
3999
|
-
});
|
|
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 ?? []);
|
|
4000
4665
|
}
|
|
4001
4666
|
async has(address) {
|
|
4002
4667
|
return new Promise((resolve, reject) => {
|
|
@@ -4022,6 +4687,7 @@ ${error.message}`);
|
|
|
4022
4687
|
});
|
|
4023
4688
|
}
|
|
4024
4689
|
#askWorker(type, input) {
|
|
4690
|
+
if (this.#closed) return Promise.reject(new Error("State machine is closed"));
|
|
4025
4691
|
return new Promise((resolve, reject) => {
|
|
4026
4692
|
const id = randombytes(20).toString("hex");
|
|
4027
4693
|
const timeout = setTimeout(() => {
|
|
@@ -4079,19 +4745,13 @@ ${error.message}`);
|
|
|
4079
4745
|
return this.getBlocks();
|
|
4080
4746
|
}
|
|
4081
4747
|
get lastBlock() {
|
|
4082
|
-
return this
|
|
4083
|
-
debug$2("lastBlock fallback after worker timeout:", error?.message ?? error);
|
|
4084
|
-
return this.states.lastBlock;
|
|
4085
|
-
});
|
|
4748
|
+
return Promise.resolve(this.states.lastBlock);
|
|
4086
4749
|
}
|
|
4087
4750
|
getCanonicalTip() {
|
|
4088
4751
|
return this.#askWorker("lastBlock");
|
|
4089
4752
|
}
|
|
4090
4753
|
get lastBlockHeight() {
|
|
4091
|
-
return
|
|
4092
|
-
debug$2("lastBlockHeight fallback after worker timeout:", error?.message ?? error);
|
|
4093
|
-
return Number(this.states.lastBlock?.index ?? 0);
|
|
4094
|
-
});
|
|
4754
|
+
return Promise.resolve(Number(this.states.lastBlock.index));
|
|
4095
4755
|
}
|
|
4096
4756
|
getBlocks(from, to) {
|
|
4097
4757
|
return this.#askWorker("blocks", { from, to });
|
|
@@ -4102,7 +4762,7 @@ ${error.message}`);
|
|
|
4102
4762
|
async addLoadedBlock(block) {
|
|
4103
4763
|
debug$2(`adding loaded block: ${block.index}@${block.hash}`);
|
|
4104
4764
|
if (block.decoded) block = { ...block.decoded, hash: await block.hash() };
|
|
4105
|
-
return this.#askWorker("addLoadedBlock",
|
|
4765
|
+
return this.#askWorker("addLoadedBlock", block);
|
|
4106
4766
|
}
|
|
4107
4767
|
async latestTransactions() {
|
|
4108
4768
|
return this.#askWorker("latestTransactions");
|
|
@@ -4150,91 +4810,19 @@ class Jobber {
|
|
|
4150
4810
|
}
|
|
4151
4811
|
}
|
|
4152
4812
|
|
|
4153
|
-
const
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
if (claimedHash && hash !== claimedHash) {
|
|
4158
|
-
throw new Error(`lastBlock hash mismatch: expected ${claimedHash}, got ${hash}`);
|
|
4159
|
-
}
|
|
4160
|
-
return new LastBlockMessage({ hash, index: block.decoded.index });
|
|
4161
|
-
};
|
|
4162
|
-
const resolveLegacyHash = async (hash) => {
|
|
4163
|
-
const blockData = await globalThis.peernet?.get?.(hash, "block");
|
|
4164
|
-
if (!blockData) throw new Error(`unable to resolve lastBlock hash: ${hash}`);
|
|
4165
|
-
return asLastBlockMessage(blockData, hash);
|
|
4166
|
-
};
|
|
4167
|
-
const resolveLastBlockMessage = async (result, depth = 0) => {
|
|
4168
|
-
if (depth > MAX_RESPONSE_DEPTH$1) throw new Error("lastBlock response nesting exceeds limit");
|
|
4169
|
-
if (result instanceof Uint8Array) {
|
|
4170
|
-
let codec;
|
|
4171
|
-
try {
|
|
4172
|
-
codec = new Codec(result);
|
|
4173
|
-
} catch {
|
|
4174
|
-
}
|
|
4175
|
-
if (codec) {
|
|
4176
|
-
if (codec.name === "peernet-response") {
|
|
4177
|
-
const ResponseMessage = globalThis.peernet?.protos?.["peernet-response"];
|
|
4178
|
-
if (!ResponseMessage) throw new Error("peernet-response codec is not registered");
|
|
4179
|
-
const wrapped = new ResponseMessage(result);
|
|
4180
|
-
if (wrapped?.decoded?.response === void 0) throw new Error("peernet-response has no response payload");
|
|
4181
|
-
return resolveLastBlockMessage(wrapped.decoded.response, depth + 1);
|
|
4182
|
-
}
|
|
4183
|
-
if (codec.name === "last-block-message") return new LastBlockMessage(result);
|
|
4184
|
-
if (codec.name === "block-message") return asLastBlockMessage(result);
|
|
4185
|
-
}
|
|
4186
|
-
const candidate = new TextDecoder().decode(result);
|
|
4187
|
-
if (candidate) return resolveLegacyHash(candidate);
|
|
4188
|
-
}
|
|
4189
|
-
if (typeof result === "string") return resolveLegacyHash(result);
|
|
4190
|
-
if (result && typeof result === "object") {
|
|
4191
|
-
const response = result.decoded?.response ?? result.response;
|
|
4192
|
-
if (response !== void 0) return resolveLastBlockMessage(response, depth + 1);
|
|
4193
|
-
if ("hash" in result && "index" in result) return new LastBlockMessage(result);
|
|
4194
|
-
if ("previousHash" in result && "index" in result) return asLastBlockMessage(result);
|
|
4195
|
-
}
|
|
4196
|
-
throw new Error(`invalid lastBlock payload: ${typeof result}`);
|
|
4197
|
-
};
|
|
4198
|
-
|
|
4199
|
-
const MAX_RESPONSE_DEPTH = 3;
|
|
4200
|
-
const decodeResponsePayload = async (result, depth = 0) => {
|
|
4201
|
-
if (depth > MAX_RESPONSE_DEPTH) throw new Error("peernet response nesting exceeds limit");
|
|
4202
|
-
if (result instanceof Uint8Array) {
|
|
4203
|
-
let codec;
|
|
4204
|
-
try {
|
|
4205
|
-
codec = new Codec(result);
|
|
4206
|
-
} catch {
|
|
4207
|
-
}
|
|
4208
|
-
if (codec?.name === "peernet-response") {
|
|
4209
|
-
const ResponseMessage = globalThis.peernet?.protos?.["peernet-response"];
|
|
4210
|
-
if (!ResponseMessage) throw new Error("peernet-response codec is not registered");
|
|
4211
|
-
const wrapped = new ResponseMessage(result);
|
|
4212
|
-
return decodeResponsePayload(wrapped.decoded.response, depth + 1);
|
|
4213
|
-
}
|
|
4214
|
-
try {
|
|
4215
|
-
return JSON.parse(new TextDecoder().decode(result));
|
|
4216
|
-
} catch {
|
|
4217
|
-
return result;
|
|
4218
|
-
}
|
|
4219
|
-
}
|
|
4220
|
-
if (result && typeof result === "object") {
|
|
4221
|
-
const response = result.decoded?.response ?? result.response;
|
|
4222
|
-
if (response !== void 0) return decodeResponsePayload(response, depth + 1);
|
|
4223
|
-
}
|
|
4224
|
-
if (typeof result === "string") {
|
|
4225
|
-
try {
|
|
4226
|
-
return JSON.parse(result);
|
|
4227
|
-
} catch {
|
|
4228
|
-
return result;
|
|
4229
|
-
}
|
|
4230
|
-
}
|
|
4231
|
-
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);
|
|
4232
4817
|
};
|
|
4233
4818
|
|
|
4234
4819
|
class CanonicalStateQueue {
|
|
4235
4820
|
#tail = Promise.resolve();
|
|
4236
4821
|
#failed = false;
|
|
4237
4822
|
#failure;
|
|
4823
|
+
get failed() {
|
|
4824
|
+
return this.#failed;
|
|
4825
|
+
}
|
|
4238
4826
|
run(operation) {
|
|
4239
4827
|
const result = this.#tail.then(async () => {
|
|
4240
4828
|
if (this.#failed) {
|
|
@@ -4260,27 +4848,6 @@ class CanonicalStateQueue {
|
|
|
4260
4848
|
}
|
|
4261
4849
|
}
|
|
4262
4850
|
|
|
4263
|
-
const validateChainLink = (localTip, incoming) => {
|
|
4264
|
-
const localHash = localTip?.hash || "0x0";
|
|
4265
|
-
const localIndex = Number(localTip?.index ?? -1);
|
|
4266
|
-
const hasCanonicalTip = localHash !== "0x0";
|
|
4267
|
-
if (hasCanonicalTip && incoming.index < localIndex) return "stale";
|
|
4268
|
-
if (hasCanonicalTip && incoming.index === localIndex) {
|
|
4269
|
-
if (incoming.hash === localHash) return "duplicate";
|
|
4270
|
-
throw new Error(`Block conflict detected at index ${incoming.index}`);
|
|
4271
|
-
}
|
|
4272
|
-
const expectedIndex = hasCanonicalTip ? localIndex + 1 : 0;
|
|
4273
|
-
if (incoming.index !== expectedIndex) {
|
|
4274
|
-
throw new Error(`Unexpected block index ${incoming.index}; expected ${expectedIndex}`);
|
|
4275
|
-
}
|
|
4276
|
-
if (incoming.previousHash !== localHash) {
|
|
4277
|
-
throw new Error(
|
|
4278
|
-
`previousHash mismatch at index ${incoming.index}: expected ${localHash}, got ${incoming.previousHash}`
|
|
4279
|
-
);
|
|
4280
|
-
}
|
|
4281
|
-
return "append";
|
|
4282
|
-
};
|
|
4283
|
-
|
|
4284
4851
|
const resolveTransactionReference = async (expectedHash, transactionData) => {
|
|
4285
4852
|
const transaction = transactionData instanceof TransactionMessage ? transactionData : new TransactionMessage(transactionData);
|
|
4286
4853
|
const actualHash = await transaction.hash();
|
|
@@ -4290,6 +4857,41 @@ const resolveTransactionReference = async (expectedHash, transactionData) => {
|
|
|
4290
4857
|
return transaction;
|
|
4291
4858
|
};
|
|
4292
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
|
+
|
|
4293
4895
|
const debug$1 = createDebugger("leofcoin/state");
|
|
4294
4896
|
class State extends Contract {
|
|
4295
4897
|
constructor(config) {
|
|
@@ -4323,15 +4925,6 @@ class State extends Contract {
|
|
|
4323
4925
|
response: { blocks: await globalThis.blockStore.keys() }
|
|
4324
4926
|
});
|
|
4325
4927
|
};
|
|
4326
|
-
this.#loadBlockTransactions = (transactions) => Promise.all(
|
|
4327
|
-
transactions.map(async (transaction) => new TransactionMessage(await peernet.get(transaction, "transaction")))
|
|
4328
|
-
);
|
|
4329
|
-
this.#getLastTransactions = async () => {
|
|
4330
|
-
let lastTransactions = (await Promise.all(
|
|
4331
|
-
(await this.blocks).filter((block) => block.loaded).slice(-24).map((block) => this.#loadBlockTransactions(block.transactions))
|
|
4332
|
-
)).reduce((all, transactions) => [...all, ...transactions], []);
|
|
4333
|
-
return Promise.all(lastTransactions.map((transaction) => transaction.hash()));
|
|
4334
|
-
};
|
|
4335
4928
|
}
|
|
4336
4929
|
#resolveErrored;
|
|
4337
4930
|
#lastResolvedTime;
|
|
@@ -4349,7 +4942,13 @@ class State extends Contract {
|
|
|
4349
4942
|
#machine;
|
|
4350
4943
|
#canonicalStateQueue;
|
|
4351
4944
|
withCanonicalState(operation) {
|
|
4352
|
-
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
|
+
});
|
|
4353
4952
|
}
|
|
4354
4953
|
canonicalTip() {
|
|
4355
4954
|
return this.#machine ? this.#machine.getCanonicalTip() : Promise.resolve({ index: -1, hash: "0x0", previousHash: "0x0" });
|
|
@@ -4449,91 +5048,79 @@ class State extends Contract {
|
|
|
4449
5048
|
#lastBlockHandler;
|
|
4450
5049
|
#knownBlocksHandler;
|
|
4451
5050
|
async init() {
|
|
4452
|
-
debug$1("State init start");
|
|
4453
5051
|
this.jobber = new Jobber(this.resolveTimeout);
|
|
4454
5052
|
await globalThis.peernet.addRequestHandler("lastBlock", this.#lastBlockHandler);
|
|
4455
5053
|
await globalThis.peernet.addRequestHandler("knownBlocks", this.#knownBlocksHandler);
|
|
4456
5054
|
await globalThis.peernet.addRequestHandler("chainState", this.#chainStateHandler);
|
|
4457
|
-
let localBlockHash;
|
|
4458
|
-
let blockMessage;
|
|
4459
|
-
let localBlock;
|
|
4460
|
-
console.log("State init before try-catch");
|
|
4461
5055
|
try {
|
|
4462
|
-
const
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
}
|
|
4475
|
-
console.log("State init after localBlock set");
|
|
4476
|
-
} else {
|
|
4477
|
-
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;
|
|
4478
5068
|
}
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
this.knownBlocks = await globalThis.blockStore.keys();
|
|
4488
|
-
} catch (error) {
|
|
4489
|
-
console.log("no local known blocks found");
|
|
4490
|
-
}
|
|
4491
|
-
try {
|
|
4492
|
-
if (localBlock?.hash && localBlock.hash !== "0x0") {
|
|
4493
|
-
try {
|
|
4494
|
-
if (!await globalThis.stateStore.has("lastBlock")) {
|
|
4495
|
-
await this.resolveBlocks();
|
|
4496
|
-
}
|
|
4497
|
-
} catch {
|
|
4498
|
-
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;
|
|
4499
5077
|
}
|
|
4500
|
-
} else {
|
|
4501
|
-
await this.resolveBlocks();
|
|
4502
5078
|
}
|
|
4503
|
-
|
|
4504
|
-
|
|
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);
|
|
4505
5095
|
await machine.ready;
|
|
4506
5096
|
this.#machine = machine;
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
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");
|
|
4511
5104
|
this.#loaded = true;
|
|
4512
5105
|
this.#chainState = "loaded";
|
|
4513
5106
|
} catch (error) {
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
console.log(error);
|
|
5107
|
+
this.#loaded = false;
|
|
5108
|
+
this.#chainState = "loading";
|
|
5109
|
+
this.#syncState = "errored";
|
|
5110
|
+
throw error;
|
|
4519
5111
|
}
|
|
4520
5112
|
}
|
|
4521
5113
|
async updateState(message) {
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
await this.#machine.updateState(true);
|
|
4533
|
-
} catch (error) {
|
|
4534
|
-
console.error(error);
|
|
4535
|
-
throw error;
|
|
4536
|
-
}
|
|
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);
|
|
4537
5124
|
}
|
|
4538
5125
|
getLatestBlock() {
|
|
4539
5126
|
return this.#getLatestBlock();
|
|
@@ -4591,7 +5178,7 @@ class State extends Contract {
|
|
|
4591
5178
|
this.#blocks[index] = { hash, ...block.decoded };
|
|
4592
5179
|
this.#blockHashMap.set(hash, index);
|
|
4593
5180
|
debug$1(`resolved block: ${hash} @${index} ${formatBytes(size)}`);
|
|
4594
|
-
globalThis.pubsub
|
|
5181
|
+
publishObserverEvent(globalThis.pubsub, "block-resolved", { hash, index });
|
|
4595
5182
|
this.#lastResolved = this.#blocks[index];
|
|
4596
5183
|
this.#lastResolvedTime = Date.now();
|
|
4597
5184
|
hash = block.decoded.previousHash;
|
|
@@ -4728,7 +5315,7 @@ class State extends Contract {
|
|
|
4728
5315
|
const localIndex = localBlock ? Number(localBlock.index) : -1;
|
|
4729
5316
|
const remoteIndex = Number(lastBlock.index);
|
|
4730
5317
|
const remoteBlockHash = lastBlock.hash;
|
|
4731
|
-
globalThis.pubsub
|
|
5318
|
+
publishObserverEvent(globalThis.pubsub, "sync-progress", {
|
|
4732
5319
|
phase: "syncing",
|
|
4733
5320
|
startIndex: localIndex,
|
|
4734
5321
|
localIndex,
|
|
@@ -4764,6 +5351,7 @@ class State extends Contract {
|
|
|
4764
5351
|
if (localStateHash !== remoteBlockHash) {
|
|
4765
5352
|
debug$1(`Resolving remote block: ${remoteBlockHash} @${remoteIndex} (differs from local state)`);
|
|
4766
5353
|
await this.resolveBlock(remoteBlockHash);
|
|
5354
|
+
await globalThis.chainStore.put("recoveryTarget", remoteBlockHash);
|
|
4767
5355
|
const blocksSynced = remoteIndex - localIndex;
|
|
4768
5356
|
debug$1(`Resolved ${blocksSynced} new block(s)`);
|
|
4769
5357
|
const blocks = this.#blocks;
|
|
@@ -4783,7 +5371,7 @@ class State extends Contract {
|
|
|
4783
5371
|
} else {
|
|
4784
5372
|
debug$1(`Block already in local state. Remote hash: ${remoteBlockHash} matches local state`);
|
|
4785
5373
|
}
|
|
4786
|
-
globalThis.pubsub
|
|
5374
|
+
publishObserverEvent(globalThis.pubsub, "sync-progress", {
|
|
4787
5375
|
phase: "synced",
|
|
4788
5376
|
startIndex: localIndex,
|
|
4789
5377
|
localIndex: remoteIndex,
|
|
@@ -4871,112 +5459,23 @@ class State extends Contract {
|
|
|
4871
5459
|
}
|
|
4872
5460
|
return latest;
|
|
4873
5461
|
}
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
// todo throw error
|
|
4877
|
-
async #_executeTransaction(transaction, validators, feesEnabled, burnBasisPoints, blockTimestamp) {
|
|
4878
|
-
try {
|
|
4879
|
-
const hash = await transaction.hash();
|
|
4880
|
-
if (feesEnabled) {
|
|
4881
|
-
const fee = BigInt(await calculateFee(transaction.decoded));
|
|
4882
|
-
const { payments, burned } = distributeTransactionFee(fee, hash, validators, burnBasisPoints);
|
|
4883
|
-
await this.#machine.collectFee(transaction.decoded.from, payments, burned);
|
|
4884
|
-
}
|
|
4885
|
-
await this.#machine.execute(
|
|
4886
|
-
transaction.decoded.to,
|
|
4887
|
-
transaction.decoded.method,
|
|
4888
|
-
transaction.decoded.params,
|
|
4889
|
-
transaction.decoded.from,
|
|
4890
|
-
{ seed: hash, timestamp: Number(blockTimestamp) }
|
|
4891
|
-
);
|
|
4892
|
-
} catch (error) {
|
|
4893
|
-
console.log(error);
|
|
4894
|
-
await globalThis.transactionPoolStore.delete(await transaction.hash());
|
|
4895
|
-
console.log("removing invalid transaction");
|
|
4896
|
-
if (isExecutionError(error)) {
|
|
4897
|
-
console.log(error);
|
|
4898
|
-
}
|
|
4899
|
-
console.log(error);
|
|
4900
|
-
throw error;
|
|
4901
|
-
}
|
|
5462
|
+
async applyRecoveredBlock(block, transition) {
|
|
5463
|
+
throw new Error("Canonical block application is not implemented");
|
|
4902
5464
|
}
|
|
4903
|
-
/**
|
|
4904
|
-
*
|
|
4905
|
-
* @param {Block[]} blocks
|
|
4906
|
-
*/
|
|
4907
5465
|
async #loadBlocks(blocks, transition) {
|
|
4908
5466
|
this.#chainState = "loading";
|
|
4909
|
-
if (blocks.some((block) => !block)) {
|
|
4910
|
-
throw new Error("missing block data during load; chain resolution incomplete");
|
|
4911
|
-
}
|
|
4912
|
-
const poolTransactionKeys = new Set(await globalThis.transactionPoolStore.keys());
|
|
4913
|
-
debug$1(`pool transactions: ${poolTransactionKeys.size}`);
|
|
4914
|
-
debug$1(`loading ${blocks.length} blocks`);
|
|
4915
5467
|
for (const block of blocks) {
|
|
4916
|
-
if (block
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
}
|
|
4928
|
-
debug$1(`loading block: ${Number(block.index)} ${block.hash}`);
|
|
4929
|
-
let transactions = await this.#loadBlockTransactions(block.transactions || []);
|
|
4930
|
-
const validators = block.validators.map(({ address }) => address);
|
|
4931
|
-
debug$1(`loading transactions: ${transactions.length} for block ${block.index}`);
|
|
4932
|
-
transition.markMutating();
|
|
4933
|
-
for (const transaction of transactions) {
|
|
4934
|
-
const hash = await transaction.hash();
|
|
4935
|
-
if (poolTransactionKeys.has(hash)) await globalThis.transactionPoolStore.delete(hash);
|
|
4936
|
-
}
|
|
4937
|
-
transactions = transactions.sort((a, b) => {
|
|
4938
|
-
if (a.decoded.priority !== b.decoded.priority) return a.decoded.priority ? -1 : 1;
|
|
4939
|
-
const left = BigInt(a.decoded.nonce);
|
|
4940
|
-
const right = BigInt(b.decoded.nonce);
|
|
4941
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
4942
|
-
});
|
|
4943
|
-
debug$1(`executing ${transactions.length} transactions for block ${block.index}`);
|
|
4944
|
-
const feesEnabled = supportsTransactionFees(block.protocolVersion);
|
|
4945
|
-
const monetaryPolicyEnabled = supportsMonetaryPolicy(block.protocolVersion);
|
|
4946
|
-
if (monetaryPolicyEnabled) await validateBlockResourceLimits(transactions);
|
|
4947
|
-
const policy = monetaryPolicyEnabled ? calculateMonetaryPolicy(
|
|
4948
|
-
BigInt(await this.#machine.get(nativeToken, "totalSupply")),
|
|
4949
|
-
BigInt(await this.#machine.get(nativeToken, "targetSupply"))
|
|
4950
|
-
) : { subsidy: 0n, burnBasisPoints: 1000n };
|
|
4951
|
-
for (const transaction of transactions) {
|
|
4952
|
-
await this.#_executeTransaction(
|
|
4953
|
-
transaction,
|
|
4954
|
-
validators,
|
|
4955
|
-
feesEnabled,
|
|
4956
|
-
policy.burnBasisPoints,
|
|
4957
|
-
block.timestamp
|
|
4958
|
-
);
|
|
4959
|
-
}
|
|
4960
|
-
if (monetaryPolicyEnabled && policy.subsidy > 0n) {
|
|
4961
|
-
await this.#machine.settleRewards([
|
|
4962
|
-
...distributeAmount(policy.subsidy, validators, Number(block.index) % validators.length)
|
|
4963
|
-
]);
|
|
4964
|
-
}
|
|
4965
|
-
debug$1(`executed transactions for block ${block.index}`);
|
|
4966
|
-
const registered = await this.#machine.addLoadedBlock({ ...block, loaded: true });
|
|
4967
|
-
if (registered !== true) throw new Error(`Loaded block registration failed after execution: ${block.hash}`);
|
|
4968
|
-
this.#blocks[block.index].loaded = true;
|
|
4969
|
-
if (Number(block.index) === 0) this.#loaded = true;
|
|
4970
|
-
debug$1(`loaded block: ${block.hash} @${Number(block.index)}`);
|
|
4971
|
-
globalThis.pubsub.publish("block-loaded", { ...block });
|
|
4972
|
-
} catch (error) {
|
|
4973
|
-
console.error(error);
|
|
4974
|
-
for (const transaction of block.transactions) {
|
|
4975
|
-
this.wantList.push(transaction);
|
|
4976
|
-
}
|
|
4977
|
-
throw error;
|
|
4978
|
-
}
|
|
4979
|
-
}
|
|
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 });
|
|
4980
5479
|
}
|
|
4981
5480
|
this.#chainState = "loaded";
|
|
4982
5481
|
return true;
|
|
@@ -5036,18 +5535,16 @@ class State extends Contract {
|
|
|
5036
5535
|
return this.syncChain(latest);
|
|
5037
5536
|
}
|
|
5038
5537
|
async triggerLoad() {
|
|
5039
|
-
if (this.#
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
await machine.ready;
|
|
5043
|
-
this.#machine = machine;
|
|
5044
|
-
}
|
|
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));
|
|
5045
5541
|
}
|
|
5046
5542
|
}
|
|
5047
5543
|
|
|
5048
5544
|
class VersionControl extends State {
|
|
5049
5545
|
constructor(config) {
|
|
5050
5546
|
super(config);
|
|
5547
|
+
this.version = PROTOCOL_VERSION;
|
|
5051
5548
|
}
|
|
5052
5549
|
#currentVersion = PROTOCOL_VERSION;
|
|
5053
5550
|
#reachedOneZeroZero = REACHED_ONE_ZERO_ZERO;
|
|
@@ -5191,80 +5688,13 @@ const assertHashFormatState = async (chainStore, stateStore) => {
|
|
|
5191
5688
|
const parsed = /^(\d+)\.(\d+)\.\d+$/.exec(version);
|
|
5192
5689
|
const current = PROTOCOL_VERSION.split(".");
|
|
5193
5690
|
if (parsed && parsed[1] === current[0] && parsed[2] === current[1]) return;
|
|
5194
|
-
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)`);
|
|
5195
5692
|
}
|
|
5196
|
-
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")) {
|
|
5197
5694
|
throw new Error("existing chainstate has no protocol version; explicit canonical hash migration required");
|
|
5198
5695
|
}
|
|
5199
5696
|
};
|
|
5200
5697
|
|
|
5201
|
-
const CHAIN_ID_SCHEMA = "leofcoin:chain-identity:v1";
|
|
5202
|
-
const GENESIS_CONTRACT_NAMES = ["contractFactory", "nativeToken", "nameService", "validators"];
|
|
5203
|
-
const assertChainId = (value) => {
|
|
5204
|
-
if (typeof value !== "string" || !/^lfc:[0-9a-f]{64}$/.test(value))
|
|
5205
|
-
throw new Error("Invalid chain ID; expected lfc: followed by 64 lowercase hex characters");
|
|
5206
|
-
return value;
|
|
5207
|
-
};
|
|
5208
|
-
const createChainIdentity = async (network, contracts) => {
|
|
5209
|
-
if (typeof network !== "string" || !/^[a-z0-9][a-z0-9:-]{0,79}$/.test(network))
|
|
5210
|
-
throw new Error("Invalid genesis network namespace");
|
|
5211
|
-
const genesisContracts = {};
|
|
5212
|
-
for (const name of GENESIS_CONTRACT_NAMES) {
|
|
5213
|
-
const hash = contracts?.[name];
|
|
5214
|
-
if (typeof hash !== "string" || !/^[A-Z2-7]{32,128}$/.test(hash))
|
|
5215
|
-
throw new Error(`Invalid genesis contract hash: ${name}`);
|
|
5216
|
-
genesisContracts[name] = hash;
|
|
5217
|
-
}
|
|
5218
|
-
const bytes = new TextEncoder().encode(JSON.stringify({ schema: CHAIN_ID_SCHEMA, network, genesisContracts }));
|
|
5219
|
-
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
5220
|
-
const chainId = "lfc:" + [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
5221
|
-
return { schema: CHAIN_ID_SCHEMA, chainId, network, genesisContracts };
|
|
5222
|
-
};
|
|
5223
|
-
const verifyChainIdentity = async (value) => {
|
|
5224
|
-
const identity = value;
|
|
5225
|
-
if (!identity || identity.schema !== CHAIN_ID_SCHEMA)
|
|
5226
|
-
throw new Error("Unsupported chain identity response");
|
|
5227
|
-
const verified = await createChainIdentity(identity.network, identity.genesisContracts);
|
|
5228
|
-
if (verified.chainId !== assertChainId(identity.chainId))
|
|
5229
|
-
throw new Error("Chain ID does not match its genesis contracts");
|
|
5230
|
-
return verified;
|
|
5231
|
-
};
|
|
5232
|
-
const findChainPresence = async (chainId, peerIds, requestIdentity, timeoutMs = 3e3) => {
|
|
5233
|
-
assertChainId(chainId);
|
|
5234
|
-
const peers = [...new Set(peerIds)].slice(0, 16);
|
|
5235
|
-
const matches = [];
|
|
5236
|
-
let responded = 0;
|
|
5237
|
-
let unavailable = 0;
|
|
5238
|
-
await Promise.all(peers.map(async (peerId) => {
|
|
5239
|
-
let timer;
|
|
5240
|
-
try {
|
|
5241
|
-
const result = await Promise.race([
|
|
5242
|
-
Promise.resolve().then(() => requestIdentity(peerId)),
|
|
5243
|
-
new Promise((_, reject) => {
|
|
5244
|
-
timer = setTimeout(() => reject(new Error("Chain identity request timed out")), timeoutMs);
|
|
5245
|
-
})
|
|
5246
|
-
]);
|
|
5247
|
-
const identity = await verifyChainIdentity(result);
|
|
5248
|
-
responded++;
|
|
5249
|
-
if (identity.chainId === chainId)
|
|
5250
|
-
matches.push(peerId);
|
|
5251
|
-
} catch {
|
|
5252
|
-
unavailable++;
|
|
5253
|
-
} finally {
|
|
5254
|
-
clearTimeout(timer);
|
|
5255
|
-
}
|
|
5256
|
-
}));
|
|
5257
|
-
return {
|
|
5258
|
-
chainId,
|
|
5259
|
-
status: matches.length ? "found" : !peers.length || unavailable ? "unknown" : "not-observed",
|
|
5260
|
-
checkedPeers: peers.length,
|
|
5261
|
-
respondedPeers: responded,
|
|
5262
|
-
unavailablePeers: unavailable,
|
|
5263
|
-
matchingPeers: matches.sort(),
|
|
5264
|
-
scope: "reachable peers only; offline or undiscovered chains may still exist"
|
|
5265
|
-
};
|
|
5266
|
-
};
|
|
5267
|
-
|
|
5268
5698
|
const debug = createDebugger("leofcoin/chain");
|
|
5269
5699
|
class Chain extends VersionControl {
|
|
5270
5700
|
constructor(config) {
|
|
@@ -5864,10 +6294,10 @@ class Chain extends VersionControl {
|
|
|
5864
6294
|
});
|
|
5865
6295
|
});
|
|
5866
6296
|
globalThis.peernet.subscribe("add-block", this.#addBlock.bind(this));
|
|
5867
|
-
globalThis.peernet.subscribe("invalid-transaction", this.#invalidTransaction
|
|
6297
|
+
globalThis.peernet.subscribe("invalid-transaction", (bytes) => this.#invalidTransaction(decodeWire$1("invalid-transaction", bytes)));
|
|
5868
6298
|
globalThis.peernet.subscribe("send-transaction", this.#sendTransaction.bind(this));
|
|
5869
6299
|
globalThis.peernet.subscribe("add-transaction", this.#addTransaction.bind(this));
|
|
5870
|
-
globalThis.peernet.subscribe("validator:timeout", this.#validatorTimeout
|
|
6300
|
+
globalThis.peernet.subscribe("validator:timeout", (bytes) => this.#validatorTimeout(decodeWire$1("validator:timeout", bytes)));
|
|
5871
6301
|
globalThis.peernet.subscribe("consensus:propose", this.#handleProposal.bind(this));
|
|
5872
6302
|
globalThis.peernet.subscribe("consensus:prevote", this.#handlePrevote.bind(this));
|
|
5873
6303
|
globalThis.peernet.subscribe("consensus:precommit", this.#handlePrecommit.bind(this));
|
|
@@ -5913,52 +6343,7 @@ class Chain extends VersionControl {
|
|
|
5913
6343
|
let response = await peer.request(node.encoded);
|
|
5914
6344
|
console.log(`raw response for request ${request}:`, response);
|
|
5915
6345
|
if (response === void 0 || response === null) return response;
|
|
5916
|
-
|
|
5917
|
-
if (payload === void 0 || payload === null) return payload;
|
|
5918
|
-
if (payload instanceof Uint8Array || typeof Buffer !== "undefined" && Buffer.isBuffer(payload)) {
|
|
5919
|
-
let bytes = payload instanceof Uint8Array ? payload : new Uint8Array(payload);
|
|
5920
|
-
for (let i = 0; i < 3; i += 1) {
|
|
5921
|
-
try {
|
|
5922
|
-
const codec = new Codec(bytes);
|
|
5923
|
-
if (codec.name !== "peernet-response") break;
|
|
5924
|
-
const wrapped = await new globalThis.peernet.protos["peernet-response"](bytes);
|
|
5925
|
-
if (wrapped?.decoded?.response === void 0) break;
|
|
5926
|
-
const next = wrapped.decoded.response;
|
|
5927
|
-
if (next instanceof Uint8Array || typeof Buffer !== "undefined" && Buffer.isBuffer(next)) {
|
|
5928
|
-
bytes = next instanceof Uint8Array ? next : new Uint8Array(next);
|
|
5929
|
-
continue;
|
|
5930
|
-
}
|
|
5931
|
-
return normalizeResponse(next);
|
|
5932
|
-
} catch {
|
|
5933
|
-
break;
|
|
5934
|
-
}
|
|
5935
|
-
}
|
|
5936
|
-
try {
|
|
5937
|
-
return JSON.parse(new TextDecoder().decode(bytes));
|
|
5938
|
-
} catch {
|
|
5939
|
-
return bytes;
|
|
5940
|
-
}
|
|
5941
|
-
}
|
|
5942
|
-
if (payload && typeof payload === "object") {
|
|
5943
|
-
const objectPayload = payload;
|
|
5944
|
-
const decoded = objectPayload.decoded;
|
|
5945
|
-
if ("decoded" in objectPayload && decoded && "response" in decoded) {
|
|
5946
|
-
return normalizeResponse(decoded.response);
|
|
5947
|
-
}
|
|
5948
|
-
if ("response" in objectPayload) {
|
|
5949
|
-
return normalizeResponse(objectPayload.response);
|
|
5950
|
-
}
|
|
5951
|
-
}
|
|
5952
|
-
if (typeof payload === "string") {
|
|
5953
|
-
try {
|
|
5954
|
-
return JSON.parse(payload);
|
|
5955
|
-
} catch {
|
|
5956
|
-
return payload;
|
|
5957
|
-
}
|
|
5958
|
-
}
|
|
5959
|
-
return payload;
|
|
5960
|
-
};
|
|
5961
|
-
return await normalizeResponse(response);
|
|
6346
|
+
return decodeResponsePayload(response);
|
|
5962
6347
|
} catch (error) {
|
|
5963
6348
|
const peerId = peer?.peerId || peer?.id || peer?.address || "unknown";
|
|
5964
6349
|
debug(`peernet request failed: ${request} -> ${peerId}:`, error?.message ?? error);
|
|
@@ -5966,60 +6351,11 @@ class Chain extends VersionControl {
|
|
|
5966
6351
|
}
|
|
5967
6352
|
}
|
|
5968
6353
|
async #decodeKnownBlocksResponse(response) {
|
|
5969
|
-
if (!response) return null;
|
|
5970
|
-
|
|
5971
|
-
return { blocks: response };
|
|
5972
|
-
}
|
|
5973
|
-
if (Array.isArray(response.blocks)) {
|
|
5974
|
-
return { blocks: response.blocks };
|
|
5975
|
-
}
|
|
5976
|
-
if (response.response && Array.isArray(response.response.blocks)) {
|
|
5977
|
-
return { blocks: response.response.blocks };
|
|
5978
|
-
}
|
|
5979
|
-
if (typeof response === "string") {
|
|
5980
|
-
try {
|
|
5981
|
-
const parsed = JSON.parse(response);
|
|
5982
|
-
if (Array.isArray(parsed)) return { blocks: parsed };
|
|
5983
|
-
if (parsed && Array.isArray(parsed.blocks)) return { blocks: parsed.blocks };
|
|
5984
|
-
if (parsed?.response && Array.isArray(parsed.response.blocks)) return { blocks: parsed.response.blocks };
|
|
5985
|
-
} catch {
|
|
5986
|
-
return null;
|
|
5987
|
-
}
|
|
5988
|
-
return null;
|
|
5989
|
-
}
|
|
5990
|
-
if (!(response instanceof Uint8Array)) return null;
|
|
5991
|
-
let payload = response;
|
|
5992
|
-
for (let i = 0; i < 3; i++) {
|
|
5993
|
-
if (!(payload instanceof Uint8Array)) break;
|
|
5994
|
-
try {
|
|
5995
|
-
const nestedResponse = await new globalThis.peernet.protos["peernet-response"](payload);
|
|
5996
|
-
payload = nestedResponse?.decoded?.response;
|
|
5997
|
-
} catch {
|
|
5998
|
-
break;
|
|
5999
|
-
}
|
|
6000
|
-
}
|
|
6001
|
-
if (Array.isArray(payload)) return { blocks: payload };
|
|
6002
|
-
if (payload && Array.isArray(payload.blocks)) return { blocks: payload.blocks };
|
|
6003
|
-
if (payload?.response && Array.isArray(payload.response.blocks)) return { blocks: payload.response.blocks };
|
|
6004
|
-
try {
|
|
6005
|
-
const decoded = new TextDecoder().decode(response);
|
|
6006
|
-
const parsed = JSON.parse(decoded);
|
|
6007
|
-
if (Array.isArray(parsed)) return { blocks: parsed };
|
|
6008
|
-
if (parsed && Array.isArray(parsed.blocks)) return { blocks: parsed.blocks };
|
|
6009
|
-
if (parsed?.response && Array.isArray(parsed.response.blocks)) return { blocks: parsed.response.blocks };
|
|
6010
|
-
} catch {
|
|
6011
|
-
return null;
|
|
6012
|
-
}
|
|
6013
|
-
return null;
|
|
6354
|
+
if (!Array.isArray(response) || response.length > 1e5 || response.some((hash) => typeof hash !== "string")) return null;
|
|
6355
|
+
return { blocks: response };
|
|
6014
6356
|
}
|
|
6015
6357
|
async getPeerTransactionPool(peer) {
|
|
6016
6358
|
let transactionsInPool = await this.#makeRequest(peer, "transactionPool");
|
|
6017
|
-
if (transactionsInPool instanceof Uint8Array) {
|
|
6018
|
-
transactionsInPool = new TextDecoder().decode(transactionsInPool);
|
|
6019
|
-
}
|
|
6020
|
-
if (typeof transactionsInPool === "string") {
|
|
6021
|
-
transactionsInPool = JSON.parse(transactionsInPool);
|
|
6022
|
-
}
|
|
6023
6359
|
if (!Array.isArray(transactionsInPool) || transactionsInPool.some((hash) => typeof hash !== "string")) {
|
|
6024
6360
|
throw new Error("transactionPool response must be an array of transaction hashes");
|
|
6025
6361
|
}
|
|
@@ -6157,7 +6493,6 @@ class Chain extends VersionControl {
|
|
|
6157
6493
|
debug(
|
|
6158
6494
|
`sync finished with peer ${peerId}: state=${this.syncState} localNow=${(await this.lastBlock)?.index ?? -1}`
|
|
6159
6495
|
);
|
|
6160
|
-
this.machine.states.info = stateInfo;
|
|
6161
6496
|
} catch (error) {
|
|
6162
6497
|
const peerName = peer?.peerId || peer?.id || peer?.address || peerId || "unknown";
|
|
6163
6498
|
debug(`stateInfo/syncChain failed: ${peerName}:`, error?.message ?? error);
|
|
@@ -6195,7 +6530,7 @@ class Chain extends VersionControl {
|
|
|
6195
6530
|
} catch (error) {
|
|
6196
6531
|
await transactionPoolStore.delete(hash);
|
|
6197
6532
|
try {
|
|
6198
|
-
globalThis.peernet.publish("invalid-transaction", hash);
|
|
6533
|
+
globalThis.peernet.publish("invalid-transaction", encodeWire$1("invalid-transaction", hash));
|
|
6199
6534
|
} catch (publishError) {
|
|
6200
6535
|
debug("peernet publish failed: invalid-transaction", publishError?.message ?? publishError);
|
|
6201
6536
|
}
|
|
@@ -6206,7 +6541,13 @@ class Chain extends VersionControl {
|
|
|
6206
6541
|
async #addBlock(block) {
|
|
6207
6542
|
return this.withCanonicalState((transition) => this.#applyBlock(block, transition));
|
|
6208
6543
|
}
|
|
6209
|
-
|
|
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) {
|
|
6210
6551
|
const receivedEncoded = block instanceof BlockMessage ? block.encoded : block;
|
|
6211
6552
|
const blockMessage = await new BlockMessage(block);
|
|
6212
6553
|
const hash = await blockMessage.hash();
|
|
@@ -6240,13 +6581,14 @@ class Chain extends VersionControl {
|
|
|
6240
6581
|
console.log(`[chain] \u2705 Block data integrity verified: ${hash}`);
|
|
6241
6582
|
await this.#validateBlockValidators(blockMessage);
|
|
6242
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);
|
|
6243
6586
|
transition.markMutating();
|
|
6244
6587
|
await Promise.all(
|
|
6245
6588
|
blockMessage.decoded.transactions.map(async (transactionHash) => {
|
|
6246
6589
|
if (await transactionPoolStore.has(transactionHash)) await transactionPoolStore.delete(transactionHash);
|
|
6247
6590
|
})
|
|
6248
6591
|
);
|
|
6249
|
-
await globalThis.blockStore.put(hash, blockMessage.encoded);
|
|
6250
6592
|
this.#blocks[blockIndex] = {
|
|
6251
6593
|
hash,
|
|
6252
6594
|
...blockMessage.decoded
|
|
@@ -6280,9 +6622,6 @@ class Chain extends VersionControl {
|
|
|
6280
6622
|
}
|
|
6281
6623
|
const noncesByAddress = {};
|
|
6282
6624
|
for (let transaction of transactions) {
|
|
6283
|
-
globalThis.pubsub.publish("transaction-processed", transaction.encoded);
|
|
6284
|
-
if (transaction.decoded.to === globalThis.peernet.selectedAccount)
|
|
6285
|
-
globalThis.pubsub.publish("account-transaction-processed", transaction.encoded);
|
|
6286
6625
|
if (!noncesByAddress[transaction.decoded.from] || noncesByAddress?.[transaction.decoded.from] < transaction.decoded.nonce) {
|
|
6287
6626
|
noncesByAddress[transaction.decoded.from] = transaction.decoded.nonce;
|
|
6288
6627
|
}
|
|
@@ -6300,14 +6639,19 @@ class Chain extends VersionControl {
|
|
|
6300
6639
|
const registered = await this.machine.addLoadedBlock({ ...blockMessage.decoded, loaded: true, hash });
|
|
6301
6640
|
if (registered !== true) throw new Error(`Block registration failed after execution: ${hash}`);
|
|
6302
6641
|
try {
|
|
6303
|
-
await this
|
|
6642
|
+
await this.#recordValidatorSnapshot(blockMessage.decoded.index);
|
|
6304
6643
|
} catch (snapshotError) {
|
|
6305
6644
|
debug(`failed to record validator snapshot: ${snapshotError?.message ?? snapshotError}`);
|
|
6306
6645
|
}
|
|
6307
6646
|
await this.updateState(blockMessage);
|
|
6308
6647
|
debug(`added block: ${hash}`);
|
|
6309
|
-
|
|
6310
|
-
|
|
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);
|
|
6311
6655
|
}
|
|
6312
6656
|
async participate(address) {
|
|
6313
6657
|
try {
|
|
@@ -6355,15 +6699,14 @@ class Chain extends VersionControl {
|
|
|
6355
6699
|
async #handleTransaction(transaction, latestTransactions, block, feePayments = { payments: [], burned: 0n }) {
|
|
6356
6700
|
await this.validateTransactionSignature(transaction);
|
|
6357
6701
|
const hash = await transaction.hash();
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
}
|
|
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}`);
|
|
6367
6710
|
try {
|
|
6368
6711
|
const result = await this.#executeTransaction({
|
|
6369
6712
|
...transaction.decoded,
|
|
@@ -6380,7 +6723,7 @@ class Chain extends VersionControl {
|
|
|
6380
6723
|
console.log("vvvvvv");
|
|
6381
6724
|
console.log({ e });
|
|
6382
6725
|
console.log(hash);
|
|
6383
|
-
peernet.publish("invalid-transaction", hash);
|
|
6726
|
+
peernet.publish("invalid-transaction", encodeWire$1("invalid-transaction", hash));
|
|
6384
6727
|
await globalThis.transactionPoolStore.delete(e.hash);
|
|
6385
6728
|
if (block) throw e;
|
|
6386
6729
|
}
|
|
@@ -6545,7 +6888,7 @@ class Chain extends VersionControl {
|
|
|
6545
6888
|
} catch (e) {
|
|
6546
6889
|
if (hash) {
|
|
6547
6890
|
try {
|
|
6548
|
-
await globalThis.peernet.publish("invalid-transaction", hash);
|
|
6891
|
+
await globalThis.peernet.publish("invalid-transaction", encodeWire$1("invalid-transaction", hash));
|
|
6549
6892
|
} catch (publishError) {
|
|
6550
6893
|
debug("peernet publish failed: invalid-transaction", publishError?.message ?? publishError);
|
|
6551
6894
|
}
|
|
@@ -6559,7 +6902,8 @@ class Chain extends VersionControl {
|
|
|
6559
6902
|
* error is thrown on error so undefined data doesn't mean there is an error...
|
|
6560
6903
|
**/
|
|
6561
6904
|
async sendTransaction(transaction) {
|
|
6562
|
-
const
|
|
6905
|
+
const { hash: ignoredHash, ...payload } = transaction instanceof TransactionMessage ? transaction.decoded : transaction;
|
|
6906
|
+
const transactionMessage = await new TransactionMessage(payload);
|
|
6563
6907
|
const event = await super.sendTransaction(transactionMessage);
|
|
6564
6908
|
await this.#sendTransaction(transactionMessage.encoded);
|
|
6565
6909
|
try {
|
|
@@ -6599,7 +6943,7 @@ class Chain extends VersionControl {
|
|
|
6599
6943
|
* @returns
|
|
6600
6944
|
*/
|
|
6601
6945
|
internalCall(sender, contract, method, parameters) {
|
|
6602
|
-
|
|
6946
|
+
throw new Error("Direct sender impersonation is disabled; submit a signed transaction with sendTransaction()");
|
|
6603
6947
|
}
|
|
6604
6948
|
/**
|
|
6605
6949
|
*
|
|
@@ -6608,18 +6952,23 @@ class Chain extends VersionControl {
|
|
|
6608
6952
|
* @param {Array} parameters
|
|
6609
6953
|
* @returns
|
|
6610
6954
|
*/
|
|
6611
|
-
call(
|
|
6612
|
-
|
|
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));
|
|
6613
6960
|
}
|
|
6614
6961
|
staticCall(contract, method, parameters) {
|
|
6615
6962
|
return this.machine.get(contract, method, parameters);
|
|
6616
6963
|
}
|
|
6617
|
-
mint(
|
|
6618
|
-
|
|
6964
|
+
mint(...args) {
|
|
6965
|
+
throw new Error("Native minting is restricted to the monetary protocol");
|
|
6619
6966
|
}
|
|
6620
|
-
transfer(
|
|
6621
|
-
|
|
6622
|
-
|
|
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);
|
|
6623
6972
|
}
|
|
6624
6973
|
balanceOf(address) {
|
|
6625
6974
|
return this.staticCall(addresses.nativeToken, "balanceOf", [address]);
|
|
@@ -6646,7 +6995,7 @@ class Chain extends VersionControl {
|
|
|
6646
6995
|
* @example chain.lookup('myCoolContractName') // qmqsfddfdgfg...
|
|
6647
6996
|
*/
|
|
6648
6997
|
lookup(name) {
|
|
6649
|
-
return this.
|
|
6998
|
+
return this.staticCall(addresses.nameService, "lookup", [name]);
|
|
6650
6999
|
}
|
|
6651
7000
|
#monitorPeerConnections() {
|
|
6652
7001
|
setInterval(() => {
|