@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/chain.js
CHANGED
|
@@ -1,42 +1,74 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { decodeWire, encodeWire } from '@leofcoin/binary';
|
|
2
2
|
import { Codec } from '@leofcoin/codec-format-interface';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
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-
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
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
|
|
19
|
+
import '@leofcoin/crypto';
|
|
20
|
+
import '@leofcoin/workers/binary-worker';
|
|
18
21
|
import '@leofcoin/networks';
|
|
19
22
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
-
|
|
251
|
-
return { hash, data, fee, wait, message };
|
|
283
|
+
return { hash, data, fee, wait: waiter.wait, message };
|
|
252
284
|
} catch (error) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
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
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
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
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
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
|
-
|
|
1467
|
-
|
|
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
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
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
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
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
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1838
|
-
|
|
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
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
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.#
|
|
2003
|
-
|
|
2004
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3193
|
-
|
|
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
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
3495
|
-
|
|
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(
|
|
3501
|
-
|
|
2786
|
+
mint(...args) {
|
|
2787
|
+
throw new Error("Native minting is restricted to the monetary protocol");
|
|
3502
2788
|
}
|
|
3503
|
-
transfer(
|
|
3504
|
-
|
|
3505
|
-
|
|
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.
|
|
2820
|
+
return this.staticCall(addresses.nameService, "lookup", [name]);
|
|
3533
2821
|
}
|
|
3534
2822
|
#monitorPeerConnections() {
|
|
3535
2823
|
setInterval(() => {
|