@leofcoin/chain 1.10.21 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -0
- package/exports/browser/beacon-wire.js +2 -2
- package/exports/browser/{browser-CCfR9KOM-BGMMnVsR.js → browser-CCfR9KOM-CNWKLusu.js} +3 -3
- package/exports/browser/{browser-crypto-CH0ua22L.js → browser-crypto-B_UJguhk.js} +453 -35
- package/exports/browser/chain.js +1275 -996
- package/exports/browser/client-DbZBfpG6-C1cXgvfr.js +5943 -0
- package/exports/browser/{connection-monitor-D-krNiRH.js → connection-monitor-BzbeYGEt.js} +153 -24
- package/exports/browser/{index-dTQVgkpf-DOL2rIjp.js → index-dTQVgkpf-CkoAiRNX.js} +3 -3
- package/exports/browser/{index-DCbLvMKL.js → index-phv5KvaE.js} +238 -234
- package/exports/browser/{messages-DCePyqKC-BSvJxDWZ.js → messages-DZYCxoZI-B472zldo.js} +3 -3
- package/exports/browser/{node-browser-Ha4Lvntj.js → node-browser-BmyPBkL5.js} +55 -11
- package/exports/browser/node-browser.js +3 -3
- package/exports/browser/workers/block-worker.js +8 -9
- package/exports/browser/workers/machine-worker.js +141 -1195
- package/exports/{workers/worker-BDTWOCzS-COrAszCt.js → browser/workers/proposal.proto-8zgMfutf-B3So_tnS.js} +910 -398
- package/exports/chain.js +296 -1078
- package/exports/connection-monitor-BdFLRh-J.js +465 -0
- package/exports/connection-monitor.js +2 -324
- package/exports/{constants-Dy9i9S_o.js → constants-BQ6f-_OT.js} +1 -1
- package/exports/hash-format-state.js +3 -3
- package/exports/machine.js +550 -0
- package/exports/node.js +35 -2
- package/exports/state-checkpoint-BiQ1EXzo.js +110 -0
- package/exports/state-checkpoint.js +3 -0
- package/exports/workers/block-worker.js +8 -9
- package/exports/workers/machine-worker.js +141 -1195
- package/exports/{browser/workers/worker-BDTWOCzS-COrAszCt.js → workers/proposal.proto-8zgMfutf-B3So_tnS.js} +910 -398
- package/package.json +9 -7
- package/exports/browser/client-DKKzPSlb-C_1ug6ux.js +0 -2401
package/exports/chain.js
CHANGED
|
@@ -1,65 +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 {
|
|
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
|
-
get limit() {
|
|
30
|
-
return limit;
|
|
31
|
-
}
|
|
32
|
-
get transactionLimit() {
|
|
33
|
-
return transactionLimit;
|
|
34
|
-
}
|
|
35
|
-
get requestTimeout() {
|
|
36
|
-
return requestTimeout;
|
|
37
|
-
}
|
|
38
|
-
get syncTimeout() {
|
|
39
|
-
return syncTimeout;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
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
|
+
};
|
|
42
31
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
const cleanup = () => {
|
|
46
|
-
pubsub.unsubscribe("block-processed", blockProcessed);
|
|
47
|
-
pubsub.unsubscribe(completionTopic, transactionCompleted);
|
|
32
|
+
const createCanonicalTransactionWaiter = (pubsub, transactionPool, hash) => {
|
|
33
|
+
let cleanup = () => {
|
|
48
34
|
};
|
|
49
|
-
|
|
50
|
-
if (!Array.isArray(block?.transactions) || !block.transactions.includes(hash)) return;
|
|
51
|
-
cleanup();
|
|
52
|
-
resolve(hash);
|
|
35
|
+
let cancel = (_reason) => {
|
|
53
36
|
};
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
+
};
|
|
63
72
|
|
|
64
73
|
class Transaction extends Protocol {
|
|
65
74
|
#pendingNonces = /* @__PURE__ */ new Map();
|
|
@@ -260,9 +269,10 @@ class Transaction extends Protocol {
|
|
|
260
269
|
await this.validateTransactionSignature(message);
|
|
261
270
|
await this.validateNonce(message.decoded.from, message.decoded.nonce);
|
|
262
271
|
const hash = await message.hash();
|
|
272
|
+
const fee = await calculateFee(message.decoded);
|
|
273
|
+
const waiter = createCanonicalTransactionWaiter(pubsub, transactionPoolStore, hash);
|
|
263
274
|
try {
|
|
264
275
|
let data;
|
|
265
|
-
const wait = waitForCanonicalTransaction(pubsub, transactionPoolStore, hash);
|
|
266
276
|
await globalThis.transactionPoolStore.put(hash, message.encoded);
|
|
267
277
|
this.addPendingNonce(message.decoded.from, message.decoded.nonce);
|
|
268
278
|
try {
|
|
@@ -270,12 +280,16 @@ class Transaction extends Protocol {
|
|
|
270
280
|
} catch (publishError) {
|
|
271
281
|
console.warn("peernet publish failed: add-transaction", publishError?.message ?? publishError);
|
|
272
282
|
}
|
|
273
|
-
|
|
274
|
-
return { hash, data, fee, wait, message };
|
|
283
|
+
return { hash, data, fee, wait: waiter.wait, message };
|
|
275
284
|
} catch (error) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
+
}
|
|
279
293
|
throw error;
|
|
280
294
|
}
|
|
281
295
|
}
|
|
@@ -602,511 +616,6 @@ class Contract extends Transaction {
|
|
|
602
616
|
}
|
|
603
617
|
}
|
|
604
618
|
|
|
605
|
-
const debug$2 = createDebugger("leofcoin/machine");
|
|
606
|
-
class Machine {
|
|
607
|
-
constructor(blocks) {
|
|
608
|
-
this.states = {
|
|
609
|
-
states: {},
|
|
610
|
-
lastBlock: {
|
|
611
|
-
index: -1,
|
|
612
|
-
hash: ""
|
|
613
|
-
},
|
|
614
|
-
accounts: {},
|
|
615
|
-
info: {
|
|
616
|
-
nativeCalls: BigInt(0),
|
|
617
|
-
nativeMints: BigInt(0),
|
|
618
|
-
nativeBurns: BigInt(0),
|
|
619
|
-
nativeTransfers: BigInt(0),
|
|
620
|
-
totalBurnAmount: BigInt(0),
|
|
621
|
-
totalMintAmount: BigInt(0),
|
|
622
|
-
totalTransferAmount: BigInt(0),
|
|
623
|
-
totalTransactions: BigInt(0),
|
|
624
|
-
totalBlocks: BigInt(0)
|
|
625
|
-
}
|
|
626
|
-
};
|
|
627
|
-
this.wantList = [];
|
|
628
|
-
this.ready = new Promise((resolve) => {
|
|
629
|
-
this.readyResolve = resolve;
|
|
630
|
-
});
|
|
631
|
-
this.init(blocks);
|
|
632
|
-
}
|
|
633
|
-
init(blocks) {
|
|
634
|
-
return this.#init(blocks);
|
|
635
|
-
}
|
|
636
|
-
async #onmessage(data) {
|
|
637
|
-
switch (data.type) {
|
|
638
|
-
case "transactionLoaded": {
|
|
639
|
-
const { from, nonce, hash } = data.result;
|
|
640
|
-
await transactionPoolStore.has(hash) && await transactionPoolStore.delete(hash);
|
|
641
|
-
try {
|
|
642
|
-
const _nonce = await accountsStore.get(from);
|
|
643
|
-
if (nonce > _nonce) await accountsStore.put(from, nonce);
|
|
644
|
-
} catch (error) {
|
|
645
|
-
await accountsStore.put(from, nonce);
|
|
646
|
-
}
|
|
647
|
-
break;
|
|
648
|
-
}
|
|
649
|
-
case "contractError": {
|
|
650
|
-
console.warn(data.error);
|
|
651
|
-
console.warn(`contract error: ${data.hash}`);
|
|
652
|
-
await contractStore.delete(data.hash);
|
|
653
|
-
break;
|
|
654
|
-
}
|
|
655
|
-
case "initError": {
|
|
656
|
-
console.error(`init error: ${data.message}`);
|
|
657
|
-
break;
|
|
658
|
-
}
|
|
659
|
-
case "executeError": {
|
|
660
|
-
pubsub.publish(data.id, { error: data.message });
|
|
661
|
-
break;
|
|
662
|
-
}
|
|
663
|
-
case "addLoadedBlockError":
|
|
664
|
-
case "collectFeeError":
|
|
665
|
-
case "settleRewardsError": {
|
|
666
|
-
pubsub.publish(data.id, { error: new Error(data.message) });
|
|
667
|
-
break;
|
|
668
|
-
}
|
|
669
|
-
case "debug": {
|
|
670
|
-
if (data.message.includes("loaded transactions for block:")) {
|
|
671
|
-
pubsub.publish("block-loaded", data.message.replace("loaded transactions for block: ", "").split(" @")[0]);
|
|
672
|
-
}
|
|
673
|
-
break;
|
|
674
|
-
}
|
|
675
|
-
case "error": {
|
|
676
|
-
console.error(data.message);
|
|
677
|
-
break;
|
|
678
|
-
}
|
|
679
|
-
case "machine-ready": {
|
|
680
|
-
this.readyResolve(this);
|
|
681
|
-
pubsub.publish("machine.ready", true);
|
|
682
|
-
break;
|
|
683
|
-
}
|
|
684
|
-
case "response": {
|
|
685
|
-
pubsub.publish(data.id, data.value || false);
|
|
686
|
-
break;
|
|
687
|
-
}
|
|
688
|
-
case "addToWantList": {
|
|
689
|
-
if (!this.wantList.includes(data.hash)) this.wantList.push(data.hash);
|
|
690
|
-
break;
|
|
691
|
-
}
|
|
692
|
-
case "ask": {
|
|
693
|
-
if (data.question === "contract" || data.question === "transaction") {
|
|
694
|
-
try {
|
|
695
|
-
const input = await peernet.get(data.input, data.question);
|
|
696
|
-
if (input === void 0) {
|
|
697
|
-
this.worker.postMessage({ id: data.id, error: `could not get ${data.question} @${data.input}` });
|
|
698
|
-
this.wantList.push(data.input);
|
|
699
|
-
} else {
|
|
700
|
-
this.worker.postMessage({ id: data.id, input });
|
|
701
|
-
}
|
|
702
|
-
} catch (error) {
|
|
703
|
-
console.error(error);
|
|
704
|
-
this.worker.postMessage({ id: data.id, error: `could not get ${data.question} @${data.input}` });
|
|
705
|
-
this.wantList.push(data.input);
|
|
706
|
-
}
|
|
707
|
-
} else if (data.question === "peers") {
|
|
708
|
-
this.worker.postMessage({
|
|
709
|
-
id: data.id,
|
|
710
|
-
input: peernet.connections ? Object.entries(peernet.connections).map(([id, peer]) => peer.toJSON()) : []
|
|
711
|
-
});
|
|
712
|
-
} else {
|
|
713
|
-
this.worker.postMessage({ id: data.id, input: data.input });
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
async updateState(strict = false) {
|
|
719
|
-
try {
|
|
720
|
-
const lastBlock = strict ? await this.getCanonicalTip() : await this.lastBlock;
|
|
721
|
-
const lastBlockIndex = Number(lastBlock.index);
|
|
722
|
-
const statesLastBlockIndex = Number(this.states.lastBlock.index);
|
|
723
|
-
if (lastBlockIndex > statesLastBlockIndex || lastBlock.hash !== this.states.lastBlock.hash && Number(lastBlockIndex) === 0) {
|
|
724
|
-
const blocks = (await this.blocks).slice(statesLastBlockIndex);
|
|
725
|
-
let transactions = [];
|
|
726
|
-
for (const block of blocks) {
|
|
727
|
-
if (block?.transactions) transactions = [...transactions, ...block.transactions];
|
|
728
|
-
}
|
|
729
|
-
transactions = await Promise.all(
|
|
730
|
-
transactions.map(async (transaction) => new TransactionMessage(await transactionStore.get(transaction)))
|
|
731
|
-
);
|
|
732
|
-
const contractsToGet = transactions.reduce((set, current) => {
|
|
733
|
-
const contract = current.decoded.to;
|
|
734
|
-
if (!set.includes(contract)) set.push(contract);
|
|
735
|
-
return set;
|
|
736
|
-
}, []);
|
|
737
|
-
const state = {};
|
|
738
|
-
if (!contractsToGet.includes(addresses.contractFactory)) contractsToGet.push(addresses.contractFactory);
|
|
739
|
-
if (!contractsToGet.includes(addresses.nativeToken)) contractsToGet.push(addresses.nativeToken);
|
|
740
|
-
if (!contractsToGet.includes(addresses.nameService)) contractsToGet.push(addresses.nameService);
|
|
741
|
-
if (!contractsToGet.includes(addresses.validators)) contractsToGet.push(addresses.validators);
|
|
742
|
-
await Promise.all(
|
|
743
|
-
contractsToGet.map(async (contract) => {
|
|
744
|
-
const value = await this.#askWorker("get", { contract, method: "state", params: [] });
|
|
745
|
-
state[contract] = value;
|
|
746
|
-
})
|
|
747
|
-
);
|
|
748
|
-
let accounts = {};
|
|
749
|
-
try {
|
|
750
|
-
const promises = [];
|
|
751
|
-
for (const address of await accountsStore.keys()) {
|
|
752
|
-
promises.push(
|
|
753
|
-
(async () => {
|
|
754
|
-
accounts[address] = await accountsStore.get(address);
|
|
755
|
-
})()
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
await Promise.all(promises);
|
|
759
|
-
} catch (error) {
|
|
760
|
-
const promises = [];
|
|
761
|
-
for (const transaction of transactions.reverse()) {
|
|
762
|
-
const { from, to, amount, nonce } = transaction.decoded.data;
|
|
763
|
-
if (!accounts[from]) {
|
|
764
|
-
accounts[from] = nonce;
|
|
765
|
-
promises.push(accountsStore.put(from, nonce));
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
await Promise.all(promises);
|
|
769
|
-
}
|
|
770
|
-
const sortedStringify = (obj, replacer) => {
|
|
771
|
-
const sorted = {};
|
|
772
|
-
const keys = Object.keys(obj).sort();
|
|
773
|
-
for (const key of keys) {
|
|
774
|
-
sorted[key] = obj[key];
|
|
775
|
-
}
|
|
776
|
-
return JSON.stringify(sorted, replacer);
|
|
777
|
-
};
|
|
778
|
-
const tasks = [
|
|
779
|
-
stateStore.put("lastBlock", sortedStringify(lastBlock, jsonStringifyBigInt)),
|
|
780
|
-
stateStore.put("states", sortedStringify(state, jsonStringifyBigInt)),
|
|
781
|
-
stateStore.put("accounts", sortedStringify(accounts, jsonStringifyBigInt)),
|
|
782
|
-
stateStore.put(
|
|
783
|
-
"info",
|
|
784
|
-
sortedStringify(
|
|
785
|
-
{
|
|
786
|
-
nativeBurnAmount: await this.totalBurnAmount,
|
|
787
|
-
nativeBurns: await this.nativeBurns,
|
|
788
|
-
nativeCalls: await this.nativeCalls,
|
|
789
|
-
nativeMints: await this.nativeMints,
|
|
790
|
-
nativeTransfers: await this.nativeTransfers,
|
|
791
|
-
totalBlocks: await blockStore.length,
|
|
792
|
-
totalBurnAmount: await this.totalBurnAmount,
|
|
793
|
-
totalMintAmount: await this.totalMintAmount,
|
|
794
|
-
totalTransactions: await this.totalTransactions,
|
|
795
|
-
totalTransferAmount: await this.totalTransferAmount
|
|
796
|
-
},
|
|
797
|
-
jsonStringifyBigInt
|
|
798
|
-
)
|
|
799
|
-
)
|
|
800
|
-
// accountsStore.clear()
|
|
801
|
-
];
|
|
802
|
-
await Promise.all(tasks);
|
|
803
|
-
}
|
|
804
|
-
} catch (error) {
|
|
805
|
-
console.error(error);
|
|
806
|
-
if (strict) throw error;
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
async #init(blocks) {
|
|
810
|
-
return new Promise(async (resolve) => {
|
|
811
|
-
const machineReady = async () => {
|
|
812
|
-
pubsub.unsubscribe("machine.ready", machineReady);
|
|
813
|
-
await this.updateState();
|
|
814
|
-
resolve(this);
|
|
815
|
-
};
|
|
816
|
-
pubsub.subscribe("machine.ready", machineReady);
|
|
817
|
-
let pre;
|
|
818
|
-
try {
|
|
819
|
-
const importee = await import('url');
|
|
820
|
-
const url = importee.default;
|
|
821
|
-
if (url) pre = url.fileURLToPath(new URL(".", import.meta.url));
|
|
822
|
-
} catch {
|
|
823
|
-
pre = "./";
|
|
824
|
-
}
|
|
825
|
-
this.worker = await new EasyWorker(pre + "workers/machine-worker.js", {
|
|
826
|
-
serialization: "advanced",
|
|
827
|
-
type: "module"
|
|
828
|
-
});
|
|
829
|
-
this.worker.onmessage(this.#onmessage.bind(this));
|
|
830
|
-
let rawLastBlock;
|
|
831
|
-
let rawStates;
|
|
832
|
-
let rawAccounts;
|
|
833
|
-
let rawInfo;
|
|
834
|
-
if (await stateStore.has("lastBlock")) {
|
|
835
|
-
const decoder = new TextDecoder();
|
|
836
|
-
const decode = (param) => decoder.decode(param);
|
|
837
|
-
rawLastBlock = decode(await stateStore.get("lastBlock"));
|
|
838
|
-
this.states.lastBlock = JSON.parse(rawLastBlock, jsonParseBigInt);
|
|
839
|
-
try {
|
|
840
|
-
rawStates = decode(await stateStore.get("states"));
|
|
841
|
-
rawAccounts = decode(await stateStore.get("accounts"));
|
|
842
|
-
rawInfo = decode(await stateStore.get("info"));
|
|
843
|
-
this.states.states = JSON.parse(rawStates, jsonParseBigInt);
|
|
844
|
-
this.states.accounts = JSON.parse(rawAccounts, jsonParseBigInt);
|
|
845
|
-
this.states.info = JSON.parse(rawInfo, jsonParseBigInt);
|
|
846
|
-
} catch (error) {
|
|
847
|
-
console.error(error);
|
|
848
|
-
this.states.accounts = {};
|
|
849
|
-
this.states.info = {
|
|
850
|
-
nativeCalls: BigInt(0),
|
|
851
|
-
nativeMints: BigInt(0),
|
|
852
|
-
nativeBurns: BigInt(0),
|
|
853
|
-
nativeTransfers: BigInt(0),
|
|
854
|
-
totalBurnAmount: BigInt(0),
|
|
855
|
-
totalMintAmount: BigInt(0),
|
|
856
|
-
totalTransferAmount: BigInt(0),
|
|
857
|
-
totalTransactions: BigInt(0),
|
|
858
|
-
totalBlocks: BigInt(0)
|
|
859
|
-
};
|
|
860
|
-
rawInfo = JSON.stringify(this.states.info, jsonStringifyBigInt);
|
|
861
|
-
await stateStore.put("info", new TextEncoder().encode(rawInfo));
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
const message = {
|
|
865
|
-
type: "init",
|
|
866
|
-
input: {
|
|
867
|
-
blocks,
|
|
868
|
-
fromState: this.states.lastBlock?.index >= 0,
|
|
869
|
-
lastBlock: rawLastBlock,
|
|
870
|
-
state: rawStates,
|
|
871
|
-
info: rawInfo,
|
|
872
|
-
// @ts-ignore
|
|
873
|
-
peerid: peernet.peerId
|
|
874
|
-
}
|
|
875
|
-
};
|
|
876
|
-
this.worker.postMessage(message);
|
|
877
|
-
});
|
|
878
|
-
}
|
|
879
|
-
async #runContract(contractMessage) {
|
|
880
|
-
const hash = await contractMessage.hash();
|
|
881
|
-
return new Promise((resolve, reject) => {
|
|
882
|
-
const id = randombytes(20).toString("hex");
|
|
883
|
-
const onmessage = (message) => {
|
|
884
|
-
pubsub.unsubscribe(id, onmessage);
|
|
885
|
-
if (message?.error) reject(message.error);
|
|
886
|
-
else resolve(message);
|
|
887
|
-
};
|
|
888
|
-
pubsub.subscribe(id, onmessage);
|
|
889
|
-
this.worker.postMessage({
|
|
890
|
-
type: "run",
|
|
891
|
-
id,
|
|
892
|
-
input: {
|
|
893
|
-
decoded: contractMessage.decoded,
|
|
894
|
-
encoded: contractMessage.encoded,
|
|
895
|
-
hash
|
|
896
|
-
}
|
|
897
|
-
});
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
/**
|
|
901
|
-
*
|
|
902
|
-
* @param {Address} contract
|
|
903
|
-
* @param {String} method
|
|
904
|
-
* @param {Array} parameters
|
|
905
|
-
* @returns Promise<message>
|
|
906
|
-
*/
|
|
907
|
-
async execute(contract, method, parameters, sender, execution) {
|
|
908
|
-
try {
|
|
909
|
-
if (contract === contractFactory && method === "registerContract") {
|
|
910
|
-
if (await this.has(parameters[0])) throw new Error(`duplicate contract @${parameters[0]}`);
|
|
911
|
-
let message;
|
|
912
|
-
if (!await globalThis.contractStore.has(parameters[0])) {
|
|
913
|
-
message = await peernet.get(parameters[0], "contract");
|
|
914
|
-
if (!message) throw new Error(`contract ${parameters[0]} not available`);
|
|
915
|
-
message = await new ContractMessage(message);
|
|
916
|
-
await globalThis.contractStore.put(await message.hash(), message.encoded);
|
|
917
|
-
}
|
|
918
|
-
if (!message) {
|
|
919
|
-
message = await globalThis.contractStore.get(parameters[0]);
|
|
920
|
-
message = await new ContractMessage(message);
|
|
921
|
-
}
|
|
922
|
-
if (!await this.has(await message.hash())) await this.#runContract(message);
|
|
923
|
-
}
|
|
924
|
-
} catch (error) {
|
|
925
|
-
throw new ContractDeploymentError(`contract deployment failed for ${parameters[0]}
|
|
926
|
-
${error.message}`);
|
|
927
|
-
}
|
|
928
|
-
return new Promise((resolve, reject) => {
|
|
929
|
-
const id = randombytes(20).toString("hex");
|
|
930
|
-
const timeout = setTimeout(() => {
|
|
931
|
-
pubsub.unsubscribe(id, onmessage);
|
|
932
|
-
reject(new Error(`Machine.execute timeout for ${contract}.${method}`));
|
|
933
|
-
}, 3e4);
|
|
934
|
-
const onmessage = (message) => {
|
|
935
|
-
clearTimeout(timeout);
|
|
936
|
-
pubsub.unsubscribe(id, onmessage);
|
|
937
|
-
if (message?.error) reject(new ExecutionError(message.error));
|
|
938
|
-
else resolve(message);
|
|
939
|
-
};
|
|
940
|
-
pubsub.subscribe(id, onmessage);
|
|
941
|
-
this.worker.postMessage({
|
|
942
|
-
type: "execute",
|
|
943
|
-
id,
|
|
944
|
-
input: {
|
|
945
|
-
to: contract,
|
|
946
|
-
contract,
|
|
947
|
-
method,
|
|
948
|
-
params: parameters,
|
|
949
|
-
sender,
|
|
950
|
-
executionSeed: execution?.seed,
|
|
951
|
-
executionTimestamp: execution?.timestamp,
|
|
952
|
-
executionRandomness: execution?.randomness
|
|
953
|
-
}
|
|
954
|
-
});
|
|
955
|
-
});
|
|
956
|
-
}
|
|
957
|
-
collectFee(from, payments, burned) {
|
|
958
|
-
return this.#askWorker("collectFee", { from, payments, burned });
|
|
959
|
-
}
|
|
960
|
-
settleRewards(rewards) {
|
|
961
|
-
return this.#askWorker("settleRewards", { rewards });
|
|
962
|
-
}
|
|
963
|
-
get(contract, method, parameters) {
|
|
964
|
-
return new Promise((resolve, reject) => {
|
|
965
|
-
const id = randombytes(20).toString();
|
|
966
|
-
const timeout = setTimeout(() => {
|
|
967
|
-
pubsub.unsubscribe(id, onmessage);
|
|
968
|
-
reject(new Error(`Machine.get timeout for ${contract}.${method}`));
|
|
969
|
-
}, 3e4);
|
|
970
|
-
const onmessage = (message) => {
|
|
971
|
-
clearTimeout(timeout);
|
|
972
|
-
pubsub.unsubscribe(id, onmessage);
|
|
973
|
-
resolve(message);
|
|
974
|
-
};
|
|
975
|
-
pubsub.subscribe(id, onmessage);
|
|
976
|
-
this.worker.postMessage({
|
|
977
|
-
type: "get",
|
|
978
|
-
id,
|
|
979
|
-
input: {
|
|
980
|
-
contract,
|
|
981
|
-
method,
|
|
982
|
-
params: parameters
|
|
983
|
-
}
|
|
984
|
-
});
|
|
985
|
-
});
|
|
986
|
-
}
|
|
987
|
-
async has(address) {
|
|
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.has timeout for ${address}`));
|
|
993
|
-
}, 1e4);
|
|
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: "has",
|
|
1003
|
-
id,
|
|
1004
|
-
input: {
|
|
1005
|
-
address
|
|
1006
|
-
}
|
|
1007
|
-
});
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
#askWorker(type, input) {
|
|
1011
|
-
return new Promise((resolve, reject) => {
|
|
1012
|
-
const id = randombytes(20).toString("hex");
|
|
1013
|
-
const timeout = setTimeout(() => {
|
|
1014
|
-
pubsub.unsubscribe(id, onmessage);
|
|
1015
|
-
reject(new Error(`Machine.#askWorker timeout for ${type}`));
|
|
1016
|
-
}, 3e4);
|
|
1017
|
-
const onmessage = (message) => {
|
|
1018
|
-
clearTimeout(timeout);
|
|
1019
|
-
pubsub.unsubscribe(id, onmessage);
|
|
1020
|
-
if (message?.error) reject(message.error);
|
|
1021
|
-
else resolve(message);
|
|
1022
|
-
};
|
|
1023
|
-
pubsub.subscribe(id, onmessage);
|
|
1024
|
-
this.worker.postMessage({
|
|
1025
|
-
type,
|
|
1026
|
-
id,
|
|
1027
|
-
input
|
|
1028
|
-
});
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
get contracts() {
|
|
1032
|
-
return this.#askWorker("contracts");
|
|
1033
|
-
}
|
|
1034
|
-
get totalContracts() {
|
|
1035
|
-
return this.#askWorker("totalContracts");
|
|
1036
|
-
}
|
|
1037
|
-
get nativeCalls() {
|
|
1038
|
-
return this.#askWorker("nativeCalls");
|
|
1039
|
-
}
|
|
1040
|
-
get nativeMints() {
|
|
1041
|
-
return this.#askWorker("nativeMints");
|
|
1042
|
-
}
|
|
1043
|
-
get nativeBurns() {
|
|
1044
|
-
return this.#askWorker("nativeBurns");
|
|
1045
|
-
}
|
|
1046
|
-
get nativeTransfers() {
|
|
1047
|
-
return this.#askWorker("nativeTransfers");
|
|
1048
|
-
}
|
|
1049
|
-
get totalBurnAmount() {
|
|
1050
|
-
return this.#askWorker("totalBurnAmount");
|
|
1051
|
-
}
|
|
1052
|
-
get totalMintAmount() {
|
|
1053
|
-
return this.#askWorker("totalMintAmount");
|
|
1054
|
-
}
|
|
1055
|
-
get totalTransferAmount() {
|
|
1056
|
-
return this.#askWorker("totalTransferAmount");
|
|
1057
|
-
}
|
|
1058
|
-
get totalTransactions() {
|
|
1059
|
-
return this.#askWorker("totalTransactions");
|
|
1060
|
-
}
|
|
1061
|
-
get totalBlocks() {
|
|
1062
|
-
return this.#askWorker("totalBlocks");
|
|
1063
|
-
}
|
|
1064
|
-
get blocks() {
|
|
1065
|
-
return this.getBlocks();
|
|
1066
|
-
}
|
|
1067
|
-
get lastBlock() {
|
|
1068
|
-
return this.#askWorker("lastBlock").catch((error) => {
|
|
1069
|
-
debug$2("lastBlock fallback after worker timeout:", error?.message ?? error);
|
|
1070
|
-
return this.states.lastBlock;
|
|
1071
|
-
});
|
|
1072
|
-
}
|
|
1073
|
-
getCanonicalTip() {
|
|
1074
|
-
return this.#askWorker("lastBlock");
|
|
1075
|
-
}
|
|
1076
|
-
get lastBlockHeight() {
|
|
1077
|
-
return this.#askWorker("lastBlockHeight").catch((error) => {
|
|
1078
|
-
debug$2("lastBlockHeight fallback after worker timeout:", error?.message ?? error);
|
|
1079
|
-
return Number(this.states.lastBlock?.index ?? 0);
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
getBlocks(from, to) {
|
|
1083
|
-
return this.#askWorker("blocks", { from, to });
|
|
1084
|
-
}
|
|
1085
|
-
getBlock(index) {
|
|
1086
|
-
return this.#askWorker("block", index);
|
|
1087
|
-
}
|
|
1088
|
-
async addLoadedBlock(block) {
|
|
1089
|
-
debug$2(`adding loaded block: ${block.index}@${block.hash}`);
|
|
1090
|
-
if (block.decoded) block = { ...block.decoded, hash: await block.hash() };
|
|
1091
|
-
return this.#askWorker("addLoadedBlock", JSON.stringify(block, jsonStringifyBigInt));
|
|
1092
|
-
}
|
|
1093
|
-
async latestTransactions() {
|
|
1094
|
-
return this.#askWorker("latestTransactions");
|
|
1095
|
-
}
|
|
1096
|
-
async delete(hash) {
|
|
1097
|
-
return globalThis.contractStore.delete(hash);
|
|
1098
|
-
}
|
|
1099
|
-
/**
|
|
1100
|
-
*
|
|
1101
|
-
* @returns Promise
|
|
1102
|
-
*/
|
|
1103
|
-
async deleteAll() {
|
|
1104
|
-
let hashes = await globalThis.contractStore.keys();
|
|
1105
|
-
hashes = Object.keys(hashes).map((hash) => this.delete(hash));
|
|
1106
|
-
return Promise.all(hashes);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
619
|
class Jobber {
|
|
1111
620
|
constructor(timeout) {
|
|
1112
621
|
this.busy = false;
|
|
@@ -1136,91 +645,19 @@ class Jobber {
|
|
|
1136
645
|
}
|
|
1137
646
|
}
|
|
1138
647
|
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
if (claimedHash && hash !== claimedHash) {
|
|
1144
|
-
throw new Error(`lastBlock hash mismatch: expected ${claimedHash}, got ${hash}`);
|
|
1145
|
-
}
|
|
1146
|
-
return new LastBlockMessage({ hash, index: block.decoded.index });
|
|
1147
|
-
};
|
|
1148
|
-
const resolveLegacyHash = async (hash) => {
|
|
1149
|
-
const blockData = await globalThis.peernet?.get?.(hash, "block");
|
|
1150
|
-
if (!blockData) throw new Error(`unable to resolve lastBlock hash: ${hash}`);
|
|
1151
|
-
return asLastBlockMessage(blockData, hash);
|
|
1152
|
-
};
|
|
1153
|
-
const resolveLastBlockMessage = async (result, depth = 0) => {
|
|
1154
|
-
if (depth > MAX_RESPONSE_DEPTH$1) throw new Error("lastBlock response nesting exceeds limit");
|
|
1155
|
-
if (result instanceof Uint8Array) {
|
|
1156
|
-
let codec;
|
|
1157
|
-
try {
|
|
1158
|
-
codec = new Codec(result);
|
|
1159
|
-
} catch {
|
|
1160
|
-
}
|
|
1161
|
-
if (codec) {
|
|
1162
|
-
if (codec.name === "peernet-response") {
|
|
1163
|
-
const ResponseMessage = globalThis.peernet?.protos?.["peernet-response"];
|
|
1164
|
-
if (!ResponseMessage) throw new Error("peernet-response codec is not registered");
|
|
1165
|
-
const wrapped = new ResponseMessage(result);
|
|
1166
|
-
if (wrapped?.decoded?.response === void 0) throw new Error("peernet-response has no response payload");
|
|
1167
|
-
return resolveLastBlockMessage(wrapped.decoded.response, depth + 1);
|
|
1168
|
-
}
|
|
1169
|
-
if (codec.name === "last-block-message") return new LastBlockMessage(result);
|
|
1170
|
-
if (codec.name === "block-message") return asLastBlockMessage(result);
|
|
1171
|
-
}
|
|
1172
|
-
const candidate = new TextDecoder().decode(result);
|
|
1173
|
-
if (candidate) return resolveLegacyHash(candidate);
|
|
1174
|
-
}
|
|
1175
|
-
if (typeof result === "string") return resolveLegacyHash(result);
|
|
1176
|
-
if (result && typeof result === "object") {
|
|
1177
|
-
const response = result.decoded?.response ?? result.response;
|
|
1178
|
-
if (response !== void 0) return resolveLastBlockMessage(response, depth + 1);
|
|
1179
|
-
if ("hash" in result && "index" in result) return new LastBlockMessage(result);
|
|
1180
|
-
if ("previousHash" in result && "index" in result) return asLastBlockMessage(result);
|
|
1181
|
-
}
|
|
1182
|
-
throw new Error(`invalid lastBlock payload: ${typeof result}`);
|
|
1183
|
-
};
|
|
1184
|
-
|
|
1185
|
-
const MAX_RESPONSE_DEPTH = 3;
|
|
1186
|
-
const decodeResponsePayload = async (result, depth = 0) => {
|
|
1187
|
-
if (depth > MAX_RESPONSE_DEPTH) throw new Error("peernet response nesting exceeds limit");
|
|
1188
|
-
if (result instanceof Uint8Array) {
|
|
1189
|
-
let codec;
|
|
1190
|
-
try {
|
|
1191
|
-
codec = new Codec(result);
|
|
1192
|
-
} catch {
|
|
1193
|
-
}
|
|
1194
|
-
if (codec?.name === "peernet-response") {
|
|
1195
|
-
const ResponseMessage = globalThis.peernet?.protos?.["peernet-response"];
|
|
1196
|
-
if (!ResponseMessage) throw new Error("peernet-response codec is not registered");
|
|
1197
|
-
const wrapped = new ResponseMessage(result);
|
|
1198
|
-
return decodeResponsePayload(wrapped.decoded.response, depth + 1);
|
|
1199
|
-
}
|
|
1200
|
-
try {
|
|
1201
|
-
return JSON.parse(new TextDecoder().decode(result));
|
|
1202
|
-
} catch {
|
|
1203
|
-
return result;
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
if (result && typeof result === "object") {
|
|
1207
|
-
const response = result.decoded?.response ?? result.response;
|
|
1208
|
-
if (response !== void 0) return decodeResponsePayload(response, depth + 1);
|
|
1209
|
-
}
|
|
1210
|
-
if (typeof result === "string") {
|
|
1211
|
-
try {
|
|
1212
|
-
return JSON.parse(result);
|
|
1213
|
-
} catch {
|
|
1214
|
-
return result;
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
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);
|
|
1218
652
|
};
|
|
1219
653
|
|
|
1220
654
|
class CanonicalStateQueue {
|
|
1221
655
|
#tail = Promise.resolve();
|
|
1222
656
|
#failed = false;
|
|
1223
657
|
#failure;
|
|
658
|
+
get failed() {
|
|
659
|
+
return this.#failed;
|
|
660
|
+
}
|
|
1224
661
|
run(operation) {
|
|
1225
662
|
const result = this.#tail.then(async () => {
|
|
1226
663
|
if (this.#failed) {
|
|
@@ -1246,27 +683,50 @@ class CanonicalStateQueue {
|
|
|
1246
683
|
}
|
|
1247
684
|
}
|
|
1248
685
|
|
|
1249
|
-
const
|
|
1250
|
-
const
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
if (hasCanonicalTip && incoming.index === localIndex) {
|
|
1255
|
-
if (incoming.hash === localHash) return "duplicate";
|
|
1256
|
-
throw new Error(`Block conflict detected at index ${incoming.index}`);
|
|
1257
|
-
}
|
|
1258
|
-
const expectedIndex = hasCanonicalTip ? localIndex + 1 : 0;
|
|
1259
|
-
if (incoming.index !== expectedIndex) {
|
|
1260
|
-
throw new Error(`Unexpected block index ${incoming.index}; expected ${expectedIndex}`);
|
|
1261
|
-
}
|
|
1262
|
-
if (incoming.previousHash !== localHash) {
|
|
1263
|
-
throw new Error(
|
|
1264
|
-
`previousHash mismatch at index ${incoming.index}: expected ${localHash}, got ${incoming.previousHash}`
|
|
1265
|
-
);
|
|
686
|
+
const resolveTransactionReference = async (expectedHash, transactionData) => {
|
|
687
|
+
const transaction = transactionData instanceof TransactionMessage ? transactionData : new TransactionMessage(transactionData);
|
|
688
|
+
const actualHash = await transaction.hash();
|
|
689
|
+
if (actualHash !== expectedHash) {
|
|
690
|
+
throw new Error(`Transaction hash mismatch: expected ${expectedHash}, got ${actualHash}`);
|
|
1266
691
|
}
|
|
1267
|
-
return
|
|
692
|
+
return transaction;
|
|
1268
693
|
};
|
|
1269
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
|
+
|
|
1270
730
|
const debug$1 = createDebugger("leofcoin/state");
|
|
1271
731
|
class State extends Contract {
|
|
1272
732
|
constructor(config) {
|
|
@@ -1283,7 +743,7 @@ class State extends Contract {
|
|
|
1283
743
|
this.#totalSize = 0;
|
|
1284
744
|
this.#canonicalStateQueue = new CanonicalStateQueue();
|
|
1285
745
|
this.#loaded = false;
|
|
1286
|
-
this.#resolvingHashes = /* @__PURE__ */ new
|
|
746
|
+
this.#resolvingHashes = /* @__PURE__ */ new Map();
|
|
1287
747
|
this._wantList = [];
|
|
1288
748
|
this.#chainStateHandler = () => {
|
|
1289
749
|
return new globalThis.peernet.protos["peernet-response"]({
|
|
@@ -1300,15 +760,6 @@ class State extends Contract {
|
|
|
1300
760
|
response: { blocks: await globalThis.blockStore.keys() }
|
|
1301
761
|
});
|
|
1302
762
|
};
|
|
1303
|
-
this.#loadBlockTransactions = (transactions) => Promise.all(
|
|
1304
|
-
transactions.map(async (transaction) => new TransactionMessage(await peernet.get(transaction, "transaction")))
|
|
1305
|
-
);
|
|
1306
|
-
this.#getLastTransactions = async () => {
|
|
1307
|
-
let lastTransactions = (await Promise.all(
|
|
1308
|
-
(await this.blocks).filter((block) => block.loaded).slice(-24).map((block) => this.#loadBlockTransactions(block.transactions))
|
|
1309
|
-
)).reduce((all, transactions) => [...all, ...transactions], []);
|
|
1310
|
-
return Promise.all(lastTransactions.map((transaction) => transaction.hash()));
|
|
1311
|
-
};
|
|
1312
763
|
}
|
|
1313
764
|
#resolveErrored;
|
|
1314
765
|
#lastResolvedTime;
|
|
@@ -1326,7 +777,13 @@ class State extends Contract {
|
|
|
1326
777
|
#machine;
|
|
1327
778
|
#canonicalStateQueue;
|
|
1328
779
|
withCanonicalState(operation) {
|
|
1329
|
-
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
|
+
});
|
|
1330
787
|
}
|
|
1331
788
|
canonicalTip() {
|
|
1332
789
|
return this.#machine ? this.#machine.getCanonicalTip() : Promise.resolve({ index: -1, hash: "0x0", previousHash: "0x0" });
|
|
@@ -1426,91 +883,79 @@ class State extends Contract {
|
|
|
1426
883
|
#lastBlockHandler;
|
|
1427
884
|
#knownBlocksHandler;
|
|
1428
885
|
async init() {
|
|
1429
|
-
debug$1("State init start");
|
|
1430
886
|
this.jobber = new Jobber(this.resolveTimeout);
|
|
1431
887
|
await globalThis.peernet.addRequestHandler("lastBlock", this.#lastBlockHandler);
|
|
1432
888
|
await globalThis.peernet.addRequestHandler("knownBlocks", this.#knownBlocksHandler);
|
|
1433
889
|
await globalThis.peernet.addRequestHandler("chainState", this.#chainStateHandler);
|
|
1434
|
-
let localBlockHash;
|
|
1435
|
-
let blockMessage;
|
|
1436
|
-
let localBlock;
|
|
1437
|
-
console.log("State init before try-catch");
|
|
1438
890
|
try {
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
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;
|
|
1451
912
|
}
|
|
1452
|
-
console.log("State init after localBlock set");
|
|
1453
|
-
} else {
|
|
1454
|
-
localBlock = { index: 0, hash: "0x0", previousHash: "0x0" };
|
|
1455
913
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
console.log("fetching known blocks from blockStore");
|
|
1464
|
-
this.knownBlocks = await globalThis.blockStore.keys();
|
|
1465
|
-
} catch (error) {
|
|
1466
|
-
console.log("no local known blocks found");
|
|
1467
|
-
}
|
|
1468
|
-
try {
|
|
1469
|
-
if (localBlock?.hash && localBlock.hash !== "0x0") {
|
|
1470
|
-
try {
|
|
1471
|
-
if (!await globalThis.stateStore.has("lastBlock")) {
|
|
1472
|
-
await this.resolveBlocks();
|
|
1473
|
-
}
|
|
1474
|
-
} catch {
|
|
1475
|
-
await this.resolveBlocks();
|
|
1476
|
-
}
|
|
1477
|
-
} else {
|
|
1478
|
-
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;
|
|
1479
921
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
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);
|
|
1482
930
|
await machine.ready;
|
|
1483
931
|
this.#machine = machine;
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
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");
|
|
1488
939
|
this.#loaded = true;
|
|
1489
940
|
this.#chainState = "loaded";
|
|
1490
941
|
} catch (error) {
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
console.log(error);
|
|
942
|
+
this.#loaded = false;
|
|
943
|
+
this.#chainState = "loading";
|
|
944
|
+
this.#syncState = "errored";
|
|
945
|
+
throw error;
|
|
1496
946
|
}
|
|
1497
947
|
}
|
|
1498
948
|
async updateState(message) {
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
await this.#machine.updateState(true);
|
|
1510
|
-
} catch (error) {
|
|
1511
|
-
console.error(error);
|
|
1512
|
-
throw error;
|
|
1513
|
-
}
|
|
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);
|
|
1514
959
|
}
|
|
1515
960
|
getLatestBlock() {
|
|
1516
961
|
return this.#getLatestBlock();
|
|
@@ -1539,7 +984,8 @@ class State extends Contract {
|
|
|
1539
984
|
if (!exists) {
|
|
1540
985
|
const data = await peernet.get(hash, "transaction");
|
|
1541
986
|
if (!data) throw new Error(`missing transaction data for ${hash}`);
|
|
1542
|
-
await
|
|
987
|
+
const transaction = await resolveTransactionReference(hash, data);
|
|
988
|
+
await transactionStore.put(hash, transaction.encoded);
|
|
1543
989
|
}
|
|
1544
990
|
const inPool = await transactionPoolStore.has(hash);
|
|
1545
991
|
if (inPool) await transactionPoolStore.delete(hash);
|
|
@@ -1547,84 +993,61 @@ class State extends Contract {
|
|
|
1547
993
|
);
|
|
1548
994
|
}
|
|
1549
995
|
async #resolveBlock(hash) {
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
996
|
+
const localHash = (await this.canonicalTip()).hash;
|
|
997
|
+
const visited = /* @__PURE__ */ new Set();
|
|
998
|
+
while (hash !== "0x0" && hash !== localHash) {
|
|
999
|
+
if (visited.has(hash)) throw new ResolveError(`cyclic block ancestry at ${hash}`);
|
|
1000
|
+
visited.add(hash);
|
|
1001
|
+
const cachedIndex = this.#blockHashMap.get(hash);
|
|
1002
|
+
const cached = this.#blocks[cachedIndex];
|
|
1003
|
+
if (cached) {
|
|
1004
|
+
hash = cached.previousHash;
|
|
1005
|
+
continue;
|
|
1557
1006
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1007
|
+
try {
|
|
1008
|
+
const block = await this.getAndPutBlock(hash);
|
|
1009
|
+
await this.#resolveTransactions(block.decoded.transactions);
|
|
1010
|
+
const index = block.decoded.index;
|
|
1011
|
+
const size = block.encoded.byteLength;
|
|
1012
|
+
this.#totalSize += size;
|
|
1013
|
+
this.#blocks[index] = { hash, ...block.decoded };
|
|
1014
|
+
this.#blockHashMap.set(hash, index);
|
|
1015
|
+
debug$1(`resolved block: ${hash} @${index} ${formatBytes(size)}`);
|
|
1016
|
+
publishObserverEvent(globalThis.pubsub, "block-resolved", { hash, index });
|
|
1017
|
+
this.#lastResolved = this.#blocks[index];
|
|
1018
|
+
this.#lastResolvedTime = Date.now();
|
|
1019
|
+
hash = block.decoded.previousHash;
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
throw new ResolveError(`block: ${hash}`, { cause: error });
|
|
1568
1022
|
}
|
|
1569
1023
|
}
|
|
1570
|
-
try {
|
|
1571
|
-
const block = await this.getAndPutBlock(hash);
|
|
1572
|
-
const promises = [];
|
|
1573
|
-
if (block.decoded.previousHash !== "0x0" && block.decoded.previousHash !== localHash) {
|
|
1574
|
-
promises.push(this.resolveBlock(block.decoded.previousHash));
|
|
1575
|
-
}
|
|
1576
|
-
promises.push(this.#resolveTransactions(block.decoded.transactions));
|
|
1577
|
-
await Promise.all(promises);
|
|
1578
|
-
index = block.decoded.index;
|
|
1579
|
-
const size = block.encoded.length > 0 ? block.encoded.length : block.encoded.byteLength;
|
|
1580
|
-
this.#totalSize += size;
|
|
1581
|
-
this.#blocks[index] = { hash, ...block.decoded };
|
|
1582
|
-
this.#blockHashMap.set(hash, index);
|
|
1583
|
-
debug$1(`resolved block: ${hash} @${index} ${formatBytes(size)}`);
|
|
1584
|
-
globalThis.pubsub.publish("block-resolved", { hash, index });
|
|
1585
|
-
this.#lastResolved = this.#blocks[index];
|
|
1586
|
-
this.#lastResolvedTime = Date.now();
|
|
1587
|
-
} catch (error) {
|
|
1588
|
-
throw new ResolveError(`block: ${hash}@${index}`, { cause: error });
|
|
1589
|
-
}
|
|
1590
|
-
return;
|
|
1591
1024
|
}
|
|
1592
1025
|
async resolveBlock(hash) {
|
|
1593
1026
|
if (!hash) throw new Error(`expected hash, got: ${hash}`);
|
|
1594
1027
|
if (hash === "0x0") return;
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
const isEntering = this.#resolvingHashes.size === 1;
|
|
1028
|
+
const existing = this.#resolvingHashes.get(hash);
|
|
1029
|
+
if (existing) return existing;
|
|
1598
1030
|
this.#resolving = true;
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
} else {
|
|
1604
|
-
await this.#resolveBlock(hash);
|
|
1605
|
-
}
|
|
1606
|
-
try {
|
|
1607
|
-
const lastBlockHash = await globalThis.stateStore.get("lastBlock");
|
|
1608
|
-
if (lastBlockHash === hash) {
|
|
1031
|
+
const resolution = Promise.resolve().then(async () => {
|
|
1032
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1033
|
+
try {
|
|
1034
|
+
await this.#resolveBlock(hash);
|
|
1609
1035
|
this.#resolveErrored = false;
|
|
1610
1036
|
return;
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
console.error(`block resolution attempt ${attempt + 1} failed for ${hash}`, error);
|
|
1039
|
+
if (attempt === 2) {
|
|
1040
|
+
if (!this.wantList.includes(hash)) this.wantList.push(hash);
|
|
1041
|
+
throw new ResolveError(`block: ${hash}`, { cause: error });
|
|
1042
|
+
}
|
|
1611
1043
|
}
|
|
1612
|
-
} catch (error) {
|
|
1613
1044
|
}
|
|
1614
|
-
}
|
|
1615
|
-
console.log({ error });
|
|
1616
|
-
this.#resolveErrorCount += 1;
|
|
1617
|
-
if (this.#resolveErrorCount < 3) {
|
|
1618
|
-
this.#resolvingHashes.delete(hash);
|
|
1619
|
-
return this.resolveBlock(hash);
|
|
1620
|
-
}
|
|
1621
|
-
this.#resolveErrorCount = 0;
|
|
1622
|
-
this.wantList.push(hash);
|
|
1623
|
-
throw new ResolveError(`block: ${hash}`, { cause: error });
|
|
1624
|
-
} finally {
|
|
1045
|
+
}).finally(() => {
|
|
1625
1046
|
this.#resolvingHashes.delete(hash);
|
|
1626
1047
|
if (this.#resolvingHashes.size === 0) this.#resolving = false;
|
|
1627
|
-
}
|
|
1048
|
+
});
|
|
1049
|
+
this.#resolvingHashes.set(hash, resolution);
|
|
1050
|
+
return resolution;
|
|
1628
1051
|
}
|
|
1629
1052
|
async resolveBlocks() {
|
|
1630
1053
|
if (this.#chainSyncing || this.#resolving) {
|
|
@@ -1727,7 +1150,7 @@ class State extends Contract {
|
|
|
1727
1150
|
const localIndex = localBlock ? Number(localBlock.index) : -1;
|
|
1728
1151
|
const remoteIndex = Number(lastBlock.index);
|
|
1729
1152
|
const remoteBlockHash = lastBlock.hash;
|
|
1730
|
-
globalThis.pubsub
|
|
1153
|
+
publishObserverEvent(globalThis.pubsub, "sync-progress", {
|
|
1731
1154
|
phase: "syncing",
|
|
1732
1155
|
startIndex: localIndex,
|
|
1733
1156
|
localIndex,
|
|
@@ -1761,26 +1184,9 @@ class State extends Contract {
|
|
|
1761
1184
|
return;
|
|
1762
1185
|
}
|
|
1763
1186
|
if (localStateHash !== remoteBlockHash) {
|
|
1764
|
-
if (this.wantList.length > 0) {
|
|
1765
|
-
debug$1(`Fetching ${this.wantList.length} blocks before resolving`);
|
|
1766
|
-
const getBatch = async (batch) => {
|
|
1767
|
-
const blocks2 = await Promise.all(
|
|
1768
|
-
batch.map(
|
|
1769
|
-
(hash) => this.getAndPutBlock(hash).catch((e) => {
|
|
1770
|
-
console.warn(`failed to fetch block ${hash}`, e);
|
|
1771
|
-
})
|
|
1772
|
-
)
|
|
1773
|
-
);
|
|
1774
|
-
const transactions = blocks2.filter((block) => Boolean(block)).flatMap((block) => block.decoded.transactions);
|
|
1775
|
-
return this.#resolveTransactions(transactions);
|
|
1776
|
-
};
|
|
1777
|
-
for (let i = 0; i < this.wantList.length; i += 50) {
|
|
1778
|
-
const batch = this.wantList.slice(i, i + 50);
|
|
1779
|
-
await getBatch(batch);
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
1187
|
debug$1(`Resolving remote block: ${remoteBlockHash} @${remoteIndex} (differs from local state)`);
|
|
1783
1188
|
await this.resolveBlock(remoteBlockHash);
|
|
1189
|
+
await globalThis.chainStore.put("recoveryTarget", remoteBlockHash);
|
|
1784
1190
|
const blocksSynced = remoteIndex - localIndex;
|
|
1785
1191
|
debug$1(`Resolved ${blocksSynced} new block(s)`);
|
|
1786
1192
|
const blocks = this.#blocks;
|
|
@@ -1800,7 +1206,7 @@ class State extends Contract {
|
|
|
1800
1206
|
} else {
|
|
1801
1207
|
debug$1(`Block already in local state. Remote hash: ${remoteBlockHash} matches local state`);
|
|
1802
1208
|
}
|
|
1803
|
-
globalThis.pubsub
|
|
1209
|
+
publishObserverEvent(globalThis.pubsub, "sync-progress", {
|
|
1804
1210
|
phase: "synced",
|
|
1805
1211
|
startIndex: localIndex,
|
|
1806
1212
|
localIndex: remoteIndex,
|
|
@@ -1888,112 +1294,23 @@ class State extends Contract {
|
|
|
1888
1294
|
}
|
|
1889
1295
|
return latest;
|
|
1890
1296
|
}
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
// todo throw error
|
|
1894
|
-
async #_executeTransaction(transaction, validators, feesEnabled, burnBasisPoints, blockTimestamp) {
|
|
1895
|
-
try {
|
|
1896
|
-
const hash = await transaction.hash();
|
|
1897
|
-
if (feesEnabled) {
|
|
1898
|
-
const fee = BigInt(await calculateFee(transaction.decoded));
|
|
1899
|
-
const { payments, burned } = distributeTransactionFee(fee, hash, validators, burnBasisPoints);
|
|
1900
|
-
await this.#machine.collectFee(transaction.decoded.from, payments, burned);
|
|
1901
|
-
}
|
|
1902
|
-
await this.#machine.execute(
|
|
1903
|
-
transaction.decoded.to,
|
|
1904
|
-
transaction.decoded.method,
|
|
1905
|
-
transaction.decoded.params,
|
|
1906
|
-
transaction.decoded.from,
|
|
1907
|
-
{ seed: hash, timestamp: Number(blockTimestamp) }
|
|
1908
|
-
);
|
|
1909
|
-
} catch (error) {
|
|
1910
|
-
console.log(error);
|
|
1911
|
-
await globalThis.transactionPoolStore.delete(await transaction.hash());
|
|
1912
|
-
console.log("removing invalid transaction");
|
|
1913
|
-
if (isExecutionError(error)) {
|
|
1914
|
-
console.log(error);
|
|
1915
|
-
}
|
|
1916
|
-
console.log(error);
|
|
1917
|
-
throw error;
|
|
1918
|
-
}
|
|
1297
|
+
async applyRecoveredBlock(block, transition) {
|
|
1298
|
+
throw new Error("Canonical block application is not implemented");
|
|
1919
1299
|
}
|
|
1920
|
-
/**
|
|
1921
|
-
*
|
|
1922
|
-
* @param {Block[]} blocks
|
|
1923
|
-
*/
|
|
1924
1300
|
async #loadBlocks(blocks, transition) {
|
|
1925
1301
|
this.#chainState = "loading";
|
|
1926
|
-
if (blocks.some((block) => !block)) {
|
|
1927
|
-
throw new Error("missing block data during load; chain resolution incomplete");
|
|
1928
|
-
}
|
|
1929
|
-
const poolTransactionKeys = new Set(await globalThis.transactionPoolStore.keys());
|
|
1930
|
-
debug$1(`pool transactions: ${poolTransactionKeys.size}`);
|
|
1931
|
-
debug$1(`loading ${blocks.length} blocks`);
|
|
1932
1302
|
for (const block of blocks) {
|
|
1933
|
-
if (block
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
}
|
|
1945
|
-
debug$1(`loading block: ${Number(block.index)} ${block.hash}`);
|
|
1946
|
-
let transactions = await this.#loadBlockTransactions(block.transactions || []);
|
|
1947
|
-
const validators = block.validators.map(({ address }) => address);
|
|
1948
|
-
debug$1(`loading transactions: ${transactions.length} for block ${block.index}`);
|
|
1949
|
-
transition.markMutating();
|
|
1950
|
-
for (const transaction of transactions) {
|
|
1951
|
-
const hash = await transaction.hash();
|
|
1952
|
-
if (poolTransactionKeys.has(hash)) await globalThis.transactionPoolStore.delete(hash);
|
|
1953
|
-
}
|
|
1954
|
-
transactions = transactions.sort((a, b) => {
|
|
1955
|
-
if (a.decoded.priority !== b.decoded.priority) return a.decoded.priority ? -1 : 1;
|
|
1956
|
-
const left = BigInt(a.decoded.nonce);
|
|
1957
|
-
const right = BigInt(b.decoded.nonce);
|
|
1958
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
1959
|
-
});
|
|
1960
|
-
debug$1(`executing ${transactions.length} transactions for block ${block.index}`);
|
|
1961
|
-
const feesEnabled = supportsTransactionFees(block.protocolVersion);
|
|
1962
|
-
const monetaryPolicyEnabled = supportsMonetaryPolicy(block.protocolVersion);
|
|
1963
|
-
if (monetaryPolicyEnabled) await validateBlockResourceLimits(transactions);
|
|
1964
|
-
const policy = monetaryPolicyEnabled ? calculateMonetaryPolicy(
|
|
1965
|
-
BigInt(await this.#machine.get(nativeToken, "totalSupply")),
|
|
1966
|
-
BigInt(await this.#machine.get(nativeToken, "targetSupply"))
|
|
1967
|
-
) : { subsidy: 0n, burnBasisPoints: 1000n };
|
|
1968
|
-
for (const transaction of transactions) {
|
|
1969
|
-
await this.#_executeTransaction(
|
|
1970
|
-
transaction,
|
|
1971
|
-
validators,
|
|
1972
|
-
feesEnabled,
|
|
1973
|
-
policy.burnBasisPoints,
|
|
1974
|
-
block.timestamp
|
|
1975
|
-
);
|
|
1976
|
-
}
|
|
1977
|
-
if (monetaryPolicyEnabled && policy.subsidy > 0n) {
|
|
1978
|
-
await this.#machine.settleRewards([
|
|
1979
|
-
...distributeAmount(policy.subsidy, validators, Number(block.index) % validators.length)
|
|
1980
|
-
]);
|
|
1981
|
-
}
|
|
1982
|
-
debug$1(`executed transactions for block ${block.index}`);
|
|
1983
|
-
const registered = await this.#machine.addLoadedBlock({ ...block, loaded: true });
|
|
1984
|
-
if (registered !== true) throw new Error(`Loaded block registration failed after execution: ${block.hash}`);
|
|
1985
|
-
this.#blocks[block.index].loaded = true;
|
|
1986
|
-
if (Number(block.index) === 0) this.#loaded = true;
|
|
1987
|
-
debug$1(`loaded block: ${block.hash} @${Number(block.index)}`);
|
|
1988
|
-
globalThis.pubsub.publish("block-loaded", { ...block });
|
|
1989
|
-
} catch (error) {
|
|
1990
|
-
console.error(error);
|
|
1991
|
-
for (const transaction of block.transactions) {
|
|
1992
|
-
this.wantList.push(transaction);
|
|
1993
|
-
}
|
|
1994
|
-
throw error;
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
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 });
|
|
1997
1314
|
}
|
|
1998
1315
|
this.#chainState = "loaded";
|
|
1999
1316
|
return true;
|
|
@@ -2053,18 +1370,16 @@ class State extends Contract {
|
|
|
2053
1370
|
return this.syncChain(latest);
|
|
2054
1371
|
}
|
|
2055
1372
|
async triggerLoad() {
|
|
2056
|
-
if (this.#
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
await machine.ready;
|
|
2060
|
-
this.#machine = machine;
|
|
2061
|
-
}
|
|
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));
|
|
2062
1376
|
}
|
|
2063
1377
|
}
|
|
2064
1378
|
|
|
2065
1379
|
class VersionControl extends State {
|
|
2066
1380
|
constructor(config) {
|
|
2067
1381
|
super(config);
|
|
1382
|
+
this.version = PROTOCOL_VERSION;
|
|
2068
1383
|
}
|
|
2069
1384
|
#currentVersion = PROTOCOL_VERSION;
|
|
2070
1385
|
#reachedOneZeroZero = REACHED_ONE_ZERO_ZERO;
|
|
@@ -2134,15 +1449,6 @@ const validateBlockEconomics = (block, calculatedFees, validatorFees = /* @__PUR
|
|
|
2134
1449
|
}
|
|
2135
1450
|
};
|
|
2136
1451
|
|
|
2137
|
-
const resolveTransactionReference = async (expectedHash, transactionData) => {
|
|
2138
|
-
const transaction = transactionData instanceof TransactionMessage ? transactionData : new TransactionMessage(transactionData);
|
|
2139
|
-
const actualHash = await transaction.hash();
|
|
2140
|
-
if (actualHash !== expectedHash) {
|
|
2141
|
-
throw new Error(`Transaction hash mismatch: expected ${expectedHash}, got ${actualHash}`);
|
|
2142
|
-
}
|
|
2143
|
-
return transaction;
|
|
2144
|
-
};
|
|
2145
|
-
|
|
2146
1452
|
const consensusSignableData = (validatorsAddress, type, message) => ({
|
|
2147
1453
|
from: String(message.from),
|
|
2148
1454
|
to: validatorsAddress,
|
|
@@ -2810,10 +2116,10 @@ class Chain extends VersionControl {
|
|
|
2810
2116
|
});
|
|
2811
2117
|
});
|
|
2812
2118
|
globalThis.peernet.subscribe("add-block", this.#addBlock.bind(this));
|
|
2813
|
-
globalThis.peernet.subscribe("invalid-transaction", this.#invalidTransaction
|
|
2119
|
+
globalThis.peernet.subscribe("invalid-transaction", (bytes) => this.#invalidTransaction(decodeWire("invalid-transaction", bytes)));
|
|
2814
2120
|
globalThis.peernet.subscribe("send-transaction", this.#sendTransaction.bind(this));
|
|
2815
2121
|
globalThis.peernet.subscribe("add-transaction", this.#addTransaction.bind(this));
|
|
2816
|
-
globalThis.peernet.subscribe("validator:timeout", this.#validatorTimeout
|
|
2122
|
+
globalThis.peernet.subscribe("validator:timeout", (bytes) => this.#validatorTimeout(decodeWire("validator:timeout", bytes)));
|
|
2817
2123
|
globalThis.peernet.subscribe("consensus:propose", this.#handleProposal.bind(this));
|
|
2818
2124
|
globalThis.peernet.subscribe("consensus:prevote", this.#handlePrevote.bind(this));
|
|
2819
2125
|
globalThis.peernet.subscribe("consensus:precommit", this.#handlePrecommit.bind(this));
|
|
@@ -2859,52 +2165,7 @@ class Chain extends VersionControl {
|
|
|
2859
2165
|
let response = await peer.request(node.encoded);
|
|
2860
2166
|
console.log(`raw response for request ${request}:`, response);
|
|
2861
2167
|
if (response === void 0 || response === null) return response;
|
|
2862
|
-
|
|
2863
|
-
if (payload === void 0 || payload === null) return payload;
|
|
2864
|
-
if (payload instanceof Uint8Array || typeof Buffer !== "undefined" && Buffer.isBuffer(payload)) {
|
|
2865
|
-
let bytes = payload instanceof Uint8Array ? payload : new Uint8Array(payload);
|
|
2866
|
-
for (let i = 0; i < 3; i += 1) {
|
|
2867
|
-
try {
|
|
2868
|
-
const codec = new Codec(bytes);
|
|
2869
|
-
if (codec.name !== "peernet-response") break;
|
|
2870
|
-
const wrapped = await new globalThis.peernet.protos["peernet-response"](bytes);
|
|
2871
|
-
if (wrapped?.decoded?.response === void 0) break;
|
|
2872
|
-
const next = wrapped.decoded.response;
|
|
2873
|
-
if (next instanceof Uint8Array || typeof Buffer !== "undefined" && Buffer.isBuffer(next)) {
|
|
2874
|
-
bytes = next instanceof Uint8Array ? next : new Uint8Array(next);
|
|
2875
|
-
continue;
|
|
2876
|
-
}
|
|
2877
|
-
return normalizeResponse(next);
|
|
2878
|
-
} catch {
|
|
2879
|
-
break;
|
|
2880
|
-
}
|
|
2881
|
-
}
|
|
2882
|
-
try {
|
|
2883
|
-
return JSON.parse(new TextDecoder().decode(bytes));
|
|
2884
|
-
} catch {
|
|
2885
|
-
return bytes;
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
if (payload && typeof payload === "object") {
|
|
2889
|
-
const objectPayload = payload;
|
|
2890
|
-
const decoded = objectPayload.decoded;
|
|
2891
|
-
if ("decoded" in objectPayload && decoded && "response" in decoded) {
|
|
2892
|
-
return normalizeResponse(decoded.response);
|
|
2893
|
-
}
|
|
2894
|
-
if ("response" in objectPayload) {
|
|
2895
|
-
return normalizeResponse(objectPayload.response);
|
|
2896
|
-
}
|
|
2897
|
-
}
|
|
2898
|
-
if (typeof payload === "string") {
|
|
2899
|
-
try {
|
|
2900
|
-
return JSON.parse(payload);
|
|
2901
|
-
} catch {
|
|
2902
|
-
return payload;
|
|
2903
|
-
}
|
|
2904
|
-
}
|
|
2905
|
-
return payload;
|
|
2906
|
-
};
|
|
2907
|
-
return await normalizeResponse(response);
|
|
2168
|
+
return decodeResponsePayload(response);
|
|
2908
2169
|
} catch (error) {
|
|
2909
2170
|
const peerId = peer?.peerId || peer?.id || peer?.address || "unknown";
|
|
2910
2171
|
debug(`peernet request failed: ${request} -> ${peerId}:`, error?.message ?? error);
|
|
@@ -2912,60 +2173,11 @@ class Chain extends VersionControl {
|
|
|
2912
2173
|
}
|
|
2913
2174
|
}
|
|
2914
2175
|
async #decodeKnownBlocksResponse(response) {
|
|
2915
|
-
if (!response) return null;
|
|
2916
|
-
|
|
2917
|
-
return { blocks: response };
|
|
2918
|
-
}
|
|
2919
|
-
if (Array.isArray(response.blocks)) {
|
|
2920
|
-
return { blocks: response.blocks };
|
|
2921
|
-
}
|
|
2922
|
-
if (response.response && Array.isArray(response.response.blocks)) {
|
|
2923
|
-
return { blocks: response.response.blocks };
|
|
2924
|
-
}
|
|
2925
|
-
if (typeof response === "string") {
|
|
2926
|
-
try {
|
|
2927
|
-
const parsed = JSON.parse(response);
|
|
2928
|
-
if (Array.isArray(parsed)) return { blocks: parsed };
|
|
2929
|
-
if (parsed && Array.isArray(parsed.blocks)) return { blocks: parsed.blocks };
|
|
2930
|
-
if (parsed?.response && Array.isArray(parsed.response.blocks)) return { blocks: parsed.response.blocks };
|
|
2931
|
-
} catch {
|
|
2932
|
-
return null;
|
|
2933
|
-
}
|
|
2934
|
-
return null;
|
|
2935
|
-
}
|
|
2936
|
-
if (!(response instanceof Uint8Array)) return null;
|
|
2937
|
-
let payload = response;
|
|
2938
|
-
for (let i = 0; i < 3; i++) {
|
|
2939
|
-
if (!(payload instanceof Uint8Array)) break;
|
|
2940
|
-
try {
|
|
2941
|
-
const nestedResponse = await new globalThis.peernet.protos["peernet-response"](payload);
|
|
2942
|
-
payload = nestedResponse?.decoded?.response;
|
|
2943
|
-
} catch {
|
|
2944
|
-
break;
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
if (Array.isArray(payload)) return { blocks: payload };
|
|
2948
|
-
if (payload && Array.isArray(payload.blocks)) return { blocks: payload.blocks };
|
|
2949
|
-
if (payload?.response && Array.isArray(payload.response.blocks)) return { blocks: payload.response.blocks };
|
|
2950
|
-
try {
|
|
2951
|
-
const decoded = new TextDecoder().decode(response);
|
|
2952
|
-
const parsed = JSON.parse(decoded);
|
|
2953
|
-
if (Array.isArray(parsed)) return { blocks: parsed };
|
|
2954
|
-
if (parsed && Array.isArray(parsed.blocks)) return { blocks: parsed.blocks };
|
|
2955
|
-
if (parsed?.response && Array.isArray(parsed.response.blocks)) return { blocks: parsed.response.blocks };
|
|
2956
|
-
} catch {
|
|
2957
|
-
return null;
|
|
2958
|
-
}
|
|
2959
|
-
return null;
|
|
2176
|
+
if (!Array.isArray(response) || response.length > 1e5 || response.some((hash) => typeof hash !== "string")) return null;
|
|
2177
|
+
return { blocks: response };
|
|
2960
2178
|
}
|
|
2961
2179
|
async getPeerTransactionPool(peer) {
|
|
2962
2180
|
let transactionsInPool = await this.#makeRequest(peer, "transactionPool");
|
|
2963
|
-
if (transactionsInPool instanceof Uint8Array) {
|
|
2964
|
-
transactionsInPool = new TextDecoder().decode(transactionsInPool);
|
|
2965
|
-
}
|
|
2966
|
-
if (typeof transactionsInPool === "string") {
|
|
2967
|
-
transactionsInPool = JSON.parse(transactionsInPool);
|
|
2968
|
-
}
|
|
2969
2181
|
if (!Array.isArray(transactionsInPool) || transactionsInPool.some((hash) => typeof hash !== "string")) {
|
|
2970
2182
|
throw new Error("transactionPool response must be an array of transaction hashes");
|
|
2971
2183
|
}
|
|
@@ -3090,13 +2302,6 @@ class Chain extends VersionControl {
|
|
|
3090
2302
|
}
|
|
3091
2303
|
}
|
|
3092
2304
|
}
|
|
3093
|
-
if (this.wantList.length > 0) {
|
|
3094
|
-
const promises = await Promise.allSettled(this.wantList.map((hash) => peernet.get(hash, "block")));
|
|
3095
|
-
for (let i = promises.length - 1; i >= 0; i -= 1) {
|
|
3096
|
-
if (promises[i].status === "fulfilled") this.wantList.splice(i, 1);
|
|
3097
|
-
}
|
|
3098
|
-
if (this.wantList.length === 0) await this.triggerSync();
|
|
3099
|
-
}
|
|
3100
2305
|
try {
|
|
3101
2306
|
let stateInfo = await this.#makeRequest(peer, "stateInfo");
|
|
3102
2307
|
if (stateInfo instanceof Uint8Array) {
|
|
@@ -3110,7 +2315,6 @@ class Chain extends VersionControl {
|
|
|
3110
2315
|
debug(
|
|
3111
2316
|
`sync finished with peer ${peerId}: state=${this.syncState} localNow=${(await this.lastBlock)?.index ?? -1}`
|
|
3112
2317
|
);
|
|
3113
|
-
this.machine.states.info = stateInfo;
|
|
3114
2318
|
} catch (error) {
|
|
3115
2319
|
const peerName = peer?.peerId || peer?.id || peer?.address || peerId || "unknown";
|
|
3116
2320
|
debug(`stateInfo/syncChain failed: ${peerName}:`, error?.message ?? error);
|
|
@@ -3148,7 +2352,7 @@ class Chain extends VersionControl {
|
|
|
3148
2352
|
} catch (error) {
|
|
3149
2353
|
await transactionPoolStore.delete(hash);
|
|
3150
2354
|
try {
|
|
3151
|
-
globalThis.peernet.publish("invalid-transaction", hash);
|
|
2355
|
+
globalThis.peernet.publish("invalid-transaction", encodeWire("invalid-transaction", hash));
|
|
3152
2356
|
} catch (publishError) {
|
|
3153
2357
|
debug("peernet publish failed: invalid-transaction", publishError?.message ?? publishError);
|
|
3154
2358
|
}
|
|
@@ -3159,7 +2363,13 @@ class Chain extends VersionControl {
|
|
|
3159
2363
|
async #addBlock(block) {
|
|
3160
2364
|
return this.withCanonicalState((transition) => this.#applyBlock(block, transition));
|
|
3161
2365
|
}
|
|
3162
|
-
|
|
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) {
|
|
3163
2373
|
const receivedEncoded = block instanceof BlockMessage ? block.encoded : block;
|
|
3164
2374
|
const blockMessage = await new BlockMessage(block);
|
|
3165
2375
|
const hash = await blockMessage.hash();
|
|
@@ -3193,13 +2403,14 @@ class Chain extends VersionControl {
|
|
|
3193
2403
|
console.log(`[chain] \u2705 Block data integrity verified: ${hash}`);
|
|
3194
2404
|
await this.#validateBlockValidators(blockMessage);
|
|
3195
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);
|
|
3196
2408
|
transition.markMutating();
|
|
3197
2409
|
await Promise.all(
|
|
3198
2410
|
blockMessage.decoded.transactions.map(async (transactionHash) => {
|
|
3199
2411
|
if (await transactionPoolStore.has(transactionHash)) await transactionPoolStore.delete(transactionHash);
|
|
3200
2412
|
})
|
|
3201
2413
|
);
|
|
3202
|
-
await globalThis.blockStore.put(hash, blockMessage.encoded);
|
|
3203
2414
|
this.#blocks[blockIndex] = {
|
|
3204
2415
|
hash,
|
|
3205
2416
|
...blockMessage.decoded
|
|
@@ -3233,9 +2444,6 @@ class Chain extends VersionControl {
|
|
|
3233
2444
|
}
|
|
3234
2445
|
const noncesByAddress = {};
|
|
3235
2446
|
for (let transaction of transactions) {
|
|
3236
|
-
globalThis.pubsub.publish("transaction-processed", transaction.encoded);
|
|
3237
|
-
if (transaction.decoded.to === globalThis.peernet.selectedAccount)
|
|
3238
|
-
globalThis.pubsub.publish("account-transaction-processed", transaction.encoded);
|
|
3239
2447
|
if (!noncesByAddress[transaction.decoded.from] || noncesByAddress?.[transaction.decoded.from] < transaction.decoded.nonce) {
|
|
3240
2448
|
noncesByAddress[transaction.decoded.from] = transaction.decoded.nonce;
|
|
3241
2449
|
}
|
|
@@ -3253,14 +2461,19 @@ class Chain extends VersionControl {
|
|
|
3253
2461
|
const registered = await this.machine.addLoadedBlock({ ...blockMessage.decoded, loaded: true, hash });
|
|
3254
2462
|
if (registered !== true) throw new Error(`Block registration failed after execution: ${hash}`);
|
|
3255
2463
|
try {
|
|
3256
|
-
await this
|
|
2464
|
+
await this.#recordValidatorSnapshot(blockMessage.decoded.index);
|
|
3257
2465
|
} catch (snapshotError) {
|
|
3258
2466
|
debug(`failed to record validator snapshot: ${snapshotError?.message ?? snapshotError}`);
|
|
3259
2467
|
}
|
|
3260
2468
|
await this.updateState(blockMessage);
|
|
3261
2469
|
debug(`added block: ${hash}`);
|
|
3262
|
-
|
|
3263
|
-
|
|
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);
|
|
3264
2477
|
}
|
|
3265
2478
|
async participate(address) {
|
|
3266
2479
|
try {
|
|
@@ -3308,15 +2521,14 @@ class Chain extends VersionControl {
|
|
|
3308
2521
|
async #handleTransaction(transaction, latestTransactions, block, feePayments = { payments: [], burned: 0n }) {
|
|
3309
2522
|
await this.validateTransactionSignature(transaction);
|
|
3310
2523
|
const hash = await transaction.hash();
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
}
|
|
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}`);
|
|
3320
2532
|
try {
|
|
3321
2533
|
const result = await this.#executeTransaction({
|
|
3322
2534
|
...transaction.decoded,
|
|
@@ -3333,7 +2545,7 @@ class Chain extends VersionControl {
|
|
|
3333
2545
|
console.log("vvvvvv");
|
|
3334
2546
|
console.log({ e });
|
|
3335
2547
|
console.log(hash);
|
|
3336
|
-
peernet.publish("invalid-transaction", hash);
|
|
2548
|
+
peernet.publish("invalid-transaction", encodeWire("invalid-transaction", hash));
|
|
3337
2549
|
await globalThis.transactionPoolStore.delete(e.hash);
|
|
3338
2550
|
if (block) throw e;
|
|
3339
2551
|
}
|
|
@@ -3498,7 +2710,7 @@ class Chain extends VersionControl {
|
|
|
3498
2710
|
} catch (e) {
|
|
3499
2711
|
if (hash) {
|
|
3500
2712
|
try {
|
|
3501
|
-
await globalThis.peernet.publish("invalid-transaction", hash);
|
|
2713
|
+
await globalThis.peernet.publish("invalid-transaction", encodeWire("invalid-transaction", hash));
|
|
3502
2714
|
} catch (publishError) {
|
|
3503
2715
|
debug("peernet publish failed: invalid-transaction", publishError?.message ?? publishError);
|
|
3504
2716
|
}
|
|
@@ -3512,7 +2724,8 @@ class Chain extends VersionControl {
|
|
|
3512
2724
|
* error is thrown on error so undefined data doesn't mean there is an error...
|
|
3513
2725
|
**/
|
|
3514
2726
|
async sendTransaction(transaction) {
|
|
3515
|
-
const
|
|
2727
|
+
const { hash: ignoredHash, ...payload } = transaction instanceof TransactionMessage ? transaction.decoded : transaction;
|
|
2728
|
+
const transactionMessage = await new TransactionMessage(payload);
|
|
3516
2729
|
const event = await super.sendTransaction(transactionMessage);
|
|
3517
2730
|
await this.#sendTransaction(transactionMessage.encoded);
|
|
3518
2731
|
try {
|
|
@@ -3552,7 +2765,7 @@ class Chain extends VersionControl {
|
|
|
3552
2765
|
* @returns
|
|
3553
2766
|
*/
|
|
3554
2767
|
internalCall(sender, contract, method, parameters) {
|
|
3555
|
-
|
|
2768
|
+
throw new Error("Direct sender impersonation is disabled; submit a signed transaction with sendTransaction()");
|
|
3556
2769
|
}
|
|
3557
2770
|
/**
|
|
3558
2771
|
*
|
|
@@ -3561,18 +2774,23 @@ class Chain extends VersionControl {
|
|
|
3561
2774
|
* @param {Array} parameters
|
|
3562
2775
|
* @returns
|
|
3563
2776
|
*/
|
|
3564
|
-
call(
|
|
3565
|
-
|
|
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));
|
|
3566
2782
|
}
|
|
3567
2783
|
staticCall(contract, method, parameters) {
|
|
3568
2784
|
return this.machine.get(contract, method, parameters);
|
|
3569
2785
|
}
|
|
3570
|
-
mint(
|
|
3571
|
-
|
|
2786
|
+
mint(...args) {
|
|
2787
|
+
throw new Error("Native minting is restricted to the monetary protocol");
|
|
3572
2788
|
}
|
|
3573
|
-
transfer(
|
|
3574
|
-
|
|
3575
|
-
|
|
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);
|
|
3576
2794
|
}
|
|
3577
2795
|
balanceOf(address) {
|
|
3578
2796
|
return this.staticCall(addresses.nativeToken, "balanceOf", [address]);
|
|
@@ -3599,7 +2817,7 @@ class Chain extends VersionControl {
|
|
|
3599
2817
|
* @example chain.lookup('myCoolContractName') // qmqsfddfdgfg...
|
|
3600
2818
|
*/
|
|
3601
2819
|
lookup(name) {
|
|
3602
|
-
return this.
|
|
2820
|
+
return this.staticCall(addresses.nameService, "lookup", [name]);
|
|
3603
2821
|
}
|
|
3604
2822
|
#monitorPeerConnections() {
|
|
3605
2823
|
setInterval(() => {
|