@leofcoin/chain 1.7.149 → 1.7.150

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.
@@ -4646,6 +4646,9 @@ class State extends Contract {
4646
4646
  #totalSize;
4647
4647
  #machine;
4648
4648
  #loaded;
4649
+ #resolvingBlocks;
4650
+ #maxConcurrentResolves;
4651
+ #blockResolveQueue;
4649
4652
  /**
4650
4653
  * contains transactions we need before we can successfully load
4651
4654
  */
@@ -4735,6 +4738,9 @@ class State extends Contract {
4735
4738
  this.#totalSize = 0;
4736
4739
  this.#loaded = false;
4737
4740
  this._wantList = [];
4741
+ this.#resolvingBlocks = new Set();
4742
+ this.#maxConcurrentResolves = 10;
4743
+ this.#blockResolveQueue = [];
4738
4744
  this.#chainStateHandler = () => {
4739
4745
  return new globalThis.peernet.protos['peernet-response']({
4740
4746
  response: this.#chainState
@@ -4873,45 +4879,40 @@ class State extends Contract {
4873
4879
  return block;
4874
4880
  }
4875
4881
  async #resolveBlock(hash) {
4876
- let index = this.#blockHashMap.get(hash);
4877
- let localHash = '0x0';
4878
- try {
4879
- if (await globalThis.stateStore.has('lastBlock'))
4880
- localHash = await globalThis.stateStore.get('lastBlock');
4882
+ if (this.#resolvingBlocks.has(hash)) {
4883
+ return; // Already resolving this block
4881
4884
  }
4882
- catch (error) {
4883
- globalThis.stateStore.put('lastBlock', new TextEncoder().encode('0x0'));
4884
- debug$1('no local state found');
4885
- }
4886
- debug$1(`resolving block: ${hash} @${index !== undefined ? index : 'unknown'}`);
4887
- debug$1(`local state hash: ${localHash}`);
4888
- if (this.#blocks[index]) {
4889
- // Block already exists, check if we need to resolve previous blocks
4890
- const previousHash = this.#blocks[index].previousHash;
4891
- if (previousHash === localHash)
4892
- return;
4893
- if (previousHash !== '0x0') {
4894
- // Previous block not in memory, recursively resolve it
4895
- return this.resolveBlock(previousHash);
4896
- }
4897
- else {
4898
- // Previous block already exists or is genesis, stop resolving
4885
+ this.#resolvingBlocks.add(hash);
4886
+ try {
4887
+ let index = this.#blockHashMap.get(hash);
4888
+ debug$1(`resolving block: ${hash} @${index !== undefined ? index : 'unknown'}`);
4889
+ if (this.#blocks[index]) {
4890
+ // Block already exists
4899
4891
  return;
4900
4892
  }
4901
- }
4902
- try {
4903
4893
  const block = await this.getAndPutBlock(hash);
4904
- await Promise.all(block.decoded.transactions.map(async (hash) => {
4905
- // should be in a transaction store already
4906
- if (!(await transactionStore.has(hash))) {
4907
- const data = await peernet.get(hash, 'transaction');
4908
- await transactionStore.put(hash, data);
4909
- }
4910
- ;
4911
- (await transactionPoolStore.has(hash)) && (await transactionPoolStore.delete(hash));
4912
- }));
4913
4894
  index = block.decoded.index;
4914
4895
  const size = block.encoded.length > 0 ? block.encoded.length : block.encoded.byteLength;
4896
+ // Batch transaction operations
4897
+ const transactionsToFetch = [];
4898
+ const transactionHashes = block.decoded.transactions || [];
4899
+ for (const txHash of transactionHashes) {
4900
+ if (!(await globalThis.transactionStore.has(txHash))) {
4901
+ transactionsToFetch.push(txHash);
4902
+ }
4903
+ }
4904
+ // Fetch all missing transactions in parallel
4905
+ if (transactionsToFetch.length > 0) {
4906
+ const fetchedTransactions = await Promise.all(transactionsToFetch.map((txHash) => globalThis.peernet.get(txHash, 'transaction')));
4907
+ // Batch store all transactions
4908
+ await Promise.all(transactionsToFetch.map((txHash, i) => globalThis.transactionStore.put(txHash, fetchedTransactions[i])));
4909
+ }
4910
+ // Remove from pool
4911
+ await Promise.all(transactionHashes.map(async (txHash) => {
4912
+ if (await globalThis.transactionPoolStore.has(txHash)) {
4913
+ await globalThis.transactionPoolStore.delete(txHash);
4914
+ }
4915
+ }));
4915
4916
  this.#totalSize += size;
4916
4917
  this.#blocks[index] = { hash, ...block.decoded };
4917
4918
  this.#blockHashMap.set(hash, index);
@@ -4921,42 +4922,103 @@ class State extends Contract {
4921
4922
  this.#lastResolvedTime = Date.now();
4922
4923
  }
4923
4924
  catch (error) {
4924
- throw new ResolveError(`block: ${hash}@${index}`);
4925
+ throw new ResolveError(`block: ${hash}`);
4925
4926
  }
4926
- return;
4927
+ finally {
4928
+ this.#resolvingBlocks.delete(hash);
4929
+ }
4930
+ }
4931
+ async #buildBlockChain(latestHash, maxBlocks = 1000) {
4932
+ const chain = [];
4933
+ let currentHash = latestHash;
4934
+ let attempts = 0;
4935
+ while (currentHash !== '0x0' && chain.length < maxBlocks && attempts < maxBlocks + 5) {
4936
+ attempts++;
4937
+ // Check if we already have this block
4938
+ if (this.#blockHashMap.has(currentHash)) {
4939
+ const block = this.#blocks[this.#blockHashMap.get(currentHash)];
4940
+ if (block) {
4941
+ chain.push(currentHash);
4942
+ currentHash = block.previousHash;
4943
+ continue;
4944
+ }
4945
+ }
4946
+ chain.push(currentHash);
4947
+ // Try to get the block to find previous hash
4948
+ try {
4949
+ const block = await this.getAndPutBlock(currentHash);
4950
+ currentHash = block.decoded.previousHash;
4951
+ }
4952
+ catch (error) {
4953
+ debug$1(`Could not fetch block ${currentHash} to determine chain: ${error}`);
4954
+ break;
4955
+ }
4956
+ }
4957
+ return chain;
4958
+ }
4959
+ async #resolveBlocksInParallel(hashes) {
4960
+ // Resolve blocks in parallel with concurrency limit
4961
+ const resolving = [];
4962
+ let index = 0;
4963
+ const resolveNext = async () => {
4964
+ while (index < hashes.length) {
4965
+ const hash = hashes[index++];
4966
+ try {
4967
+ await this.#resolveBlock(hash);
4968
+ }
4969
+ catch (error) {
4970
+ debug$1(`Failed to resolve block ${hash}: ${error}`);
4971
+ this.#blockResolveQueue.push({ hash, retries: 0 });
4972
+ }
4973
+ }
4974
+ };
4975
+ // Start concurrent resolution tasks
4976
+ for (let i = 0; i < Math.min(this.#maxConcurrentResolves, hashes.length); i++) {
4977
+ resolving.push(resolveNext());
4978
+ }
4979
+ await Promise.all(resolving);
4927
4980
  }
4928
4981
  async resolveBlock(hash) {
4929
4982
  if (!hash)
4930
4983
  throw new Error(`expected hash, got: ${hash}`);
4931
4984
  if (hash === '0x0')
4932
4985
  return;
4933
- if (this.#resolving)
4934
- return 'already resolving';
4986
+ if (this.#resolving) {
4987
+ debug$1('Already resolving, queueing block');
4988
+ return;
4989
+ }
4935
4990
  this.#resolving = true;
4936
- if (this.jobber.busy && this.jobber.destroy)
4937
- await this.jobber.destroy();
4938
4991
  try {
4939
- await this.jobber.add(() => this.#resolveBlock(hash));
4992
+ // Build the entire block chain from latest to genesis
4993
+ debug$1(`Building block chain from ${hash}`);
4994
+ const blockChain = await this.#buildBlockChain(hash);
4995
+ debug$1(`Built chain of ${blockChain.length} blocks`);
4996
+ // Resolve all blocks in parallel
4997
+ if (blockChain.length > 0) {
4998
+ await this.#resolveBlocksInParallel(blockChain);
4999
+ }
4940
5000
  this.#resolving = false;
5001
+ this.#resolveErrorCount = 0;
5002
+ this.#resolveErrored = false;
4941
5003
  try {
4942
5004
  const lastBlockHash = await globalThis.stateStore.get('lastBlock');
4943
5005
  if (lastBlockHash === hash) {
4944
- this.#resolveErrored = false;
5006
+ debug$1('Resolved to latest block state');
4945
5007
  return;
4946
5008
  }
4947
5009
  }
4948
5010
  catch (error) {
4949
5011
  debug$1('no local state found');
4950
5012
  }
4951
- if (!this.#blockHashMap.has(this.#lastResolved.previousHash) && this.#lastResolved.previousHash !== '0x0')
4952
- return this.resolveBlock(this.#lastResolved.previousHash);
4953
5013
  }
4954
5014
  catch (error) {
4955
5015
  console.log({ error });
4956
5016
  this.#resolveErrorCount += 1;
4957
5017
  this.#resolving = false;
4958
- if (this.#resolveErrorCount < 3)
5018
+ if (this.#resolveErrorCount < 3) {
5019
+ debug$1(`Retry ${this.#resolveErrorCount}/3 for block ${hash}`);
4959
5020
  return this.resolveBlock(hash);
5021
+ }
4960
5022
  this.#resolveErrorCount = 0;
4961
5023
  this.wantList.push(hash);
4962
5024
  throw new ResolveError(`block: ${hash}`, { cause: error });
@@ -5157,7 +5219,7 @@ class State extends Contract {
5157
5219
  let node = await globalThis.peernet.prepareMessage(data);
5158
5220
  let message = await peer.request(node.encode());
5159
5221
  message = await new globalThis.peernet.protos['peernet-response'](message);
5160
- this.knownBlocks = message.decoded.response;
5222
+ this.wantList.push(...message.decoded.response);
5161
5223
  }
5162
5224
  }
5163
5225
  return latest;
package/exports/chain.js CHANGED
@@ -786,6 +786,9 @@ class State extends Contract {
786
786
  #totalSize;
787
787
  #machine;
788
788
  #loaded;
789
+ #resolvingBlocks;
790
+ #maxConcurrentResolves;
791
+ #blockResolveQueue;
789
792
  /**
790
793
  * contains transactions we need before we can successfully load
791
794
  */
@@ -875,6 +878,9 @@ class State extends Contract {
875
878
  this.#totalSize = 0;
876
879
  this.#loaded = false;
877
880
  this._wantList = [];
881
+ this.#resolvingBlocks = new Set();
882
+ this.#maxConcurrentResolves = 10;
883
+ this.#blockResolveQueue = [];
878
884
  this.#chainStateHandler = () => {
879
885
  return new globalThis.peernet.protos['peernet-response']({
880
886
  response: this.#chainState
@@ -1013,45 +1019,40 @@ class State extends Contract {
1013
1019
  return block;
1014
1020
  }
1015
1021
  async #resolveBlock(hash) {
1016
- let index = this.#blockHashMap.get(hash);
1017
- let localHash = '0x0';
1018
- try {
1019
- if (await globalThis.stateStore.has('lastBlock'))
1020
- localHash = await globalThis.stateStore.get('lastBlock');
1022
+ if (this.#resolvingBlocks.has(hash)) {
1023
+ return; // Already resolving this block
1021
1024
  }
1022
- catch (error) {
1023
- globalThis.stateStore.put('lastBlock', new TextEncoder().encode('0x0'));
1024
- debug$1('no local state found');
1025
- }
1026
- debug$1(`resolving block: ${hash} @${index !== undefined ? index : 'unknown'}`);
1027
- debug$1(`local state hash: ${localHash}`);
1028
- if (this.#blocks[index]) {
1029
- // Block already exists, check if we need to resolve previous blocks
1030
- const previousHash = this.#blocks[index].previousHash;
1031
- if (previousHash === localHash)
1032
- return;
1033
- if (previousHash !== '0x0') {
1034
- // Previous block not in memory, recursively resolve it
1035
- return this.resolveBlock(previousHash);
1036
- }
1037
- else {
1038
- // Previous block already exists or is genesis, stop resolving
1025
+ this.#resolvingBlocks.add(hash);
1026
+ try {
1027
+ let index = this.#blockHashMap.get(hash);
1028
+ debug$1(`resolving block: ${hash} @${index !== undefined ? index : 'unknown'}`);
1029
+ if (this.#blocks[index]) {
1030
+ // Block already exists
1039
1031
  return;
1040
1032
  }
1041
- }
1042
- try {
1043
1033
  const block = await this.getAndPutBlock(hash);
1044
- await Promise.all(block.decoded.transactions.map(async (hash) => {
1045
- // should be in a transaction store already
1046
- if (!(await transactionStore.has(hash))) {
1047
- const data = await peernet.get(hash, 'transaction');
1048
- await transactionStore.put(hash, data);
1049
- }
1050
- ;
1051
- (await transactionPoolStore.has(hash)) && (await transactionPoolStore.delete(hash));
1052
- }));
1053
1034
  index = block.decoded.index;
1054
1035
  const size = block.encoded.length > 0 ? block.encoded.length : block.encoded.byteLength;
1036
+ // Batch transaction operations
1037
+ const transactionsToFetch = [];
1038
+ const transactionHashes = block.decoded.transactions || [];
1039
+ for (const txHash of transactionHashes) {
1040
+ if (!(await globalThis.transactionStore.has(txHash))) {
1041
+ transactionsToFetch.push(txHash);
1042
+ }
1043
+ }
1044
+ // Fetch all missing transactions in parallel
1045
+ if (transactionsToFetch.length > 0) {
1046
+ const fetchedTransactions = await Promise.all(transactionsToFetch.map((txHash) => globalThis.peernet.get(txHash, 'transaction')));
1047
+ // Batch store all transactions
1048
+ await Promise.all(transactionsToFetch.map((txHash, i) => globalThis.transactionStore.put(txHash, fetchedTransactions[i])));
1049
+ }
1050
+ // Remove from pool
1051
+ await Promise.all(transactionHashes.map(async (txHash) => {
1052
+ if (await globalThis.transactionPoolStore.has(txHash)) {
1053
+ await globalThis.transactionPoolStore.delete(txHash);
1054
+ }
1055
+ }));
1055
1056
  this.#totalSize += size;
1056
1057
  this.#blocks[index] = { hash, ...block.decoded };
1057
1058
  this.#blockHashMap.set(hash, index);
@@ -1061,42 +1062,103 @@ class State extends Contract {
1061
1062
  this.#lastResolvedTime = Date.now();
1062
1063
  }
1063
1064
  catch (error) {
1064
- throw new ResolveError(`block: ${hash}@${index}`);
1065
+ throw new ResolveError(`block: ${hash}`);
1066
+ }
1067
+ finally {
1068
+ this.#resolvingBlocks.delete(hash);
1069
+ }
1070
+ }
1071
+ async #buildBlockChain(latestHash, maxBlocks = 1000) {
1072
+ const chain = [];
1073
+ let currentHash = latestHash;
1074
+ let attempts = 0;
1075
+ while (currentHash !== '0x0' && chain.length < maxBlocks && attempts < maxBlocks + 5) {
1076
+ attempts++;
1077
+ // Check if we already have this block
1078
+ if (this.#blockHashMap.has(currentHash)) {
1079
+ const block = this.#blocks[this.#blockHashMap.get(currentHash)];
1080
+ if (block) {
1081
+ chain.push(currentHash);
1082
+ currentHash = block.previousHash;
1083
+ continue;
1084
+ }
1085
+ }
1086
+ chain.push(currentHash);
1087
+ // Try to get the block to find previous hash
1088
+ try {
1089
+ const block = await this.getAndPutBlock(currentHash);
1090
+ currentHash = block.decoded.previousHash;
1091
+ }
1092
+ catch (error) {
1093
+ debug$1(`Could not fetch block ${currentHash} to determine chain: ${error}`);
1094
+ break;
1095
+ }
1065
1096
  }
1066
- return;
1097
+ return chain;
1098
+ }
1099
+ async #resolveBlocksInParallel(hashes) {
1100
+ // Resolve blocks in parallel with concurrency limit
1101
+ const resolving = [];
1102
+ let index = 0;
1103
+ const resolveNext = async () => {
1104
+ while (index < hashes.length) {
1105
+ const hash = hashes[index++];
1106
+ try {
1107
+ await this.#resolveBlock(hash);
1108
+ }
1109
+ catch (error) {
1110
+ debug$1(`Failed to resolve block ${hash}: ${error}`);
1111
+ this.#blockResolveQueue.push({ hash, retries: 0 });
1112
+ }
1113
+ }
1114
+ };
1115
+ // Start concurrent resolution tasks
1116
+ for (let i = 0; i < Math.min(this.#maxConcurrentResolves, hashes.length); i++) {
1117
+ resolving.push(resolveNext());
1118
+ }
1119
+ await Promise.all(resolving);
1067
1120
  }
1068
1121
  async resolveBlock(hash) {
1069
1122
  if (!hash)
1070
1123
  throw new Error(`expected hash, got: ${hash}`);
1071
1124
  if (hash === '0x0')
1072
1125
  return;
1073
- if (this.#resolving)
1074
- return 'already resolving';
1126
+ if (this.#resolving) {
1127
+ debug$1('Already resolving, queueing block');
1128
+ return;
1129
+ }
1075
1130
  this.#resolving = true;
1076
- if (this.jobber.busy && this.jobber.destroy)
1077
- await this.jobber.destroy();
1078
1131
  try {
1079
- await this.jobber.add(() => this.#resolveBlock(hash));
1132
+ // Build the entire block chain from latest to genesis
1133
+ debug$1(`Building block chain from ${hash}`);
1134
+ const blockChain = await this.#buildBlockChain(hash);
1135
+ debug$1(`Built chain of ${blockChain.length} blocks`);
1136
+ // Resolve all blocks in parallel
1137
+ if (blockChain.length > 0) {
1138
+ await this.#resolveBlocksInParallel(blockChain);
1139
+ }
1080
1140
  this.#resolving = false;
1141
+ this.#resolveErrorCount = 0;
1142
+ this.#resolveErrored = false;
1081
1143
  try {
1082
1144
  const lastBlockHash = await globalThis.stateStore.get('lastBlock');
1083
1145
  if (lastBlockHash === hash) {
1084
- this.#resolveErrored = false;
1146
+ debug$1('Resolved to latest block state');
1085
1147
  return;
1086
1148
  }
1087
1149
  }
1088
1150
  catch (error) {
1089
1151
  debug$1('no local state found');
1090
1152
  }
1091
- if (!this.#blockHashMap.has(this.#lastResolved.previousHash) && this.#lastResolved.previousHash !== '0x0')
1092
- return this.resolveBlock(this.#lastResolved.previousHash);
1093
1153
  }
1094
1154
  catch (error) {
1095
1155
  console.log({ error });
1096
1156
  this.#resolveErrorCount += 1;
1097
1157
  this.#resolving = false;
1098
- if (this.#resolveErrorCount < 3)
1158
+ if (this.#resolveErrorCount < 3) {
1159
+ debug$1(`Retry ${this.#resolveErrorCount}/3 for block ${hash}`);
1099
1160
  return this.resolveBlock(hash);
1161
+ }
1100
1162
  this.#resolveErrorCount = 0;
1101
1163
  this.wantList.push(hash);
1102
1164
  throw new ResolveError(`block: ${hash}`, { cause: error });
@@ -1297,7 +1359,7 @@ class State extends Contract {
1297
1359
  let node = await globalThis.peernet.prepareMessage(data);
1298
1360
  let message = await peer.request(node.encode());
1299
1361
  message = await new globalThis.peernet.protos['peernet-response'](message);
1300
- this.knownBlocks = message.decoded.response;
1362
+ this.wantList.push(...message.decoded.response);
1301
1363
  }
1302
1364
  }
1303
1365
  return latest;
@@ -53,7 +53,7 @@ export default class State extends Contract {
53
53
  updateState(message: BlockMessage): Promise<void>;
54
54
  getLatestBlock(): Promise<BlockMessage['decoded']>;
55
55
  getAndPutBlock(hash: string): Promise<BlockMessage>;
56
- resolveBlock(hash: any): any;
56
+ resolveBlock(hash: string): Promise<void>;
57
57
  resolveBlocks(): Promise<any>;
58
58
  restoreChain(): any;
59
59
  syncChain(lastBlock?: any): Promise<SyncState>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leofcoin/chain",
3
- "version": "1.7.149",
3
+ "version": "1.7.150",
4
4
  "description": "Official javascript implementation",
5
5
  "private": false,
6
6
  "exports": {