@leofcoin/chain 1.8.1 → 1.8.3
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/exports/browser/{browser-Qcpp3EKK-DOtgsScX.js → browser-BHbuEZJu-DB_cOj5W.js} +1 -1
- package/exports/browser/{browser-CfYI-6aD-DHRKebpJ.js → browser-DQlwTLRn-h2IyYbfg.js} +1 -1
- package/exports/browser/chain.js +302 -226
- package/exports/browser/{client-q0JXZnpu-ChkF_Pn_.js → client-DCeU_UX5-CR7iKpxy.js} +226 -32
- package/exports/browser/{identity-nIyW_Xm8-BU8xakCv.js → identity-B6BHwSTU-5rsAWMHo.js} +2 -2
- package/exports/browser/{index-DTbjK0sK-BK_5FT46.js → index-DYdP5D9L-D_bByMvp.js} +1 -1
- package/exports/browser/{messages-CC7UR5ol-BvFNyHjv.js → messages-CiR1YiV5-BOjOxVgq.js} +2 -2
- package/exports/browser/{node-browser-SPIwS-5O.js → node-browser-Bv-OkGUJ.js} +25 -36
- package/exports/browser/node-browser.js +2 -2
- package/exports/chain.js +279 -207
- package/exports/state.d.ts +17 -9
- package/package.json +4 -4
- package/exports/browser/index-ChRjMyiM-EjbBu23l.js +0 -36
package/exports/chain.js
CHANGED
|
@@ -6,7 +6,7 @@ import { calculateFee, createContractMessage, signTransaction, contractFactoryMe
|
|
|
6
6
|
import semver from 'semver';
|
|
7
7
|
import { randombytes } from '@leofcoin/crypto';
|
|
8
8
|
import EasyWorker from '@vandeurenglenn/easy-worker';
|
|
9
|
-
import { ContractDeploymentError, ExecutionError, ResolveError, isExecutionError } from '@leofcoin/errors';
|
|
9
|
+
import { ContractDeploymentError, ExecutionError, isResolveError, ResolveError, isExecutionError } from '@leofcoin/errors';
|
|
10
10
|
|
|
11
11
|
const limit = 1800;
|
|
12
12
|
const transactionLimit = 1000;
|
|
@@ -771,34 +771,42 @@ class Jobber {
|
|
|
771
771
|
|
|
772
772
|
const debug$1 = createDebugger('leofcoin/state');
|
|
773
773
|
class State extends Contract {
|
|
774
|
+
#resolveErrored;
|
|
775
|
+
#lastResolvedTime;
|
|
776
|
+
#lastResolved;
|
|
777
|
+
#resolving;
|
|
778
|
+
#resolveErrorCount;
|
|
779
|
+
#syncState;
|
|
780
|
+
#chainState;
|
|
781
|
+
#lastBlockInQue;
|
|
782
|
+
#syncErrorCount;
|
|
774
783
|
#blockHashMap;
|
|
784
|
+
#chainSyncing;
|
|
775
785
|
#blocks;
|
|
786
|
+
#totalSize;
|
|
776
787
|
#machine;
|
|
777
788
|
#loaded;
|
|
778
|
-
// Sync state
|
|
779
|
-
#syncing;
|
|
780
|
-
#syncErrorCount;
|
|
781
|
-
// Block resolution state
|
|
782
|
-
#resolvingBlocks;
|
|
783
|
-
#maxConcurrentResolves;
|
|
784
|
-
#totalSize;
|
|
785
|
-
#lastResolved;
|
|
786
|
-
#lastResolvedTime;
|
|
787
|
-
#blockResolveQueue;
|
|
788
|
-
#chainState;
|
|
789
789
|
/**
|
|
790
790
|
* contains transactions we need before we can successfully load
|
|
791
791
|
*/
|
|
792
792
|
get wantList() {
|
|
793
793
|
return this.#machine?.wantList ?? this._wantList;
|
|
794
794
|
}
|
|
795
|
+
get state() {
|
|
796
|
+
return {
|
|
797
|
+
sync: this.#syncState,
|
|
798
|
+
chain: this.#chainState
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
get blockHashMap() {
|
|
802
|
+
return this.#blockHashMap.entries();
|
|
803
|
+
}
|
|
795
804
|
get loaded() {
|
|
796
805
|
return this.#loaded;
|
|
797
806
|
}
|
|
798
|
-
get
|
|
799
|
-
return this.#
|
|
807
|
+
get resolving() {
|
|
808
|
+
return this.#resolving;
|
|
800
809
|
}
|
|
801
|
-
// Delegate to machine
|
|
802
810
|
get contracts() {
|
|
803
811
|
return this.#machine.contracts;
|
|
804
812
|
}
|
|
@@ -841,38 +849,35 @@ class State extends Contract {
|
|
|
841
849
|
get lastBlockHeight() {
|
|
842
850
|
return this.#machine ? this.#machine.lastBlockHeight : 0;
|
|
843
851
|
}
|
|
844
|
-
get totalSize() {
|
|
845
|
-
return this.#totalSize;
|
|
846
|
-
}
|
|
847
|
-
get machine() {
|
|
848
|
-
return this.#machine;
|
|
849
|
-
}
|
|
850
|
-
get blockHashMap() {
|
|
851
|
-
return this.#blockHashMap.entries();
|
|
852
|
-
}
|
|
853
852
|
getBlock(index) {
|
|
854
853
|
return this.#machine.getBlock(index);
|
|
855
854
|
}
|
|
856
855
|
getBlocks(from, to) {
|
|
857
856
|
return this.#machine.getBlocks(from, to);
|
|
858
857
|
}
|
|
858
|
+
get totalSize() {
|
|
859
|
+
return this.#totalSize;
|
|
860
|
+
}
|
|
861
|
+
get machine() {
|
|
862
|
+
return this.#machine;
|
|
863
|
+
}
|
|
859
864
|
constructor(config) {
|
|
860
865
|
super(config);
|
|
866
|
+
this.#lastResolvedTime = 0;
|
|
867
|
+
this.#resolving = false;
|
|
868
|
+
this.#resolveErrorCount = 0;
|
|
869
|
+
this.#chainState = 'loading';
|
|
870
|
+
this.#syncErrorCount = 0;
|
|
861
871
|
this.#blockHashMap = new Map();
|
|
872
|
+
this.#chainSyncing = false;
|
|
862
873
|
this.#blocks = [];
|
|
863
|
-
this.#loaded = false;
|
|
864
|
-
// Sync state
|
|
865
|
-
this.#syncing = false;
|
|
866
|
-
this.#syncErrorCount = 0;
|
|
867
|
-
// Block resolution state
|
|
868
|
-
this.#resolvingBlocks = new Set();
|
|
869
|
-
this.#maxConcurrentResolves = 10;
|
|
870
874
|
this.knownBlocks = [];
|
|
871
875
|
this.#totalSize = 0;
|
|
876
|
+
this.#loaded = false;
|
|
872
877
|
this._wantList = [];
|
|
873
878
|
this.#chainStateHandler = () => {
|
|
874
879
|
return new globalThis.peernet.protos['peernet-response']({
|
|
875
|
-
response:
|
|
880
|
+
response: this.#chainState
|
|
876
881
|
});
|
|
877
882
|
};
|
|
878
883
|
this.#lastBlockHandler = async () => {
|
|
@@ -914,45 +919,67 @@ class State extends Contract {
|
|
|
914
919
|
#lastBlockHandler;
|
|
915
920
|
#knownBlocksHandler;
|
|
916
921
|
async init() {
|
|
917
|
-
// Initialize jobber for timed, cancelable tasks
|
|
918
922
|
this.jobber = new Jobber(this.resolveTimeout);
|
|
919
|
-
|
|
920
|
-
await globalThis.peernet.addRequestHandler('
|
|
921
|
-
await globalThis.peernet.addRequestHandler('
|
|
922
|
-
|
|
923
|
+
await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler);
|
|
924
|
+
await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler);
|
|
925
|
+
await globalThis.peernet.addRequestHandler('chainState', this.#chainStateHandler);
|
|
926
|
+
let localBlockHash;
|
|
927
|
+
let blockMessage;
|
|
928
|
+
let localBlock;
|
|
923
929
|
try {
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
+
const rawBlock = await globalThis.chainStore.has('lastBlock');
|
|
931
|
+
if (rawBlock) {
|
|
932
|
+
localBlockHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
|
|
933
|
+
if (localBlockHash !== '0x0') {
|
|
934
|
+
blockMessage = await globalThis.peernet.get(localBlockHash, 'block');
|
|
935
|
+
blockMessage = await new BlockMessage(blockMessage);
|
|
930
936
|
localBlock = { ...blockMessage.decoded, hash: localBlockHash };
|
|
931
937
|
}
|
|
932
938
|
}
|
|
933
|
-
|
|
934
|
-
|
|
939
|
+
else {
|
|
940
|
+
localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
|
|
935
941
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
942
|
+
}
|
|
943
|
+
catch {
|
|
944
|
+
localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
|
|
945
|
+
}
|
|
946
|
+
try {
|
|
947
|
+
this.knownBlocks = await blockStore.keys();
|
|
948
|
+
}
|
|
949
|
+
catch (error) {
|
|
950
|
+
debug$1('no local known blocks found');
|
|
951
|
+
}
|
|
952
|
+
try {
|
|
953
|
+
if (localBlock.hash && localBlock.hash !== '0x0') {
|
|
954
|
+
try {
|
|
955
|
+
const states = {
|
|
956
|
+
lastBlock: JSON.parse(new TextDecoder().decode(await globalThis.stateStore.get('lastBlock')))
|
|
957
|
+
};
|
|
958
|
+
if (blockMessage.decoded.index > states.lastBlock.index)
|
|
959
|
+
await this.resolveBlocks();
|
|
960
|
+
}
|
|
961
|
+
catch (error) {
|
|
962
|
+
// no states found, try resolving blocks
|
|
963
|
+
await this.resolveBlocks();
|
|
964
|
+
}
|
|
939
965
|
}
|
|
940
|
-
|
|
941
|
-
|
|
966
|
+
else {
|
|
967
|
+
await this.resolveBlocks();
|
|
942
968
|
}
|
|
943
|
-
// Initialize machine and resolve blocks if needed
|
|
944
969
|
this.#machine = await new Machine(this.#blocks);
|
|
945
|
-
if (localBlock.hash !== '0x0') {
|
|
946
|
-
await this.resolveBlock(localBlock.hash);
|
|
947
|
-
}
|
|
948
970
|
const lastBlock = await this.#machine.lastBlock;
|
|
949
971
|
if (lastBlock.hash !== '0x0') {
|
|
950
|
-
|
|
972
|
+
this.updateState(new BlockMessage(lastBlock));
|
|
951
973
|
}
|
|
952
974
|
this.#loaded = true;
|
|
975
|
+
// await this.#loadBlocks(this.#blocks)
|
|
953
976
|
}
|
|
954
977
|
catch (error) {
|
|
955
|
-
console.
|
|
978
|
+
console.log('e');
|
|
979
|
+
if (isResolveError(error)) {
|
|
980
|
+
console.error(error);
|
|
981
|
+
}
|
|
982
|
+
console.log(error);
|
|
956
983
|
}
|
|
957
984
|
}
|
|
958
985
|
async updateState(message) {
|
|
@@ -986,47 +1013,41 @@ class State extends Contract {
|
|
|
986
1013
|
return block;
|
|
987
1014
|
}
|
|
988
1015
|
async #resolveBlock(hash) {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
this.#resolvingBlocks.add(hash);
|
|
1016
|
+
let index = this.#blockHashMap.get(hash);
|
|
1017
|
+
let localHash = '0x0';
|
|
993
1018
|
try {
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1019
|
+
localHash = await globalThis.stateStore.get('lastBlock');
|
|
1020
|
+
}
|
|
1021
|
+
catch (error) {
|
|
1022
|
+
debug$1('no local state found');
|
|
1023
|
+
}
|
|
1024
|
+
if (this.#blocks[index]) {
|
|
1025
|
+
// Block already exists, check if we need to resolve previous blocks
|
|
1026
|
+
const previousHash = this.#blocks[index].previousHash;
|
|
1027
|
+
if (previousHash === localHash)
|
|
998
1028
|
return;
|
|
1029
|
+
if (previousHash !== '0x0') {
|
|
1030
|
+
// Previous block not in memory, recursively resolve it
|
|
1031
|
+
return this.resolveBlock(previousHash);
|
|
999
1032
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
// Batch transaction operations
|
|
1004
|
-
const transactionsToFetch = [];
|
|
1005
|
-
const transactionHashes = block.decoded.transactions || [];
|
|
1006
|
-
for (const txHash of transactionHashes) {
|
|
1007
|
-
if (!(await globalThis.transactionStore.has(txHash))) {
|
|
1008
|
-
transactionsToFetch.push(txHash);
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
// Fetch all missing transactions in parallel
|
|
1012
|
-
if (transactionsToFetch.length > 0) {
|
|
1013
|
-
const fetchedResults = await Promise.allSettled(transactionsToFetch.map((txHash) => globalThis.peernet.get(txHash, 'transaction')));
|
|
1014
|
-
// Batch store all transactions that were successfully fetched
|
|
1015
|
-
for (let i = 0; i < fetchedResults.length; i++) {
|
|
1016
|
-
if (fetchedResults[i].status === 'fulfilled') {
|
|
1017
|
-
await globalThis.transactionStore.put(transactionsToFetch[i], fetchedResults[i].value);
|
|
1018
|
-
}
|
|
1019
|
-
else {
|
|
1020
|
-
debug$1(`failed to fetch transaction ${transactionsToFetch[i]}: ${fetchedResults[i].reason?.message || fetchedResults[i].reason}`);
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1033
|
+
else {
|
|
1034
|
+
// Previous block already exists or is genesis, stop resolving
|
|
1035
|
+
return;
|
|
1023
1036
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1037
|
+
}
|
|
1038
|
+
try {
|
|
1039
|
+
const block = await this.getAndPutBlock(hash);
|
|
1040
|
+
await Promise.all(block.decoded.transactions.map(async (hash) => {
|
|
1041
|
+
// should be in a transaction store already
|
|
1042
|
+
if (!(await transactionStore.has(hash))) {
|
|
1043
|
+
const data = await peernet.get(hash, 'transaction');
|
|
1044
|
+
await transactionStore.put(hash, data);
|
|
1028
1045
|
}
|
|
1046
|
+
;
|
|
1047
|
+
(await transactionPoolStore.has(hash)) && (await transactionPoolStore.delete(hash));
|
|
1029
1048
|
}));
|
|
1049
|
+
index = block.decoded.index;
|
|
1050
|
+
const size = block.encoded.length > 0 ? block.encoded.length : block.encoded.byteLength;
|
|
1030
1051
|
this.#totalSize += size;
|
|
1031
1052
|
this.#blocks[index] = { hash, ...block.decoded };
|
|
1032
1053
|
this.#blockHashMap.set(hash, index);
|
|
@@ -1036,147 +1057,187 @@ class State extends Contract {
|
|
|
1036
1057
|
this.#lastResolvedTime = Date.now();
|
|
1037
1058
|
}
|
|
1038
1059
|
catch (error) {
|
|
1039
|
-
throw new ResolveError(`block: ${hash}`);
|
|
1060
|
+
throw new ResolveError(`block: ${hash}@${index}`);
|
|
1040
1061
|
}
|
|
1041
|
-
|
|
1042
|
-
this.#resolvingBlocks.delete(hash);
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
async #buildBlockChain(latestHash, maxBlocks = 1000) {
|
|
1046
|
-
const chain = [];
|
|
1047
|
-
let currentHash = latestHash;
|
|
1048
|
-
let attempts = 0;
|
|
1049
|
-
while (currentHash !== '0x0' && chain.length < maxBlocks && attempts < maxBlocks + 5) {
|
|
1050
|
-
attempts++;
|
|
1051
|
-
// Check if we already have this block
|
|
1052
|
-
if (this.#blockHashMap.has(currentHash)) {
|
|
1053
|
-
const block = this.#blocks[this.#blockHashMap.get(currentHash)];
|
|
1054
|
-
if (block) {
|
|
1055
|
-
chain.push(currentHash);
|
|
1056
|
-
currentHash = block.previousHash;
|
|
1057
|
-
continue;
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
chain.push(currentHash);
|
|
1061
|
-
// Try to get the block to find previous hash
|
|
1062
|
-
try {
|
|
1063
|
-
const block = await this.getAndPutBlock(currentHash);
|
|
1064
|
-
currentHash = block.decoded.previousHash;
|
|
1065
|
-
}
|
|
1066
|
-
catch (error) {
|
|
1067
|
-
debug$1(`Could not fetch block ${currentHash} to determine chain: ${error}`);
|
|
1068
|
-
break;
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
return chain;
|
|
1072
|
-
}
|
|
1073
|
-
async #resolveBlocksInParallel(hashes) {
|
|
1074
|
-
// Resolve blocks in parallel with concurrency limit
|
|
1075
|
-
const resolving = [];
|
|
1076
|
-
let index = 0;
|
|
1077
|
-
const resolveNext = async () => {
|
|
1078
|
-
while (index < hashes.length) {
|
|
1079
|
-
const hash = hashes[index++];
|
|
1080
|
-
try {
|
|
1081
|
-
await this.#resolveBlock(hash);
|
|
1082
|
-
}
|
|
1083
|
-
catch (error) {
|
|
1084
|
-
debug$1(`Failed to resolve block ${hash}: ${error}`);
|
|
1085
|
-
this.#blockResolveQueue.push({ hash, retries: 0 });
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
};
|
|
1089
|
-
// Start concurrent resolution tasks
|
|
1090
|
-
for (let i = 0; i < Math.min(this.#maxConcurrentResolves, hashes.length); i++) {
|
|
1091
|
-
resolving.push(resolveNext());
|
|
1092
|
-
}
|
|
1093
|
-
await Promise.all(resolving);
|
|
1062
|
+
return;
|
|
1094
1063
|
}
|
|
1095
1064
|
async resolveBlock(hash) {
|
|
1096
|
-
if (!hash
|
|
1097
|
-
|
|
1098
|
-
if (
|
|
1065
|
+
if (!hash)
|
|
1066
|
+
throw new Error(`expected hash, got: ${hash}`);
|
|
1067
|
+
if (hash === '0x0')
|
|
1099
1068
|
return;
|
|
1100
|
-
this.#
|
|
1101
|
-
|
|
1069
|
+
if (this.#resolving)
|
|
1070
|
+
return 'already resolving';
|
|
1071
|
+
this.#resolving = true;
|
|
1072
|
+
if (this.jobber.busy && this.jobber.destroy)
|
|
1073
|
+
await this.jobber.destroy();
|
|
1102
1074
|
try {
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
if (
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
await this.jobber.destroy();
|
|
1110
|
-
// Run the parallel resolution inside a timed jobber task
|
|
1111
|
-
await this.jobber.add(() => this.#resolveBlocksInParallel(blockChain));
|
|
1075
|
+
await this.jobber.add(() => this.#resolveBlock(hash));
|
|
1076
|
+
this.#resolving = false;
|
|
1077
|
+
const lastBlockHash = await globalThis.stateStore.get('lastBlock');
|
|
1078
|
+
if (lastBlockHash === hash) {
|
|
1079
|
+
this.#resolveErrored = false;
|
|
1080
|
+
return;
|
|
1112
1081
|
}
|
|
1082
|
+
if (!this.#blockHashMap.has(this.#lastResolved.previousHash) && this.#lastResolved.previousHash !== '0x0')
|
|
1083
|
+
return this.resolveBlock(this.#lastResolved.previousHash);
|
|
1113
1084
|
}
|
|
1114
1085
|
catch (error) {
|
|
1115
|
-
console.
|
|
1086
|
+
console.log({ error });
|
|
1087
|
+
this.#resolveErrorCount += 1;
|
|
1088
|
+
this.#resolving = false;
|
|
1089
|
+
if (this.#resolveErrorCount < 3)
|
|
1090
|
+
return this.resolveBlock(hash);
|
|
1091
|
+
this.#resolveErrorCount = 0;
|
|
1116
1092
|
this.wantList.push(hash);
|
|
1117
|
-
|
|
1118
|
-
finally {
|
|
1119
|
-
this.#syncing = false;
|
|
1093
|
+
throw new ResolveError(`block: ${hash}`, { cause: error });
|
|
1120
1094
|
}
|
|
1121
1095
|
}
|
|
1122
1096
|
async resolveBlocks() {
|
|
1123
|
-
if
|
|
1097
|
+
// Don't re-resolve if already syncing or resolving
|
|
1098
|
+
if (this.#chainSyncing || this.#resolving) {
|
|
1099
|
+
debug$1('Already syncing or resolving, skipping resolveBlocks()');
|
|
1124
1100
|
return;
|
|
1101
|
+
}
|
|
1102
|
+
try {
|
|
1103
|
+
if (this.jobber.busy && this.jobber.destroy) {
|
|
1104
|
+
await this.jobber.destroy();
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
catch (error) {
|
|
1108
|
+
console.error(error);
|
|
1109
|
+
}
|
|
1125
1110
|
try {
|
|
1126
1111
|
const localBlock = await globalThis.chainStore.get('lastBlock');
|
|
1127
1112
|
const hash = new TextDecoder().decode(localBlock);
|
|
1128
1113
|
if (hash && hash !== '0x0') {
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
await this.jobber.destroy();
|
|
1132
|
-
// Build chain and resolve in parallel under jobber control
|
|
1133
|
-
const run = async () => {
|
|
1134
|
-
const chain = await this.#buildBlockChain(hash);
|
|
1135
|
-
if (chain.length > 0) {
|
|
1136
|
-
await this.#resolveBlocksInParallel(chain);
|
|
1137
|
-
}
|
|
1138
|
-
};
|
|
1139
|
-
await this.jobber.add(run);
|
|
1114
|
+
debug$1(`Resolving blocks from hash: ${hash}`);
|
|
1115
|
+
await this.resolveBlock(hash);
|
|
1140
1116
|
}
|
|
1141
1117
|
}
|
|
1142
1118
|
catch (error) {
|
|
1143
|
-
|
|
1119
|
+
console.log(error);
|
|
1120
|
+
this.#chainSyncing = false;
|
|
1121
|
+
this.#syncState = 'errored';
|
|
1122
|
+
this.#resolveErrored = true;
|
|
1123
|
+
return this.restoreChain();
|
|
1124
|
+
// console.log(e);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
async restoreChain() {
|
|
1128
|
+
try {
|
|
1129
|
+
const { hash } = await this.#getLatestBlock();
|
|
1130
|
+
await globalThis.chainStore.put('lastBlock', hash);
|
|
1131
|
+
if (hash && hash !== '0x0') {
|
|
1132
|
+
await this.resolveBlock(hash);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
catch (error) {
|
|
1136
|
+
console.log(error);
|
|
1137
|
+
this.#resolveErrored = true;
|
|
1138
|
+
this.#resolveErrorCount += 1;
|
|
1139
|
+
this.#resolving = false;
|
|
1140
|
+
return this.restoreChain();
|
|
1141
|
+
// console.log(e);
|
|
1144
1142
|
}
|
|
1145
1143
|
}
|
|
1146
1144
|
async syncChain(lastBlock) {
|
|
1147
|
-
if
|
|
1148
|
-
|
|
1145
|
+
console.log('check if can sync');
|
|
1146
|
+
if (!this.shouldSync)
|
|
1147
|
+
return;
|
|
1148
|
+
console.log('starting sync');
|
|
1149
|
+
this.#syncState = 'syncing';
|
|
1150
|
+
this.#chainSyncing = true;
|
|
1151
|
+
try {
|
|
1152
|
+
if (this.jobber.busy && this.jobber.destroy) {
|
|
1153
|
+
await this.jobber.destroy();
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
catch (error) {
|
|
1157
|
+
console.error(error);
|
|
1158
|
+
}
|
|
1149
1159
|
if (!lastBlock)
|
|
1150
1160
|
lastBlock = await this.#getLatestBlock();
|
|
1151
|
-
|
|
1161
|
+
if (globalThis.peernet.peers.length === 0)
|
|
1162
|
+
return 'connectionless';
|
|
1152
1163
|
try {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1164
|
+
await this.#syncChain(lastBlock);
|
|
1165
|
+
}
|
|
1166
|
+
catch (error) {
|
|
1167
|
+
this.#syncErrorCount += 1;
|
|
1168
|
+
if (this.#syncErrorCount < 3)
|
|
1169
|
+
return this.syncChain(lastBlock);
|
|
1170
|
+
this.#syncErrorCount = 0;
|
|
1171
|
+
this.#chainSyncing = false;
|
|
1172
|
+
this.#syncState = 'errored';
|
|
1173
|
+
return this.#syncState;
|
|
1174
|
+
}
|
|
1175
|
+
if (lastBlock.index === this.#lastBlockInQue?.index)
|
|
1176
|
+
this.#lastBlockInQue = undefined;
|
|
1177
|
+
this.#syncErrorCount = 0;
|
|
1178
|
+
this.#chainSyncing = false;
|
|
1179
|
+
if (this.#lastBlockInQue)
|
|
1180
|
+
return this.syncChain(this.#lastBlockInQue);
|
|
1181
|
+
this.#syncState = 'synced';
|
|
1182
|
+
return this.#syncState;
|
|
1183
|
+
}
|
|
1184
|
+
async #syncChain(lastBlock) {
|
|
1185
|
+
try {
|
|
1186
|
+
// if (this.knownBlocks?.length === Number(lastBlock.index) + 1) {
|
|
1187
|
+
// let promises = []
|
|
1188
|
+
// promises = await Promise.allSettled(
|
|
1189
|
+
// this.knownBlocks.map(async (address) => {
|
|
1190
|
+
// const has = await globalThis.peernet.has(address)
|
|
1191
|
+
// return { has, address }
|
|
1192
|
+
// })
|
|
1193
|
+
// )
|
|
1194
|
+
// promises = promises.filter(({ status, value }) => status === 'fulfilled' && !value.has)
|
|
1195
|
+
// await Promise.allSettled(promises.map(({ value }) => this.getAndPutBlock(value.address)))
|
|
1196
|
+
// }
|
|
1197
|
+
const localBlock = await this.lastBlock;
|
|
1198
|
+
const localIndex = localBlock ? Number(localBlock.index) : -1;
|
|
1199
|
+
const remoteIndex = Number(lastBlock.index);
|
|
1200
|
+
const remoteBlockHash = lastBlock.hash;
|
|
1201
|
+
// Get the local state hash from chainStore
|
|
1202
|
+
let localStateHash = '0x0';
|
|
1203
|
+
try {
|
|
1204
|
+
localStateHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
|
|
1156
1205
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
const localIndex = (await this.lastBlock).index || -1;
|
|
1160
|
-
const start = Math.max(0, localIndex + 1);
|
|
1161
|
-
if (this.#machine && blocks.length > start) {
|
|
1162
|
-
await this.#loadBlocks(blocks.slice(start));
|
|
1206
|
+
catch (error) {
|
|
1207
|
+
debug$1(`No local state hash found: ${error}`);
|
|
1163
1208
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1209
|
+
debug$1(`Local block height: ${localIndex}, remote block height: ${remoteIndex}`);
|
|
1210
|
+
debug$1(`Local state hash: ${localStateHash}, remote block hash: ${remoteBlockHash}`);
|
|
1211
|
+
// Skip syncing if remote block hash is 0x0 (invalid state)
|
|
1212
|
+
if (remoteBlockHash === '0x0') {
|
|
1213
|
+
debug$1(`Remote block hash is 0x0, skipping sync`);
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
// Use state hash comparison: only resolve if remote hash differs from local state hash
|
|
1217
|
+
if (localStateHash !== remoteBlockHash) {
|
|
1218
|
+
// Remote block hash differs from our local state, need to resolve
|
|
1219
|
+
debug$1(`Resolving remote block: ${remoteBlockHash} @${remoteIndex} (differs from local state)`);
|
|
1220
|
+
await this.resolveBlock(remoteBlockHash);
|
|
1221
|
+
const blocksSynced = remoteIndex - localIndex;
|
|
1222
|
+
debug$1(`Resolved ${blocksSynced} new block(s)`);
|
|
1223
|
+
const blocks = this.#blocks;
|
|
1224
|
+
debug$1(`Loading blocks from index ${localIndex + 1} to ${remoteIndex}`);
|
|
1225
|
+
const start = localIndex + 1;
|
|
1226
|
+
if (this.#machine && blocks.length > start) {
|
|
1227
|
+
await this.#loadBlocks(blocks.slice(start));
|
|
1228
|
+
}
|
|
1229
|
+
// Update state with the latest block
|
|
1230
|
+
if (blocks.length > 0) {
|
|
1231
|
+
await this.updateState(new BlockMessage(blocks[blocks.length - 1]));
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
else {
|
|
1235
|
+
debug$1(`Block already in local state. Remote hash: ${remoteBlockHash} matches local state`);
|
|
1166
1236
|
}
|
|
1167
|
-
this.#syncErrorCount = 0;
|
|
1168
|
-
this.#syncing = false;
|
|
1169
|
-
return 'synced';
|
|
1170
1237
|
}
|
|
1171
1238
|
catch (error) {
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
this.#syncing = false;
|
|
1175
|
-
return this.syncChain(lastBlock);
|
|
1176
|
-
}
|
|
1177
|
-
this.#syncErrorCount = 0;
|
|
1178
|
-
this.#syncing = false;
|
|
1179
|
-
return 'errored';
|
|
1239
|
+
console.log(error);
|
|
1240
|
+
throw error;
|
|
1180
1241
|
}
|
|
1181
1242
|
}
|
|
1182
1243
|
async #getLatestBlock() {
|
|
@@ -1212,9 +1273,7 @@ class State extends Contract {
|
|
|
1212
1273
|
debug$1(`Latest block from peers: ${latest.hash} @${latest.index}`);
|
|
1213
1274
|
if (latest.hash && latest.hash !== '0x0') {
|
|
1214
1275
|
let message = await globalThis.peernet.get(latest.hash, 'block');
|
|
1215
|
-
debug$1({ message });
|
|
1216
1276
|
message = await new BlockMessage(message);
|
|
1217
|
-
debug$1({ message });
|
|
1218
1277
|
const hash = await message.hash();
|
|
1219
1278
|
if (hash !== latest.hash)
|
|
1220
1279
|
throw new Error('invalid block @getLatestBlock');
|
|
@@ -1227,7 +1286,7 @@ class State extends Contract {
|
|
|
1227
1286
|
let node = await globalThis.peernet.prepareMessage(data);
|
|
1228
1287
|
let message = await peer.request(node.encode());
|
|
1229
1288
|
message = await new globalThis.peernet.protos['peernet-response'](message);
|
|
1230
|
-
this.wantList.push(...message.decoded.response);
|
|
1289
|
+
this.wantList.push(...message.decoded.response.blocks.filter((block) => !this.knownBlocks.includes(block)));
|
|
1231
1290
|
}
|
|
1232
1291
|
}
|
|
1233
1292
|
return latest;
|
|
@@ -1347,13 +1406,26 @@ class State extends Contract {
|
|
|
1347
1406
|
});
|
|
1348
1407
|
}
|
|
1349
1408
|
get canSync() {
|
|
1350
|
-
|
|
1409
|
+
if (this.#chainSyncing)
|
|
1410
|
+
return false;
|
|
1411
|
+
return true;
|
|
1351
1412
|
}
|
|
1352
1413
|
get shouldSync() {
|
|
1353
|
-
if (this.#
|
|
1414
|
+
if (this.#chainSyncing)
|
|
1354
1415
|
return false;
|
|
1416
|
+
// Check if we have any connected peers with the same version
|
|
1355
1417
|
const compatiblePeers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && peer.version === this.version);
|
|
1356
|
-
|
|
1418
|
+
if (compatiblePeers.length === 0) {
|
|
1419
|
+
debug$1('No compatible peers available for sync');
|
|
1420
|
+
return false;
|
|
1421
|
+
}
|
|
1422
|
+
if (!this.#chainSyncing ||
|
|
1423
|
+
this.#resolveErrored ||
|
|
1424
|
+
this.#syncState === 'errored' ||
|
|
1425
|
+
this.#syncState === 'connectionless' ||
|
|
1426
|
+
this.#lastResolvedTime + this.resolveTimeout > Date.now())
|
|
1427
|
+
return true;
|
|
1428
|
+
return false;
|
|
1357
1429
|
}
|
|
1358
1430
|
async #waitForPeers(timeoutMs = 30000) {
|
|
1359
1431
|
return new Promise((resolve) => {
|