@leofcoin/chain 1.10.22 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +74 -0
  2. package/exports/browser/beacon-wire.js +2 -2
  3. package/exports/browser/{browser-CCfR9KOM-UO9mzy-j.js → browser-CCfR9KOM-CNWKLusu.js} +3 -3
  4. package/exports/browser/{browser-crypto-CH0ua22L.js → browser-crypto-B_UJguhk.js} +453 -35
  5. package/exports/browser/chain.js +1234 -885
  6. package/exports/browser/{client-DbZBfpG6-CYDyUGUC.js → client-DbZBfpG6-C1cXgvfr.js} +4 -4
  7. package/exports/browser/{connection-monitor-DMGAhcC0.js → connection-monitor-BzbeYGEt.js} +46 -26
  8. package/exports/browser/{index-dTQVgkpf-C6SJ0ZYW.js → index-dTQVgkpf-CkoAiRNX.js} +3 -3
  9. package/exports/browser/{index-dxD17Fom.js → index-phv5KvaE.js} +7 -7
  10. package/exports/browser/{messages-DZYCxoZI-D6kIiLKF.js → messages-DZYCxoZI-B472zldo.js} +3 -3
  11. package/exports/browser/{node-browser-BMfelxXV.js → node-browser-BmyPBkL5.js} +36 -8
  12. package/exports/browser/node-browser.js +3 -3
  13. package/exports/browser/workers/block-worker.js +8 -9
  14. package/exports/browser/workers/machine-worker.js +141 -1195
  15. package/exports/{workers/worker-BDTWOCzS-COrAszCt.js → browser/workers/proposal.proto-8zgMfutf-B3So_tnS.js} +910 -398
  16. package/exports/chain.js +248 -960
  17. package/exports/{connection-monitor-h2L8vsS7.js → connection-monitor-BdFLRh-J.js} +33 -3
  18. package/exports/connection-monitor.js +1 -1
  19. package/exports/{constants-Dy9i9S_o.js → constants-BQ6f-_OT.js} +1 -1
  20. package/exports/hash-format-state.js +3 -3
  21. package/exports/machine.js +550 -0
  22. package/exports/node.js +33 -2
  23. package/exports/state-checkpoint-BiQ1EXzo.js +110 -0
  24. package/exports/state-checkpoint.js +3 -0
  25. package/exports/workers/block-worker.js +8 -9
  26. package/exports/workers/machine-worker.js +141 -1195
  27. package/exports/{browser/workers/worker-BDTWOCzS-COrAszCt.js → workers/proposal.proto-8zgMfutf-B3So_tnS.js} +910 -398
  28. package/package.json +7 -5
package/exports/chain.js CHANGED
@@ -1,42 +1,74 @@
1
- import { createDebugger } from '@vandeurenglenn/debug';
1
+ import { decodeWire, encodeWire } from '@leofcoin/binary';
2
2
  import { Codec } from '@leofcoin/codec-format-interface';
3
- import { jsonStringifyBigInt, jsonParseBigInt, formatBytes, parseUnits, formatUnits } from '@leofcoin/utils';
4
- import { TransactionMessage, BlockMessage, ContractMessage, LastBlockMessage, PrevoteMessage, PrecommitMessage, ProposalMessage, BWMessage, StateMessage } from '@leofcoin/messages';
5
- import addresses, { contractFactory, nativeToken } from '@leofcoin/addresses';
6
- import { MAX_TRANSACTION_BYTES, MAX_BLOCK_TRANSACTION_BYTES, MAX_BLOCK_TRANSACTIONS, createTransactionHash, calculateFee, createContractMessage, signTransaction, distributeTransactionFee, supportsTransactionFees, supportsMonetaryPolicy, validateBlockResourceLimits, calculateMonetaryPolicy, distributeAmount, aggregateValidatorFees, MONETARY_POLICY_AUTHORITY, contractFactoryMessage, nativeTokenMessage, validatorsMessage, nameServiceMessage } from '@leofcoin/lib';
3
+ import { createDebugger } from '@vandeurenglenn/debug';
4
+ import { formatBytes, parseUnits, formatUnits } from '@leofcoin/utils';
5
+ import { TransactionMessage, BlockMessage, LastBlockMessage, PrevoteMessage, PrecommitMessage, ProposalMessage, ContractMessage, BWMessage, StateMessage } from '@leofcoin/messages';
6
+ import addresses from '@leofcoin/addresses';
7
+ import { MAX_TRANSACTION_BYTES, MAX_BLOCK_TRANSACTION_BYTES, MAX_BLOCK_TRANSACTIONS, createTransactionHash, calculateFee, createContractMessage, signTransaction, supportsTransactionFees, supportsMonetaryPolicy, validateBlockResourceLimits, aggregateValidatorFees, distributeAmount, MONETARY_POLICY_AUTHORITY, calculateMonetaryPolicy, contractFactoryMessage, nativeTokenMessage, validatorsMessage, nameServiceMessage, distributeTransactionFee } from '@leofcoin/lib';
7
8
  import MultiWallet from '@leofcoin/multi-wallet';
8
9
  import { fromBase58 } from '@vandeurenglenn/typed-array-utils';
9
10
  import semver from 'semver';
10
- import { P as Protocol, C as ConnectionMonitor } from './connection-monitor-h2L8vsS7.js';
11
- import { randombytes } from '@leofcoin/crypto';
12
- import EasyWorker from '@vandeurenglenn/easy-worker';
13
- import { ContractDeploymentError, ExecutionError, isResolveError, ResolveError, isExecutionError } from '@leofcoin/errors';
14
- import { P as PROTOCOL_VERSION, R as REACHED_ONE_ZERO_ZERO } from './constants-Dy9i9S_o.js';
11
+ import { P as Protocol, C as ConnectionMonitor } from './connection-monitor-BdFLRh-J.js';
12
+ import Machine from './machine.js';
13
+ import { createChainIdentity, assertChainId, findChainPresence } from '@leofcoin/lib/chain-identity';
14
+ import { r as readCheckpoint, a as recoveryHistory, b as restoreAccountNonces, v as validateChainLink } from './state-checkpoint-BiQ1EXzo.js';
15
+ import { ResolveError } from '@leofcoin/errors';
16
+ import { P as PROTOCOL_VERSION, R as REACHED_ONE_ZERO_ZERO } from './constants-BQ6f-_OT.js';
15
17
  import { q as quorumThreshold } from './quorum-S_qdgiAY.js';
16
18
  import { assertHashFormatState } from './hash-format-state.js';
17
- import { createChainIdentity, assertChainId, findChainPresence } from '@leofcoin/lib/chain-identity';
19
+ import '@leofcoin/crypto';
20
+ import '@leofcoin/workers/binary-worker';
18
21
  import '@leofcoin/networks';
19
22
 
20
- const waitForCanonicalTransaction = (pubsub, transactionPool, hash) => new Promise((resolve, reject) => {
21
- const completionTopic = `transaction.completed.${hash}`;
22
- const cleanup = () => {
23
- pubsub.unsubscribe("block-processed", blockProcessed);
24
- pubsub.unsubscribe(completionTopic, transactionCompleted);
25
- };
26
- const blockProcessed = (block) => {
27
- if (!Array.isArray(block?.transactions) || !block.transactions.includes(hash)) return;
28
- cleanup();
29
- resolve(hash);
23
+ const decodeResponsePayload = async (result) => {
24
+ if (!(result instanceof Uint8Array)) throw new TypeError("Peer response must be Uint8Array");
25
+ const codec = new Codec(result);
26
+ if (codec.name !== "peernet-response") return result;
27
+ const ResponseMessage = globalThis.peernet?.protos?.["peernet-response"];
28
+ if (!ResponseMessage) throw new Error("peernet-response codec is not registered");
29
+ return new ResponseMessage(result).decoded.response;
30
+ };
31
+
32
+ const createCanonicalTransactionWaiter = (pubsub, transactionPool, hash) => {
33
+ let cleanup = () => {
30
34
  };
31
- const transactionCompleted = async (result) => {
32
- if (result?.status === "fulfilled") return;
33
- await transactionPool.delete(hash);
34
- cleanup();
35
- reject({ hash, error: result?.error });
35
+ let cancel = (_reason) => {
36
36
  };
37
- pubsub.subscribe("block-processed", blockProcessed);
38
- pubsub.subscribe(completionTopic, transactionCompleted);
39
- });
37
+ const wait = new Promise((resolve, reject) => {
38
+ const completionTopic = `transaction.completed.${hash}`;
39
+ let settled = false;
40
+ cancel = (reason) => {
41
+ if (settled) return;
42
+ settled = true;
43
+ reject({ hash, error: reason });
44
+ };
45
+ cleanup = () => {
46
+ pubsub.unsubscribe("block-processed", blockProcessed);
47
+ pubsub.unsubscribe(completionTopic, transactionCompleted);
48
+ };
49
+ const blockProcessed = (block) => {
50
+ if (settled || !Array.isArray(block?.transactions) || !block.transactions.includes(hash)) return;
51
+ settled = true;
52
+ resolve(hash);
53
+ };
54
+ const transactionCompleted = async (result) => {
55
+ if (settled || result?.status === "fulfilled") return;
56
+ settled = true;
57
+ try {
58
+ await transactionPool.delete(hash);
59
+ reject({ hash, error: result?.error });
60
+ } catch (error) {
61
+ reject({ hash, error, transactionError: result?.error });
62
+ }
63
+ };
64
+ pubsub.subscribe("block-processed", blockProcessed);
65
+ pubsub.subscribe(completionTopic, transactionCompleted);
66
+ });
67
+ const finalized = wait.finally(() => cleanup());
68
+ void finalized.catch(() => {
69
+ });
70
+ return { wait: finalized, cancel };
71
+ };
40
72
 
41
73
  class Transaction extends Protocol {
42
74
  #pendingNonces = /* @__PURE__ */ new Map();
@@ -237,9 +269,10 @@ class Transaction extends Protocol {
237
269
  await this.validateTransactionSignature(message);
238
270
  await this.validateNonce(message.decoded.from, message.decoded.nonce);
239
271
  const hash = await message.hash();
272
+ const fee = await calculateFee(message.decoded);
273
+ const waiter = createCanonicalTransactionWaiter(pubsub, transactionPoolStore, hash);
240
274
  try {
241
275
  let data;
242
- const wait = waitForCanonicalTransaction(pubsub, transactionPoolStore, hash);
243
276
  await globalThis.transactionPoolStore.put(hash, message.encoded);
244
277
  this.addPendingNonce(message.decoded.from, message.decoded.nonce);
245
278
  try {
@@ -247,12 +280,16 @@ class Transaction extends Protocol {
247
280
  } catch (publishError) {
248
281
  console.warn("peernet publish failed: add-transaction", publishError?.message ?? publishError);
249
282
  }
250
- const fee = await calculateFee(message.decoded);
251
- return { hash, data, fee, wait, message };
283
+ return { hash, data, fee, wait: waiter.wait, message };
252
284
  } catch (error) {
253
- console.log("remo");
254
- await transactionPoolStore.delete(hash);
255
- this.removePendingNonce(message.decoded.from, message.decoded.nonce);
285
+ waiter.cancel(error);
286
+ try {
287
+ await transactionPoolStore.delete(hash);
288
+ } catch (cleanupError) {
289
+ console.warn("failed to clean up rejected transaction:", cleanupError);
290
+ } finally {
291
+ this.removePendingNonce(message.decoded.from, message.decoded.nonce);
292
+ }
256
293
  throw error;
257
294
  }
258
295
  }
@@ -579,511 +616,6 @@ class Contract extends Transaction {
579
616
  }
580
617
  }
581
618
 
582
- const debug$2 = createDebugger("leofcoin/machine");
583
- class Machine {
584
- constructor(blocks) {
585
- this.states = {
586
- states: {},
587
- lastBlock: {
588
- index: -1,
589
- hash: ""
590
- },
591
- accounts: {},
592
- info: {
593
- nativeCalls: BigInt(0),
594
- nativeMints: BigInt(0),
595
- nativeBurns: BigInt(0),
596
- nativeTransfers: BigInt(0),
597
- totalBurnAmount: BigInt(0),
598
- totalMintAmount: BigInt(0),
599
- totalTransferAmount: BigInt(0),
600
- totalTransactions: BigInt(0),
601
- totalBlocks: BigInt(0)
602
- }
603
- };
604
- this.wantList = [];
605
- this.ready = new Promise((resolve) => {
606
- this.readyResolve = resolve;
607
- });
608
- this.init(blocks);
609
- }
610
- init(blocks) {
611
- return this.#init(blocks);
612
- }
613
- async #onmessage(data) {
614
- switch (data.type) {
615
- case "transactionLoaded": {
616
- const { from, nonce, hash } = data.result;
617
- await transactionPoolStore.has(hash) && await transactionPoolStore.delete(hash);
618
- try {
619
- const _nonce = await accountsStore.get(from);
620
- if (nonce > _nonce) await accountsStore.put(from, nonce);
621
- } catch (error) {
622
- await accountsStore.put(from, nonce);
623
- }
624
- break;
625
- }
626
- case "contractError": {
627
- console.warn(data.error);
628
- console.warn(`contract error: ${data.hash}`);
629
- await contractStore.delete(data.hash);
630
- break;
631
- }
632
- case "initError": {
633
- console.error(`init error: ${data.message}`);
634
- break;
635
- }
636
- case "executeError": {
637
- pubsub.publish(data.id, { error: data.message });
638
- break;
639
- }
640
- case "addLoadedBlockError":
641
- case "collectFeeError":
642
- case "settleRewardsError": {
643
- pubsub.publish(data.id, { error: new Error(data.message) });
644
- break;
645
- }
646
- case "debug": {
647
- if (data.message.includes("loaded transactions for block:")) {
648
- pubsub.publish("block-loaded", data.message.replace("loaded transactions for block: ", "").split(" @")[0]);
649
- }
650
- break;
651
- }
652
- case "error": {
653
- console.error(data.message);
654
- break;
655
- }
656
- case "machine-ready": {
657
- this.readyResolve(this);
658
- pubsub.publish("machine.ready", true);
659
- break;
660
- }
661
- case "response": {
662
- pubsub.publish(data.id, data.value || false);
663
- break;
664
- }
665
- case "addToWantList": {
666
- if (!this.wantList.includes(data.hash)) this.wantList.push(data.hash);
667
- break;
668
- }
669
- case "ask": {
670
- if (data.question === "contract" || data.question === "transaction") {
671
- try {
672
- const input = await peernet.get(data.input, data.question);
673
- if (input === void 0) {
674
- this.worker.postMessage({ id: data.id, error: `could not get ${data.question} @${data.input}` });
675
- this.wantList.push(data.input);
676
- } else {
677
- this.worker.postMessage({ id: data.id, input });
678
- }
679
- } catch (error) {
680
- console.error(error);
681
- this.worker.postMessage({ id: data.id, error: `could not get ${data.question} @${data.input}` });
682
- this.wantList.push(data.input);
683
- }
684
- } else if (data.question === "peers") {
685
- this.worker.postMessage({
686
- id: data.id,
687
- input: peernet.connections ? Object.entries(peernet.connections).map(([id, peer]) => peer.toJSON()) : []
688
- });
689
- } else {
690
- this.worker.postMessage({ id: data.id, input: data.input });
691
- }
692
- }
693
- }
694
- }
695
- async updateState(strict = false) {
696
- try {
697
- const lastBlock = strict ? await this.getCanonicalTip() : await this.lastBlock;
698
- const lastBlockIndex = Number(lastBlock.index);
699
- const statesLastBlockIndex = Number(this.states.lastBlock.index);
700
- if (lastBlockIndex > statesLastBlockIndex || lastBlock.hash !== this.states.lastBlock.hash && Number(lastBlockIndex) === 0) {
701
- const blocks = (await this.blocks).slice(statesLastBlockIndex);
702
- let transactions = [];
703
- for (const block of blocks) {
704
- if (block?.transactions) transactions = [...transactions, ...block.transactions];
705
- }
706
- transactions = await Promise.all(
707
- transactions.map(async (transaction) => new TransactionMessage(await transactionStore.get(transaction)))
708
- );
709
- const contractsToGet = transactions.reduce((set, current) => {
710
- const contract = current.decoded.to;
711
- if (!set.includes(contract)) set.push(contract);
712
- return set;
713
- }, []);
714
- const state = {};
715
- if (!contractsToGet.includes(addresses.contractFactory)) contractsToGet.push(addresses.contractFactory);
716
- if (!contractsToGet.includes(addresses.nativeToken)) contractsToGet.push(addresses.nativeToken);
717
- if (!contractsToGet.includes(addresses.nameService)) contractsToGet.push(addresses.nameService);
718
- if (!contractsToGet.includes(addresses.validators)) contractsToGet.push(addresses.validators);
719
- await Promise.all(
720
- contractsToGet.map(async (contract) => {
721
- const value = await this.#askWorker("get", { contract, method: "state", params: [] });
722
- state[contract] = value;
723
- })
724
- );
725
- let accounts = {};
726
- try {
727
- const promises = [];
728
- for (const address of await accountsStore.keys()) {
729
- promises.push(
730
- (async () => {
731
- accounts[address] = await accountsStore.get(address);
732
- })()
733
- );
734
- }
735
- await Promise.all(promises);
736
- } catch (error) {
737
- const promises = [];
738
- for (const transaction of transactions.reverse()) {
739
- const { from, to, amount, nonce } = transaction.decoded.data;
740
- if (!accounts[from]) {
741
- accounts[from] = nonce;
742
- promises.push(accountsStore.put(from, nonce));
743
- }
744
- }
745
- await Promise.all(promises);
746
- }
747
- const sortedStringify = (obj, replacer) => {
748
- const sorted = {};
749
- const keys = Object.keys(obj).sort();
750
- for (const key of keys) {
751
- sorted[key] = obj[key];
752
- }
753
- return JSON.stringify(sorted, replacer);
754
- };
755
- const tasks = [
756
- stateStore.put("lastBlock", sortedStringify(lastBlock, jsonStringifyBigInt)),
757
- stateStore.put("states", sortedStringify(state, jsonStringifyBigInt)),
758
- stateStore.put("accounts", sortedStringify(accounts, jsonStringifyBigInt)),
759
- stateStore.put(
760
- "info",
761
- sortedStringify(
762
- {
763
- nativeBurnAmount: await this.totalBurnAmount,
764
- nativeBurns: await this.nativeBurns,
765
- nativeCalls: await this.nativeCalls,
766
- nativeMints: await this.nativeMints,
767
- nativeTransfers: await this.nativeTransfers,
768
- totalBlocks: await blockStore.length,
769
- totalBurnAmount: await this.totalBurnAmount,
770
- totalMintAmount: await this.totalMintAmount,
771
- totalTransactions: await this.totalTransactions,
772
- totalTransferAmount: await this.totalTransferAmount
773
- },
774
- jsonStringifyBigInt
775
- )
776
- )
777
- // accountsStore.clear()
778
- ];
779
- await Promise.all(tasks);
780
- }
781
- } catch (error) {
782
- console.error(error);
783
- if (strict) throw error;
784
- }
785
- }
786
- async #init(blocks) {
787
- return new Promise(async (resolve) => {
788
- const machineReady = async () => {
789
- pubsub.unsubscribe("machine.ready", machineReady);
790
- await this.updateState();
791
- resolve(this);
792
- };
793
- pubsub.subscribe("machine.ready", machineReady);
794
- let pre;
795
- try {
796
- const importee = await import('url');
797
- const url = importee.default;
798
- if (url) pre = url.fileURLToPath(new URL(".", import.meta.url));
799
- } catch {
800
- pre = "./";
801
- }
802
- this.worker = await new EasyWorker(pre + "workers/machine-worker.js", {
803
- serialization: "advanced",
804
- type: "module"
805
- });
806
- this.worker.onmessage(this.#onmessage.bind(this));
807
- let rawLastBlock;
808
- let rawStates;
809
- let rawAccounts;
810
- let rawInfo;
811
- if (await stateStore.has("lastBlock")) {
812
- const decoder = new TextDecoder();
813
- const decode = (param) => decoder.decode(param);
814
- rawLastBlock = decode(await stateStore.get("lastBlock"));
815
- this.states.lastBlock = JSON.parse(rawLastBlock, jsonParseBigInt);
816
- try {
817
- rawStates = decode(await stateStore.get("states"));
818
- rawAccounts = decode(await stateStore.get("accounts"));
819
- rawInfo = decode(await stateStore.get("info"));
820
- this.states.states = JSON.parse(rawStates, jsonParseBigInt);
821
- this.states.accounts = JSON.parse(rawAccounts, jsonParseBigInt);
822
- this.states.info = JSON.parse(rawInfo, jsonParseBigInt);
823
- } catch (error) {
824
- console.error(error);
825
- this.states.accounts = {};
826
- this.states.info = {
827
- nativeCalls: BigInt(0),
828
- nativeMints: BigInt(0),
829
- nativeBurns: BigInt(0),
830
- nativeTransfers: BigInt(0),
831
- totalBurnAmount: BigInt(0),
832
- totalMintAmount: BigInt(0),
833
- totalTransferAmount: BigInt(0),
834
- totalTransactions: BigInt(0),
835
- totalBlocks: BigInt(0)
836
- };
837
- rawInfo = JSON.stringify(this.states.info, jsonStringifyBigInt);
838
- await stateStore.put("info", new TextEncoder().encode(rawInfo));
839
- }
840
- }
841
- const message = {
842
- type: "init",
843
- input: {
844
- blocks,
845
- fromState: this.states.lastBlock?.index >= 0,
846
- lastBlock: rawLastBlock,
847
- state: rawStates,
848
- info: rawInfo,
849
- // @ts-ignore
850
- peerid: peernet.peerId
851
- }
852
- };
853
- this.worker.postMessage(message);
854
- });
855
- }
856
- async #runContract(contractMessage) {
857
- const hash = await contractMessage.hash();
858
- return new Promise((resolve, reject) => {
859
- const id = randombytes(20).toString("hex");
860
- const onmessage = (message) => {
861
- pubsub.unsubscribe(id, onmessage);
862
- if (message?.error) reject(message.error);
863
- else resolve(message);
864
- };
865
- pubsub.subscribe(id, onmessage);
866
- this.worker.postMessage({
867
- type: "run",
868
- id,
869
- input: {
870
- decoded: contractMessage.decoded,
871
- encoded: contractMessage.encoded,
872
- hash
873
- }
874
- });
875
- });
876
- }
877
- /**
878
- *
879
- * @param {Address} contract
880
- * @param {String} method
881
- * @param {Array} parameters
882
- * @returns Promise<message>
883
- */
884
- async execute(contract, method, parameters, sender, execution) {
885
- try {
886
- if (contract === contractFactory && method === "registerContract") {
887
- if (await this.has(parameters[0])) throw new Error(`duplicate contract @${parameters[0]}`);
888
- let message;
889
- if (!await globalThis.contractStore.has(parameters[0])) {
890
- message = await peernet.get(parameters[0], "contract");
891
- if (!message) throw new Error(`contract ${parameters[0]} not available`);
892
- message = await new ContractMessage(message);
893
- await globalThis.contractStore.put(await message.hash(), message.encoded);
894
- }
895
- if (!message) {
896
- message = await globalThis.contractStore.get(parameters[0]);
897
- message = await new ContractMessage(message);
898
- }
899
- if (!await this.has(await message.hash())) await this.#runContract(message);
900
- }
901
- } catch (error) {
902
- throw new ContractDeploymentError(`contract deployment failed for ${parameters[0]}
903
- ${error.message}`);
904
- }
905
- return new Promise((resolve, reject) => {
906
- const id = randombytes(20).toString("hex");
907
- const timeout = setTimeout(() => {
908
- pubsub.unsubscribe(id, onmessage);
909
- reject(new Error(`Machine.execute timeout for ${contract}.${method}`));
910
- }, 3e4);
911
- const onmessage = (message) => {
912
- clearTimeout(timeout);
913
- pubsub.unsubscribe(id, onmessage);
914
- if (message?.error) reject(new ExecutionError(message.error));
915
- else resolve(message);
916
- };
917
- pubsub.subscribe(id, onmessage);
918
- this.worker.postMessage({
919
- type: "execute",
920
- id,
921
- input: {
922
- to: contract,
923
- contract,
924
- method,
925
- params: parameters,
926
- sender,
927
- executionSeed: execution?.seed,
928
- executionTimestamp: execution?.timestamp,
929
- executionRandomness: execution?.randomness
930
- }
931
- });
932
- });
933
- }
934
- collectFee(from, payments, burned) {
935
- return this.#askWorker("collectFee", { from, payments, burned });
936
- }
937
- settleRewards(rewards) {
938
- return this.#askWorker("settleRewards", { rewards });
939
- }
940
- get(contract, method, parameters) {
941
- return new Promise((resolve, reject) => {
942
- const id = randombytes(20).toString();
943
- const timeout = setTimeout(() => {
944
- pubsub.unsubscribe(id, onmessage);
945
- reject(new Error(`Machine.get timeout for ${contract}.${method}`));
946
- }, 3e4);
947
- const onmessage = (message) => {
948
- clearTimeout(timeout);
949
- pubsub.unsubscribe(id, onmessage);
950
- resolve(message);
951
- };
952
- pubsub.subscribe(id, onmessage);
953
- this.worker.postMessage({
954
- type: "get",
955
- id,
956
- input: {
957
- contract,
958
- method,
959
- params: parameters
960
- }
961
- });
962
- });
963
- }
964
- async has(address) {
965
- return new Promise((resolve, reject) => {
966
- const id = randombytes(20).toString("hex");
967
- const timeout = setTimeout(() => {
968
- pubsub.unsubscribe(id, onmessage);
969
- reject(new Error(`Machine.has timeout for ${address}`));
970
- }, 1e4);
971
- const onmessage = (message) => {
972
- clearTimeout(timeout);
973
- pubsub.unsubscribe(id, onmessage);
974
- if (message?.error) reject(message.error);
975
- else resolve(message);
976
- };
977
- pubsub.subscribe(id, onmessage);
978
- this.worker.postMessage({
979
- type: "has",
980
- id,
981
- input: {
982
- address
983
- }
984
- });
985
- });
986
- }
987
- #askWorker(type, input) {
988
- return new Promise((resolve, reject) => {
989
- const id = randombytes(20).toString("hex");
990
- const timeout = setTimeout(() => {
991
- pubsub.unsubscribe(id, onmessage);
992
- reject(new Error(`Machine.#askWorker timeout for ${type}`));
993
- }, 3e4);
994
- const onmessage = (message) => {
995
- clearTimeout(timeout);
996
- pubsub.unsubscribe(id, onmessage);
997
- if (message?.error) reject(message.error);
998
- else resolve(message);
999
- };
1000
- pubsub.subscribe(id, onmessage);
1001
- this.worker.postMessage({
1002
- type,
1003
- id,
1004
- input
1005
- });
1006
- });
1007
- }
1008
- get contracts() {
1009
- return this.#askWorker("contracts");
1010
- }
1011
- get totalContracts() {
1012
- return this.#askWorker("totalContracts");
1013
- }
1014
- get nativeCalls() {
1015
- return this.#askWorker("nativeCalls");
1016
- }
1017
- get nativeMints() {
1018
- return this.#askWorker("nativeMints");
1019
- }
1020
- get nativeBurns() {
1021
- return this.#askWorker("nativeBurns");
1022
- }
1023
- get nativeTransfers() {
1024
- return this.#askWorker("nativeTransfers");
1025
- }
1026
- get totalBurnAmount() {
1027
- return this.#askWorker("totalBurnAmount");
1028
- }
1029
- get totalMintAmount() {
1030
- return this.#askWorker("totalMintAmount");
1031
- }
1032
- get totalTransferAmount() {
1033
- return this.#askWorker("totalTransferAmount");
1034
- }
1035
- get totalTransactions() {
1036
- return this.#askWorker("totalTransactions");
1037
- }
1038
- get totalBlocks() {
1039
- return this.#askWorker("totalBlocks");
1040
- }
1041
- get blocks() {
1042
- return this.getBlocks();
1043
- }
1044
- get lastBlock() {
1045
- return this.#askWorker("lastBlock").catch((error) => {
1046
- debug$2("lastBlock fallback after worker timeout:", error?.message ?? error);
1047
- return this.states.lastBlock;
1048
- });
1049
- }
1050
- getCanonicalTip() {
1051
- return this.#askWorker("lastBlock");
1052
- }
1053
- get lastBlockHeight() {
1054
- return this.#askWorker("lastBlockHeight").catch((error) => {
1055
- debug$2("lastBlockHeight fallback after worker timeout:", error?.message ?? error);
1056
- return Number(this.states.lastBlock?.index ?? 0);
1057
- });
1058
- }
1059
- getBlocks(from, to) {
1060
- return this.#askWorker("blocks", { from, to });
1061
- }
1062
- getBlock(index) {
1063
- return this.#askWorker("block", index);
1064
- }
1065
- async addLoadedBlock(block) {
1066
- debug$2(`adding loaded block: ${block.index}@${block.hash}`);
1067
- if (block.decoded) block = { ...block.decoded, hash: await block.hash() };
1068
- return this.#askWorker("addLoadedBlock", JSON.stringify(block, jsonStringifyBigInt));
1069
- }
1070
- async latestTransactions() {
1071
- return this.#askWorker("latestTransactions");
1072
- }
1073
- async delete(hash) {
1074
- return globalThis.contractStore.delete(hash);
1075
- }
1076
- /**
1077
- *
1078
- * @returns Promise
1079
- */
1080
- async deleteAll() {
1081
- let hashes = await globalThis.contractStore.keys();
1082
- hashes = Object.keys(hashes).map((hash) => this.delete(hash));
1083
- return Promise.all(hashes);
1084
- }
1085
- }
1086
-
1087
619
  class Jobber {
1088
620
  constructor(timeout) {
1089
621
  this.busy = false;
@@ -1113,91 +645,19 @@ class Jobber {
1113
645
  }
1114
646
  }
1115
647
 
1116
- const MAX_RESPONSE_DEPTH$1 = 3;
1117
- const asLastBlockMessage = async (blockInput, claimedHash) => {
1118
- const block = new BlockMessage(blockInput);
1119
- const hash = await block.hash();
1120
- if (claimedHash && hash !== claimedHash) {
1121
- throw new Error(`lastBlock hash mismatch: expected ${claimedHash}, got ${hash}`);
1122
- }
1123
- return new LastBlockMessage({ hash, index: block.decoded.index });
1124
- };
1125
- const resolveLegacyHash = async (hash) => {
1126
- const blockData = await globalThis.peernet?.get?.(hash, "block");
1127
- if (!blockData) throw new Error(`unable to resolve lastBlock hash: ${hash}`);
1128
- return asLastBlockMessage(blockData, hash);
1129
- };
1130
- const resolveLastBlockMessage = async (result, depth = 0) => {
1131
- if (depth > MAX_RESPONSE_DEPTH$1) throw new Error("lastBlock response nesting exceeds limit");
1132
- if (result instanceof Uint8Array) {
1133
- let codec;
1134
- try {
1135
- codec = new Codec(result);
1136
- } catch {
1137
- }
1138
- if (codec) {
1139
- if (codec.name === "peernet-response") {
1140
- const ResponseMessage = globalThis.peernet?.protos?.["peernet-response"];
1141
- if (!ResponseMessage) throw new Error("peernet-response codec is not registered");
1142
- const wrapped = new ResponseMessage(result);
1143
- if (wrapped?.decoded?.response === void 0) throw new Error("peernet-response has no response payload");
1144
- return resolveLastBlockMessage(wrapped.decoded.response, depth + 1);
1145
- }
1146
- if (codec.name === "last-block-message") return new LastBlockMessage(result);
1147
- if (codec.name === "block-message") return asLastBlockMessage(result);
1148
- }
1149
- const candidate = new TextDecoder().decode(result);
1150
- if (candidate) return resolveLegacyHash(candidate);
1151
- }
1152
- if (typeof result === "string") return resolveLegacyHash(result);
1153
- if (result && typeof result === "object") {
1154
- const response = result.decoded?.response ?? result.response;
1155
- if (response !== void 0) return resolveLastBlockMessage(response, depth + 1);
1156
- if ("hash" in result && "index" in result) return new LastBlockMessage(result);
1157
- if ("previousHash" in result && "index" in result) return asLastBlockMessage(result);
1158
- }
1159
- throw new Error(`invalid lastBlock payload: ${typeof result}`);
1160
- };
1161
-
1162
- const MAX_RESPONSE_DEPTH = 3;
1163
- const decodeResponsePayload = async (result, depth = 0) => {
1164
- if (depth > MAX_RESPONSE_DEPTH) throw new Error("peernet response nesting exceeds limit");
1165
- if (result instanceof Uint8Array) {
1166
- let codec;
1167
- try {
1168
- codec = new Codec(result);
1169
- } catch {
1170
- }
1171
- if (codec?.name === "peernet-response") {
1172
- const ResponseMessage = globalThis.peernet?.protos?.["peernet-response"];
1173
- if (!ResponseMessage) throw new Error("peernet-response codec is not registered");
1174
- const wrapped = new ResponseMessage(result);
1175
- return decodeResponsePayload(wrapped.decoded.response, depth + 1);
1176
- }
1177
- try {
1178
- return JSON.parse(new TextDecoder().decode(result));
1179
- } catch {
1180
- return result;
1181
- }
1182
- }
1183
- if (result && typeof result === "object") {
1184
- const response = result.decoded?.response ?? result.response;
1185
- if (response !== void 0) return decodeResponsePayload(response, depth + 1);
1186
- }
1187
- if (typeof result === "string") {
1188
- try {
1189
- return JSON.parse(result);
1190
- } catch {
1191
- return result;
1192
- }
1193
- }
1194
- return result;
648
+ const resolveLastBlockMessage = async (result) => {
649
+ if (!(result instanceof Uint8Array) || new Codec(result).name !== "last-block-message")
650
+ throw new TypeError("lastBlock response must be a binary last-block message");
651
+ return new LastBlockMessage(result);
1195
652
  };
1196
653
 
1197
654
  class CanonicalStateQueue {
1198
655
  #tail = Promise.resolve();
1199
656
  #failed = false;
1200
657
  #failure;
658
+ get failed() {
659
+ return this.#failed;
660
+ }
1201
661
  run(operation) {
1202
662
  const result = this.#tail.then(async () => {
1203
663
  if (this.#failed) {
@@ -1223,27 +683,6 @@ class CanonicalStateQueue {
1223
683
  }
1224
684
  }
1225
685
 
1226
- const validateChainLink = (localTip, incoming) => {
1227
- const localHash = localTip?.hash || "0x0";
1228
- const localIndex = Number(localTip?.index ?? -1);
1229
- const hasCanonicalTip = localHash !== "0x0";
1230
- if (hasCanonicalTip && incoming.index < localIndex) return "stale";
1231
- if (hasCanonicalTip && incoming.index === localIndex) {
1232
- if (incoming.hash === localHash) return "duplicate";
1233
- throw new Error(`Block conflict detected at index ${incoming.index}`);
1234
- }
1235
- const expectedIndex = hasCanonicalTip ? localIndex + 1 : 0;
1236
- if (incoming.index !== expectedIndex) {
1237
- throw new Error(`Unexpected block index ${incoming.index}; expected ${expectedIndex}`);
1238
- }
1239
- if (incoming.previousHash !== localHash) {
1240
- throw new Error(
1241
- `previousHash mismatch at index ${incoming.index}: expected ${localHash}, got ${incoming.previousHash}`
1242
- );
1243
- }
1244
- return "append";
1245
- };
1246
-
1247
686
  const resolveTransactionReference = async (expectedHash, transactionData) => {
1248
687
  const transaction = transactionData instanceof TransactionMessage ? transactionData : new TransactionMessage(transactionData);
1249
688
  const actualHash = await transaction.hash();
@@ -1253,6 +692,41 @@ const resolveTransactionReference = async (expectedHash, transactionData) => {
1253
692
  return transaction;
1254
693
  };
1255
694
 
695
+ function publishObserverEvent(bus, topic, payload, onError = (topic2, error) => console.warn(`[chain observer] ${topic2}:`, error)) {
696
+ const report = (error) => {
697
+ try {
698
+ onError(topic, error);
699
+ } catch {
700
+ }
701
+ };
702
+ const invoke = (callback) => {
703
+ try {
704
+ const result = callback();
705
+ if (result && typeof result.then === "function") Promise.resolve(result).catch(report);
706
+ } catch (error) {
707
+ report(error);
708
+ }
709
+ };
710
+ try {
711
+ const value = structuredClone(payload);
712
+ if (!(bus.subscribers instanceof Map)) {
713
+ invoke(() => bus.publish(topic, value));
714
+ return;
715
+ }
716
+ let subscriber = bus.subscribers.get(topic);
717
+ if (!subscriber) {
718
+ subscriber = { handlers: [], value: void 0 };
719
+ bus.subscribers.set(topic, subscriber);
720
+ }
721
+ const oldValue = subscriber.value;
722
+ if (!bus.verbose && oldValue === value) return;
723
+ subscriber.value = value;
724
+ for (const handler of [...subscriber.handlers]) invoke(() => handler.bound(value, oldValue));
725
+ } catch (error) {
726
+ report(error);
727
+ }
728
+ }
729
+
1256
730
  const debug$1 = createDebugger("leofcoin/state");
1257
731
  class State extends Contract {
1258
732
  constructor(config) {
@@ -1286,15 +760,6 @@ class State extends Contract {
1286
760
  response: { blocks: await globalThis.blockStore.keys() }
1287
761
  });
1288
762
  };
1289
- this.#loadBlockTransactions = (transactions) => Promise.all(
1290
- transactions.map(async (transaction) => new TransactionMessage(await peernet.get(transaction, "transaction")))
1291
- );
1292
- this.#getLastTransactions = async () => {
1293
- let lastTransactions = (await Promise.all(
1294
- (await this.blocks).filter((block) => block.loaded).slice(-24).map((block) => this.#loadBlockTransactions(block.transactions))
1295
- )).reduce((all, transactions) => [...all, ...transactions], []);
1296
- return Promise.all(lastTransactions.map((transaction) => transaction.hash()));
1297
- };
1298
763
  }
1299
764
  #resolveErrored;
1300
765
  #lastResolvedTime;
@@ -1312,7 +777,13 @@ class State extends Contract {
1312
777
  #machine;
1313
778
  #canonicalStateQueue;
1314
779
  withCanonicalState(operation) {
1315
- return this.#canonicalStateQueue.run(operation);
780
+ return this.#canonicalStateQueue.run(operation).catch((error) => {
781
+ if (this.#canonicalStateQueue.failed) {
782
+ this.#loaded = false;
783
+ this.#syncState = "errored";
784
+ }
785
+ throw error;
786
+ });
1316
787
  }
1317
788
  canonicalTip() {
1318
789
  return this.#machine ? this.#machine.getCanonicalTip() : Promise.resolve({ index: -1, hash: "0x0", previousHash: "0x0" });
@@ -1412,91 +883,79 @@ class State extends Contract {
1412
883
  #lastBlockHandler;
1413
884
  #knownBlocksHandler;
1414
885
  async init() {
1415
- debug$1("State init start");
1416
886
  this.jobber = new Jobber(this.resolveTimeout);
1417
887
  await globalThis.peernet.addRequestHandler("lastBlock", this.#lastBlockHandler);
1418
888
  await globalThis.peernet.addRequestHandler("knownBlocks", this.#knownBlocksHandler);
1419
889
  await globalThis.peernet.addRequestHandler("chainState", this.#chainStateHandler);
1420
- let localBlockHash;
1421
- let blockMessage;
1422
- let localBlock;
1423
- console.log("State init before try-catch");
1424
890
  try {
1425
- const rawBlock = await globalThis.chainStore.has("lastBlock");
1426
- console.log("State init after has lastBlock check");
1427
- if (rawBlock) {
1428
- console.log("State init after has lastBlock found");
1429
- console.log(rawBlock);
1430
- localBlockHash = new TextDecoder().decode(await globalThis.chainStore.get("lastBlock"));
1431
- console.log(localBlockHash);
1432
- if (localBlockHash !== "0x0") {
1433
- blockMessage = await globalThis.blockStore.get(localBlockHash);
1434
- console.log(blockMessage);
1435
- blockMessage = await new BlockMessage(blockMessage);
1436
- localBlock = { ...blockMessage.decoded, hash: localBlockHash };
891
+ const identity = await createChainIdentity(globalThis.peernet?.network || "leofcoin:peach", addresses);
892
+ const required = [addresses.contractFactory, addresses.nativeToken, addresses.nameService, addresses.validators];
893
+ const checkpoint = await readCheckpoint(globalThis.stateStore, identity.chainId, required);
894
+ const checkpointTip = checkpoint?.snapshot.lastBlock ?? { index: -1, hash: "0x0" };
895
+ const marker = await globalThis.chainStore.has("lastBlock") ? new TextDecoder().decode(await globalThis.chainStore.get("lastBlock")) : "0x0";
896
+ const readBlock = async (hash) => await globalThis.blockStore.has(hash) ? globalThis.blockStore.get(hash) : globalThis.peernet.get(hash, "block");
897
+ let history = await recoveryHistory(checkpointTip.hash, readBlock);
898
+ if (marker !== "0x0" && marker !== checkpointTip.hash && !history.some((block) => block.hash === marker)) {
899
+ const candidate = await recoveryHistory(marker, readBlock);
900
+ if (checkpointTip.hash !== "0x0" && candidate[Number(checkpointTip.index)]?.hash !== checkpointTip.hash)
901
+ throw new Error("Persisted chain marker conflicts with state checkpoint");
902
+ history = candidate;
903
+ }
904
+ if (!checkpoint && await globalThis.stateStore.has("lastBlock")) {
905
+ const legacy = JSON.parse(new TextDecoder().decode(await globalThis.stateStore.get("lastBlock")));
906
+ if (legacy.hash && legacy.hash !== "0x0" && legacy.hash !== history.at(-1)?.hash) {
907
+ const candidate = await recoveryHistory(legacy.hash, readBlock);
908
+ const common = Math.min(history.length, candidate.length);
909
+ if (common && history[common - 1].hash !== candidate[common - 1].hash)
910
+ throw new Error("Legacy snapshot conflicts with canonical chain marker");
911
+ if (candidate.length > history.length) history = candidate;
1437
912
  }
1438
- console.log("State init after localBlock set");
1439
- } else {
1440
- localBlock = { index: 0, hash: "0x0", previousHash: "0x0" };
1441
913
  }
1442
- } catch {
1443
- console.log("e");
1444
- console.log("State init middle");
1445
- localBlock = { index: 0, hash: "0x0", previousHash: "0x0" };
1446
- }
1447
- console.log("State init middle");
1448
- try {
1449
- console.log("fetching known blocks from blockStore");
1450
- this.knownBlocks = await globalThis.blockStore.keys();
1451
- } catch (error) {
1452
- console.log("no local known blocks found");
1453
- }
1454
- try {
1455
- if (localBlock?.hash && localBlock.hash !== "0x0") {
1456
- try {
1457
- if (!await globalThis.stateStore.has("lastBlock")) {
1458
- await this.resolveBlocks();
1459
- }
1460
- } catch {
1461
- await this.resolveBlocks();
1462
- }
1463
- } else {
1464
- await this.resolveBlocks();
914
+ if (await globalThis.chainStore.has("recoveryTarget")) {
915
+ const target = new TextDecoder().decode(await globalThis.chainStore.get("recoveryTarget"));
916
+ const candidate = await recoveryHistory(target, readBlock);
917
+ const common = Math.min(history.length, candidate.length);
918
+ if (common && history[common - 1].hash !== candidate[common - 1].hash)
919
+ throw new Error("Recovery target conflicts with canonical checkpoint history");
920
+ if (candidate.length > history.length) history = candidate;
1465
921
  }
1466
- const machine = new Machine(this.#blocks);
1467
- console.log(machine);
922
+ this.#blocks = history;
923
+ this.#blockHashMap = new Map(history.map((block) => [block.hash, block.index]));
924
+ this.knownBlocks = await globalThis.blockStore.keys();
925
+ if (!checkpoint && !history.length && this.knownBlocks.length)
926
+ throw new Error("Local blocks exist without a canonical recovery anchor; preserve this root and rebuild in a separate root");
927
+ const prefix = history.slice(0, Number(checkpointTip.index) + 1).map((block) => ({ ...block, loaded: true }));
928
+ if (history.length > prefix.length) await globalThis.chainStore.put("recoveryTarget", history.at(-1).hash);
929
+ const machine = new Machine(prefix, checkpoint);
1468
930
  await machine.ready;
1469
931
  this.#machine = machine;
1470
- const lastBlock = await this.#machine.lastBlock;
1471
- if (lastBlock.hash !== "0x0") {
1472
- await this.updateState(new BlockMessage(lastBlock));
1473
- }
932
+ await restoreAccountNonces(globalThis.accountsStore, checkpoint?.snapshot.accounts ?? {});
933
+ for (const block of prefix) this.#blocks[Number(block.index)].loaded = true;
934
+ await this.withCanonicalState((transition) => this.#loadBlocks(history.slice(prefix.length), transition));
935
+ if (!checkpoint && !history.length) await this.#machine.updateState(true);
936
+ const tip = await this.canonicalTip();
937
+ await globalThis.chainStore.put("lastBlock", tip.hash);
938
+ await globalThis.chainStore.delete("recoveryTarget");
1474
939
  this.#loaded = true;
1475
940
  this.#chainState = "loaded";
1476
941
  } catch (error) {
1477
- console.log("e");
1478
- if (isResolveError(error)) {
1479
- console.error(error);
1480
- }
1481
- console.log(error);
942
+ this.#loaded = false;
943
+ this.#chainState = "loading";
944
+ this.#syncState = "errored";
945
+ throw error;
1482
946
  }
1483
947
  }
1484
948
  async updateState(message) {
1485
- try {
1486
- const hash = await message.hash();
1487
- await globalThis.chainStore.put("lastBlock", hash);
1488
- globalThis.pubsub.publish("lastBlock", message.encoded);
1489
- if (!this.#machine) {
1490
- const machine = new Machine(this.#blocks);
1491
- console.log(machine);
1492
- await machine.ready;
1493
- this.#machine = machine;
1494
- }
1495
- await this.#machine.updateState(true);
1496
- } catch (error) {
1497
- console.error(error);
1498
- throw error;
1499
- }
949
+ if (!this.#machine) throw new Error("Cannot checkpoint an uninitialized state machine");
950
+ const hash = await message.hash();
951
+ const tip = await this.canonicalTip();
952
+ if (tip.hash !== hash || Number(tip.index) !== Number(message.decoded.index))
953
+ throw new Error("Checkpoint target does not match canonical worker tip");
954
+ await this.#machine.updateState(true);
955
+ await globalThis.chainStore.put("lastBlock", hash);
956
+ if (await globalThis.chainStore.has("recoveryTarget") && new TextDecoder().decode(await globalThis.chainStore.get("recoveryTarget")) === hash)
957
+ await globalThis.chainStore.delete("recoveryTarget");
958
+ publishObserverEvent(globalThis.pubsub, "lastBlock", message.encoded);
1500
959
  }
1501
960
  getLatestBlock() {
1502
961
  return this.#getLatestBlock();
@@ -1554,7 +1013,7 @@ class State extends Contract {
1554
1013
  this.#blocks[index] = { hash, ...block.decoded };
1555
1014
  this.#blockHashMap.set(hash, index);
1556
1015
  debug$1(`resolved block: ${hash} @${index} ${formatBytes(size)}`);
1557
- globalThis.pubsub.publish("block-resolved", { hash, index });
1016
+ publishObserverEvent(globalThis.pubsub, "block-resolved", { hash, index });
1558
1017
  this.#lastResolved = this.#blocks[index];
1559
1018
  this.#lastResolvedTime = Date.now();
1560
1019
  hash = block.decoded.previousHash;
@@ -1691,7 +1150,7 @@ class State extends Contract {
1691
1150
  const localIndex = localBlock ? Number(localBlock.index) : -1;
1692
1151
  const remoteIndex = Number(lastBlock.index);
1693
1152
  const remoteBlockHash = lastBlock.hash;
1694
- globalThis.pubsub.publish("sync-progress", {
1153
+ publishObserverEvent(globalThis.pubsub, "sync-progress", {
1695
1154
  phase: "syncing",
1696
1155
  startIndex: localIndex,
1697
1156
  localIndex,
@@ -1727,6 +1186,7 @@ class State extends Contract {
1727
1186
  if (localStateHash !== remoteBlockHash) {
1728
1187
  debug$1(`Resolving remote block: ${remoteBlockHash} @${remoteIndex} (differs from local state)`);
1729
1188
  await this.resolveBlock(remoteBlockHash);
1189
+ await globalThis.chainStore.put("recoveryTarget", remoteBlockHash);
1730
1190
  const blocksSynced = remoteIndex - localIndex;
1731
1191
  debug$1(`Resolved ${blocksSynced} new block(s)`);
1732
1192
  const blocks = this.#blocks;
@@ -1746,7 +1206,7 @@ class State extends Contract {
1746
1206
  } else {
1747
1207
  debug$1(`Block already in local state. Remote hash: ${remoteBlockHash} matches local state`);
1748
1208
  }
1749
- globalThis.pubsub.publish("sync-progress", {
1209
+ publishObserverEvent(globalThis.pubsub, "sync-progress", {
1750
1210
  phase: "synced",
1751
1211
  startIndex: localIndex,
1752
1212
  localIndex: remoteIndex,
@@ -1834,112 +1294,23 @@ class State extends Contract {
1834
1294
  }
1835
1295
  return latest;
1836
1296
  }
1837
- #loadBlockTransactions;
1838
- #getLastTransactions;
1839
- // todo throw error
1840
- async #_executeTransaction(transaction, validators, feesEnabled, burnBasisPoints, blockTimestamp) {
1841
- try {
1842
- const hash = await transaction.hash();
1843
- if (feesEnabled) {
1844
- const fee = BigInt(await calculateFee(transaction.decoded));
1845
- const { payments, burned } = distributeTransactionFee(fee, hash, validators, burnBasisPoints);
1846
- await this.#machine.collectFee(transaction.decoded.from, payments, burned);
1847
- }
1848
- await this.#machine.execute(
1849
- transaction.decoded.to,
1850
- transaction.decoded.method,
1851
- transaction.decoded.params,
1852
- transaction.decoded.from,
1853
- { seed: hash, timestamp: Number(blockTimestamp) }
1854
- );
1855
- } catch (error) {
1856
- console.log(error);
1857
- await globalThis.transactionPoolStore.delete(await transaction.hash());
1858
- console.log("removing invalid transaction");
1859
- if (isExecutionError(error)) {
1860
- console.log(error);
1861
- }
1862
- console.log(error);
1863
- throw error;
1864
- }
1297
+ async applyRecoveredBlock(block, transition) {
1298
+ throw new Error("Canonical block application is not implemented");
1865
1299
  }
1866
- /**
1867
- *
1868
- * @param {Block[]} blocks
1869
- */
1870
1300
  async #loadBlocks(blocks, transition) {
1871
1301
  this.#chainState = "loading";
1872
- if (blocks.some((block) => !block)) {
1873
- throw new Error("missing block data during load; chain resolution incomplete");
1874
- }
1875
- const poolTransactionKeys = new Set(await globalThis.transactionPoolStore.keys());
1876
- debug$1(`pool transactions: ${poolTransactionKeys.size}`);
1877
- debug$1(`loading ${blocks.length} blocks`);
1878
1302
  for (const block of blocks) {
1879
- if (block && !block.loaded) {
1880
- try {
1881
- const link = validateChainLink(await this.canonicalTip(), {
1882
- index: Number(block.index),
1883
- hash: block.hash,
1884
- previousHash: String(block.previousHash)
1885
- });
1886
- if (link !== "append") {
1887
- this.#blocks[block.index].loaded = true;
1888
- debug$1(`Ignoring ${link} loaded block ${block.hash} at height ${block.index}`);
1889
- continue;
1890
- }
1891
- debug$1(`loading block: ${Number(block.index)} ${block.hash}`);
1892
- let transactions = await this.#loadBlockTransactions(block.transactions || []);
1893
- const validators = block.validators.map(({ address }) => address);
1894
- debug$1(`loading transactions: ${transactions.length} for block ${block.index}`);
1895
- transition.markMutating();
1896
- for (const transaction of transactions) {
1897
- const hash = await transaction.hash();
1898
- if (poolTransactionKeys.has(hash)) await globalThis.transactionPoolStore.delete(hash);
1899
- }
1900
- transactions = transactions.sort((a, b) => {
1901
- if (a.decoded.priority !== b.decoded.priority) return a.decoded.priority ? -1 : 1;
1902
- const left = BigInt(a.decoded.nonce);
1903
- const right = BigInt(b.decoded.nonce);
1904
- return left < right ? -1 : left > right ? 1 : 0;
1905
- });
1906
- debug$1(`executing ${transactions.length} transactions for block ${block.index}`);
1907
- const feesEnabled = supportsTransactionFees(block.protocolVersion);
1908
- const monetaryPolicyEnabled = supportsMonetaryPolicy(block.protocolVersion);
1909
- if (monetaryPolicyEnabled) await validateBlockResourceLimits(transactions);
1910
- const policy = monetaryPolicyEnabled ? calculateMonetaryPolicy(
1911
- BigInt(await this.#machine.get(nativeToken, "totalSupply")),
1912
- BigInt(await this.#machine.get(nativeToken, "targetSupply"))
1913
- ) : { subsidy: 0n, burnBasisPoints: 1000n };
1914
- for (const transaction of transactions) {
1915
- await this.#_executeTransaction(
1916
- transaction,
1917
- validators,
1918
- feesEnabled,
1919
- policy.burnBasisPoints,
1920
- block.timestamp
1921
- );
1922
- }
1923
- if (monetaryPolicyEnabled && policy.subsidy > 0n) {
1924
- await this.#machine.settleRewards([
1925
- ...distributeAmount(policy.subsidy, validators, Number(block.index) % validators.length)
1926
- ]);
1927
- }
1928
- debug$1(`executed transactions for block ${block.index}`);
1929
- const registered = await this.#machine.addLoadedBlock({ ...block, loaded: true });
1930
- if (registered !== true) throw new Error(`Loaded block registration failed after execution: ${block.hash}`);
1931
- this.#blocks[block.index].loaded = true;
1932
- if (Number(block.index) === 0) this.#loaded = true;
1933
- debug$1(`loaded block: ${block.hash} @${Number(block.index)}`);
1934
- globalThis.pubsub.publish("block-loaded", { ...block });
1935
- } catch (error) {
1936
- console.error(error);
1937
- for (const transaction of block.transactions) {
1938
- this.wantList.push(transaction);
1939
- }
1940
- throw error;
1941
- }
1942
- }
1303
+ if (!block) throw new Error("Missing block data during recovery");
1304
+ if (block.loaded) continue;
1305
+ const { hash, loaded, ...header } = block;
1306
+ const message = new BlockMessage(header);
1307
+ if (await message.hash() !== block.hash) throw new Error("Recovery block hash changed before application");
1308
+ await this.applyRecoveredBlock(message, transition);
1309
+ const tip = await this.canonicalTip();
1310
+ if (tip.hash !== block.hash || Number(tip.index) !== Number(block.index))
1311
+ throw new Error("Recovery did not apply the expected canonical block");
1312
+ block.loaded = true;
1313
+ publishObserverEvent(globalThis.pubsub, "block-loaded", { ...block });
1943
1314
  }
1944
1315
  this.#chainState = "loaded";
1945
1316
  return true;
@@ -1999,18 +1370,16 @@ class State extends Contract {
1999
1370
  return this.syncChain(latest);
2000
1371
  }
2001
1372
  async triggerLoad() {
2002
- if (this.#blocks?.length > 0) {
2003
- const machine = new Machine(this.#blocks);
2004
- console.log(machine);
2005
- await machine.ready;
2006
- this.#machine = machine;
2007
- }
1373
+ if (!this.#machine) throw new Error("Restart the node to initialize recovery");
1374
+ const tip = await this.canonicalTip();
1375
+ return this.withCanonicalState((transition) => this.#loadBlocks(this.#blocks.slice(Number(tip.index) + 1), transition));
2008
1376
  }
2009
1377
  }
2010
1378
 
2011
1379
  class VersionControl extends State {
2012
1380
  constructor(config) {
2013
1381
  super(config);
1382
+ this.version = PROTOCOL_VERSION;
2014
1383
  }
2015
1384
  #currentVersion = PROTOCOL_VERSION;
2016
1385
  #reachedOneZeroZero = REACHED_ONE_ZERO_ZERO;
@@ -2747,10 +2116,10 @@ class Chain extends VersionControl {
2747
2116
  });
2748
2117
  });
2749
2118
  globalThis.peernet.subscribe("add-block", this.#addBlock.bind(this));
2750
- globalThis.peernet.subscribe("invalid-transaction", this.#invalidTransaction.bind(this));
2119
+ globalThis.peernet.subscribe("invalid-transaction", (bytes) => this.#invalidTransaction(decodeWire("invalid-transaction", bytes)));
2751
2120
  globalThis.peernet.subscribe("send-transaction", this.#sendTransaction.bind(this));
2752
2121
  globalThis.peernet.subscribe("add-transaction", this.#addTransaction.bind(this));
2753
- globalThis.peernet.subscribe("validator:timeout", this.#validatorTimeout.bind(this));
2122
+ globalThis.peernet.subscribe("validator:timeout", (bytes) => this.#validatorTimeout(decodeWire("validator:timeout", bytes)));
2754
2123
  globalThis.peernet.subscribe("consensus:propose", this.#handleProposal.bind(this));
2755
2124
  globalThis.peernet.subscribe("consensus:prevote", this.#handlePrevote.bind(this));
2756
2125
  globalThis.peernet.subscribe("consensus:precommit", this.#handlePrecommit.bind(this));
@@ -2796,52 +2165,7 @@ class Chain extends VersionControl {
2796
2165
  let response = await peer.request(node.encoded);
2797
2166
  console.log(`raw response for request ${request}:`, response);
2798
2167
  if (response === void 0 || response === null) return response;
2799
- const normalizeResponse = async (payload) => {
2800
- if (payload === void 0 || payload === null) return payload;
2801
- if (payload instanceof Uint8Array || typeof Buffer !== "undefined" && Buffer.isBuffer(payload)) {
2802
- let bytes = payload instanceof Uint8Array ? payload : new Uint8Array(payload);
2803
- for (let i = 0; i < 3; i += 1) {
2804
- try {
2805
- const codec = new Codec(bytes);
2806
- if (codec.name !== "peernet-response") break;
2807
- const wrapped = await new globalThis.peernet.protos["peernet-response"](bytes);
2808
- if (wrapped?.decoded?.response === void 0) break;
2809
- const next = wrapped.decoded.response;
2810
- if (next instanceof Uint8Array || typeof Buffer !== "undefined" && Buffer.isBuffer(next)) {
2811
- bytes = next instanceof Uint8Array ? next : new Uint8Array(next);
2812
- continue;
2813
- }
2814
- return normalizeResponse(next);
2815
- } catch {
2816
- break;
2817
- }
2818
- }
2819
- try {
2820
- return JSON.parse(new TextDecoder().decode(bytes));
2821
- } catch {
2822
- return bytes;
2823
- }
2824
- }
2825
- if (payload && typeof payload === "object") {
2826
- const objectPayload = payload;
2827
- const decoded = objectPayload.decoded;
2828
- if ("decoded" in objectPayload && decoded && "response" in decoded) {
2829
- return normalizeResponse(decoded.response);
2830
- }
2831
- if ("response" in objectPayload) {
2832
- return normalizeResponse(objectPayload.response);
2833
- }
2834
- }
2835
- if (typeof payload === "string") {
2836
- try {
2837
- return JSON.parse(payload);
2838
- } catch {
2839
- return payload;
2840
- }
2841
- }
2842
- return payload;
2843
- };
2844
- return await normalizeResponse(response);
2168
+ return decodeResponsePayload(response);
2845
2169
  } catch (error) {
2846
2170
  const peerId = peer?.peerId || peer?.id || peer?.address || "unknown";
2847
2171
  debug(`peernet request failed: ${request} -> ${peerId}:`, error?.message ?? error);
@@ -2849,60 +2173,11 @@ class Chain extends VersionControl {
2849
2173
  }
2850
2174
  }
2851
2175
  async #decodeKnownBlocksResponse(response) {
2852
- if (!response) return null;
2853
- if (Array.isArray(response)) {
2854
- return { blocks: response };
2855
- }
2856
- if (Array.isArray(response.blocks)) {
2857
- return { blocks: response.blocks };
2858
- }
2859
- if (response.response && Array.isArray(response.response.blocks)) {
2860
- return { blocks: response.response.blocks };
2861
- }
2862
- if (typeof response === "string") {
2863
- try {
2864
- const parsed = JSON.parse(response);
2865
- if (Array.isArray(parsed)) return { blocks: parsed };
2866
- if (parsed && Array.isArray(parsed.blocks)) return { blocks: parsed.blocks };
2867
- if (parsed?.response && Array.isArray(parsed.response.blocks)) return { blocks: parsed.response.blocks };
2868
- } catch {
2869
- return null;
2870
- }
2871
- return null;
2872
- }
2873
- if (!(response instanceof Uint8Array)) return null;
2874
- let payload = response;
2875
- for (let i = 0; i < 3; i++) {
2876
- if (!(payload instanceof Uint8Array)) break;
2877
- try {
2878
- const nestedResponse = await new globalThis.peernet.protos["peernet-response"](payload);
2879
- payload = nestedResponse?.decoded?.response;
2880
- } catch {
2881
- break;
2882
- }
2883
- }
2884
- if (Array.isArray(payload)) return { blocks: payload };
2885
- if (payload && Array.isArray(payload.blocks)) return { blocks: payload.blocks };
2886
- if (payload?.response && Array.isArray(payload.response.blocks)) return { blocks: payload.response.blocks };
2887
- try {
2888
- const decoded = new TextDecoder().decode(response);
2889
- const parsed = JSON.parse(decoded);
2890
- if (Array.isArray(parsed)) return { blocks: parsed };
2891
- if (parsed && Array.isArray(parsed.blocks)) return { blocks: parsed.blocks };
2892
- if (parsed?.response && Array.isArray(parsed.response.blocks)) return { blocks: parsed.response.blocks };
2893
- } catch {
2894
- return null;
2895
- }
2896
- return null;
2176
+ if (!Array.isArray(response) || response.length > 1e5 || response.some((hash) => typeof hash !== "string")) return null;
2177
+ return { blocks: response };
2897
2178
  }
2898
2179
  async getPeerTransactionPool(peer) {
2899
2180
  let transactionsInPool = await this.#makeRequest(peer, "transactionPool");
2900
- if (transactionsInPool instanceof Uint8Array) {
2901
- transactionsInPool = new TextDecoder().decode(transactionsInPool);
2902
- }
2903
- if (typeof transactionsInPool === "string") {
2904
- transactionsInPool = JSON.parse(transactionsInPool);
2905
- }
2906
2181
  if (!Array.isArray(transactionsInPool) || transactionsInPool.some((hash) => typeof hash !== "string")) {
2907
2182
  throw new Error("transactionPool response must be an array of transaction hashes");
2908
2183
  }
@@ -3040,7 +2315,6 @@ class Chain extends VersionControl {
3040
2315
  debug(
3041
2316
  `sync finished with peer ${peerId}: state=${this.syncState} localNow=${(await this.lastBlock)?.index ?? -1}`
3042
2317
  );
3043
- this.machine.states.info = stateInfo;
3044
2318
  } catch (error) {
3045
2319
  const peerName = peer?.peerId || peer?.id || peer?.address || peerId || "unknown";
3046
2320
  debug(`stateInfo/syncChain failed: ${peerName}:`, error?.message ?? error);
@@ -3078,7 +2352,7 @@ class Chain extends VersionControl {
3078
2352
  } catch (error) {
3079
2353
  await transactionPoolStore.delete(hash);
3080
2354
  try {
3081
- globalThis.peernet.publish("invalid-transaction", hash);
2355
+ globalThis.peernet.publish("invalid-transaction", encodeWire("invalid-transaction", hash));
3082
2356
  } catch (publishError) {
3083
2357
  debug("peernet publish failed: invalid-transaction", publishError?.message ?? publishError);
3084
2358
  }
@@ -3089,7 +2363,13 @@ class Chain extends VersionControl {
3089
2363
  async #addBlock(block) {
3090
2364
  return this.withCanonicalState((transition) => this.#applyBlock(block, transition));
3091
2365
  }
3092
- async #applyBlock(block, transition) {
2366
+ applyRecoveredBlock(block, transition) {
2367
+ return this.#applyBlock(block, transition, false);
2368
+ }
2369
+ #recordValidatorSnapshot(index) {
2370
+ return this.machine.execute(addresses.validators, "recordValidatorSnapshot", [index], globalThis.peernet.selectedAccount);
2371
+ }
2372
+ async #applyBlock(block, transition, schedule = true) {
3093
2373
  const receivedEncoded = block instanceof BlockMessage ? block.encoded : block;
3094
2374
  const blockMessage = await new BlockMessage(block);
3095
2375
  const hash = await blockMessage.hash();
@@ -3123,13 +2403,14 @@ class Chain extends VersionControl {
3123
2403
  console.log(`[chain] \u2705 Block data integrity verified: ${hash}`);
3124
2404
  await this.#validateBlockValidators(blockMessage);
3125
2405
  const transactions = await this.#resolveBlockTransactions(blockMessage);
2406
+ await globalThis.blockStore.put(hash, blockMessage.encoded);
2407
+ if (!await globalThis.chainStore.has("recoveryTarget")) await globalThis.chainStore.put("recoveryTarget", hash);
3126
2408
  transition.markMutating();
3127
2409
  await Promise.all(
3128
2410
  blockMessage.decoded.transactions.map(async (transactionHash) => {
3129
2411
  if (await transactionPoolStore.has(transactionHash)) await transactionPoolStore.delete(transactionHash);
3130
2412
  })
3131
2413
  );
3132
- await globalThis.blockStore.put(hash, blockMessage.encoded);
3133
2414
  this.#blocks[blockIndex] = {
3134
2415
  hash,
3135
2416
  ...blockMessage.decoded
@@ -3163,9 +2444,6 @@ class Chain extends VersionControl {
3163
2444
  }
3164
2445
  const noncesByAddress = {};
3165
2446
  for (let transaction of transactions) {
3166
- globalThis.pubsub.publish("transaction-processed", transaction.encoded);
3167
- if (transaction.decoded.to === globalThis.peernet.selectedAccount)
3168
- globalThis.pubsub.publish("account-transaction-processed", transaction.encoded);
3169
2447
  if (!noncesByAddress[transaction.decoded.from] || noncesByAddress?.[transaction.decoded.from] < transaction.decoded.nonce) {
3170
2448
  noncesByAddress[transaction.decoded.from] = transaction.decoded.nonce;
3171
2449
  }
@@ -3183,14 +2461,19 @@ class Chain extends VersionControl {
3183
2461
  const registered = await this.machine.addLoadedBlock({ ...blockMessage.decoded, loaded: true, hash });
3184
2462
  if (registered !== true) throw new Error(`Block registration failed after execution: ${hash}`);
3185
2463
  try {
3186
- await this.call(addresses.validators, "recordValidatorSnapshot", [blockMessage.decoded.index]);
2464
+ await this.#recordValidatorSnapshot(blockMessage.decoded.index);
3187
2465
  } catch (snapshotError) {
3188
2466
  debug(`failed to record validator snapshot: ${snapshotError?.message ?? snapshotError}`);
3189
2467
  }
3190
2468
  await this.updateState(blockMessage);
3191
2469
  debug(`added block: ${hash}`);
3192
- globalThis.pubsub.publish("block-processed", blockMessage.decoded);
3193
- await this.#handleEpochBoundary(blockIndex);
2470
+ for (const transaction of transactions) {
2471
+ publishObserverEvent(globalThis.pubsub, "transaction-processed", transaction.encoded);
2472
+ if (transaction.decoded.to === globalThis.peernet.selectedAccount)
2473
+ publishObserverEvent(globalThis.pubsub, "account-transaction-processed", transaction.encoded);
2474
+ }
2475
+ publishObserverEvent(globalThis.pubsub, "block-processed", blockMessage.decoded);
2476
+ if (schedule) await this.#handleEpochBoundary(blockIndex);
3194
2477
  }
3195
2478
  async participate(address) {
3196
2479
  try {
@@ -3238,15 +2521,14 @@ class Chain extends VersionControl {
3238
2521
  async #handleTransaction(transaction, latestTransactions, block, feePayments = { payments: [], burned: 0n }) {
3239
2522
  await this.validateTransactionSignature(transaction);
3240
2523
  const hash = await transaction.hash();
3241
- const doubleTransactions = [];
3242
- if (latestTransactions.includes(hash) || await transactionStore.has(hash)) {
3243
- doubleTransactions.push(hash);
3244
- }
3245
- if (doubleTransactions.length > 0) {
3246
- await globalThis.transactionPoolStore.delete(hash);
3247
- await globalThis.peernet.publish("invalid-transaction", hash);
3248
- return;
3249
- }
2524
+ if (latestTransactions.includes(hash)) throw new Error(`Duplicate canonical transaction: ${hash}`);
2525
+ const nonce = BigInt(transaction.decoded.nonce);
2526
+ let committed = 0n;
2527
+ if (await globalThis.accountsStore.has(transaction.decoded.from)) {
2528
+ const raw = await globalThis.accountsStore.get(transaction.decoded.from);
2529
+ committed = BigInt(typeof raw === "string" ? raw : new TextDecoder().decode(raw));
2530
+ }
2531
+ if (nonce <= committed) throw new Error(`Transaction nonce already committed: ${hash}`);
3250
2532
  try {
3251
2533
  const result = await this.#executeTransaction({
3252
2534
  ...transaction.decoded,
@@ -3263,7 +2545,7 @@ class Chain extends VersionControl {
3263
2545
  console.log("vvvvvv");
3264
2546
  console.log({ e });
3265
2547
  console.log(hash);
3266
- peernet.publish("invalid-transaction", hash);
2548
+ peernet.publish("invalid-transaction", encodeWire("invalid-transaction", hash));
3267
2549
  await globalThis.transactionPoolStore.delete(e.hash);
3268
2550
  if (block) throw e;
3269
2551
  }
@@ -3428,7 +2710,7 @@ class Chain extends VersionControl {
3428
2710
  } catch (e) {
3429
2711
  if (hash) {
3430
2712
  try {
3431
- await globalThis.peernet.publish("invalid-transaction", hash);
2713
+ await globalThis.peernet.publish("invalid-transaction", encodeWire("invalid-transaction", hash));
3432
2714
  } catch (publishError) {
3433
2715
  debug("peernet publish failed: invalid-transaction", publishError?.message ?? publishError);
3434
2716
  }
@@ -3442,7 +2724,8 @@ class Chain extends VersionControl {
3442
2724
  * error is thrown on error so undefined data doesn't mean there is an error...
3443
2725
  **/
3444
2726
  async sendTransaction(transaction) {
3445
- const transactionMessage = await new TransactionMessage({ ...transaction });
2727
+ const { hash: ignoredHash, ...payload } = transaction instanceof TransactionMessage ? transaction.decoded : transaction;
2728
+ const transactionMessage = await new TransactionMessage(payload);
3446
2729
  const event = await super.sendTransaction(transactionMessage);
3447
2730
  await this.#sendTransaction(transactionMessage.encoded);
3448
2731
  try {
@@ -3482,7 +2765,7 @@ class Chain extends VersionControl {
3482
2765
  * @returns
3483
2766
  */
3484
2767
  internalCall(sender, contract, method, parameters) {
3485
- return this.machine.execute(contract, method, parameters, sender);
2768
+ throw new Error("Direct sender impersonation is disabled; submit a signed transaction with sendTransaction()");
3486
2769
  }
3487
2770
  /**
3488
2771
  *
@@ -3491,18 +2774,23 @@ class Chain extends VersionControl {
3491
2774
  * @param {Array} parameters
3492
2775
  * @returns
3493
2776
  */
3494
- call(contract, method, parameters) {
3495
- return this.machine.execute(contract, method, parameters, globalThis.peernet.selectedAccount);
2777
+ async call(transaction, ...legacy) {
2778
+ const signed = transaction instanceof TransactionMessage ? transaction.decoded : transaction;
2779
+ if (legacy.length || !signed || typeof signed !== "object" || typeof signed.signature !== "string" || !signed.signature)
2780
+ throw new Error("call() requires a signed transaction object; use staticCall() for reads or sendTransaction() for writes");
2781
+ return this.sendTransaction(structuredClone(signed));
3496
2782
  }
3497
2783
  staticCall(contract, method, parameters) {
3498
2784
  return this.machine.get(contract, method, parameters);
3499
2785
  }
3500
- mint(to, amount) {
3501
- return this.call(addresses.nativeToken, "mint", [to, amount]);
2786
+ mint(...args) {
2787
+ throw new Error("Native minting is restricted to the monetary protocol");
3502
2788
  }
3503
- transfer(from, to, amount) {
3504
- if (from !== globalThis.peernet.selectedAccount) throw new Error("transfer sender must be the selected account");
3505
- return this.call(addresses.nativeToken, "transfer", [to, amount]);
2789
+ async transfer(transaction, ...legacy) {
2790
+ const signed = transaction instanceof TransactionMessage ? transaction.decoded : transaction;
2791
+ if (legacy.length || signed?.to !== addresses.nativeToken || signed?.method !== "transfer")
2792
+ throw new Error("transfer() requires a signed native-token transfer transaction");
2793
+ return this.call(signed);
3506
2794
  }
3507
2795
  balanceOf(address) {
3508
2796
  return this.staticCall(addresses.nativeToken, "balanceOf", [address]);
@@ -3529,7 +2817,7 @@ class Chain extends VersionControl {
3529
2817
  * @example chain.lookup('myCoolContractName') // qmqsfddfdgfg...
3530
2818
  */
3531
2819
  lookup(name) {
3532
- return this.call(addresses.nameService, "lookup", [name]);
2820
+ return this.staticCall(addresses.nameService, "lookup", [name]);
3533
2821
  }
3534
2822
  #monitorPeerConnections() {
3535
2823
  setInterval(() => {