@leofcoin/chain 1.7.152 → 1.7.154

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.
@@ -4189,7 +4189,7 @@ class Machine {
4189
4189
  break;
4190
4190
  }
4191
4191
  case 'debug': {
4192
- debug$2(data.message);
4192
+ // debug(data.message)
4193
4193
  if (data.message.includes('loaded transactions for block:')) {
4194
4194
  pubsub.publish('block-loaded', data.message.replace('loaded transactions for block: ', '').split(' @')[0]);
4195
4195
  }
@@ -4598,6 +4598,36 @@ class Machine {
4598
4598
  }
4599
4599
  }
4600
4600
 
4601
+ class Jobber {
4602
+ constructor(timeout) {
4603
+ this.busy = false;
4604
+ this.timeout = timeout;
4605
+ }
4606
+ add(fn) {
4607
+ this.busy = true;
4608
+ return new Promise(async (resolve, reject) => {
4609
+ const timeout = setTimeout(() => {
4610
+ reject('timeout');
4611
+ }, this.timeout);
4612
+ this.destroy = () => {
4613
+ clearTimeout(timeout);
4614
+ this.busy = false;
4615
+ resolve('stopped');
4616
+ };
4617
+ try {
4618
+ const result = await fn();
4619
+ clearTimeout(timeout);
4620
+ this.busy = false;
4621
+ resolve(result);
4622
+ }
4623
+ catch (error) {
4624
+ clearTimeout(timeout);
4625
+ reject(error);
4626
+ }
4627
+ });
4628
+ }
4629
+ }
4630
+
4601
4631
  const debug$1 = createDebugger('leofcoin/state');
4602
4632
  class State extends Contract {
4603
4633
  #blockHashMap;
@@ -4610,6 +4640,7 @@ class State extends Contract {
4610
4640
  // Block resolution state
4611
4641
  #resolvingBlocks;
4612
4642
  #maxConcurrentResolves;
4643
+ #txConcurrency;
4613
4644
  #totalSize;
4614
4645
  #lastResolved;
4615
4646
  #lastResolvedTime;
@@ -4696,6 +4727,7 @@ class State extends Contract {
4696
4727
  // Block resolution state
4697
4728
  this.#resolvingBlocks = new Set();
4698
4729
  this.#maxConcurrentResolves = 10;
4730
+ this.#txConcurrency = 25;
4699
4731
  this.knownBlocks = [];
4700
4732
  this.#totalSize = 0;
4701
4733
  this._wantList = [];
@@ -4743,6 +4775,8 @@ class State extends Contract {
4743
4775
  #lastBlockHandler;
4744
4776
  #knownBlocksHandler;
4745
4777
  async init() {
4778
+ // Initialize jobber for timed, cancelable tasks
4779
+ this.jobber = new Jobber(this.resolveTimeout);
4746
4780
  // Register request handlers
4747
4781
  await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler.bind(this));
4748
4782
  await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler.bind(this));
@@ -4862,6 +4896,52 @@ class State extends Contract {
4862
4896
  this.#resolvingBlocks.delete(hash);
4863
4897
  }
4864
4898
  }
4899
+ // Small utility to keep event loop responsive
4900
+ async #sleep(ms) {
4901
+ return new Promise((resolve) => setTimeout(resolve, ms));
4902
+ }
4903
+ // Run a list of tasks with bounded concurrency
4904
+ async #runWithConcurrency(items, concurrency, handler) {
4905
+ let index = 0;
4906
+ const total = items.length;
4907
+ const worker = async () => {
4908
+ while (true) {
4909
+ const current = index++;
4910
+ if (current >= total)
4911
+ break;
4912
+ try {
4913
+ await handler(items[current], current);
4914
+ if (current % 50 === 0)
4915
+ debug$1(`executed ${current}/${total}`);
4916
+ }
4917
+ catch (e) {
4918
+ debug$1(`transaction handler error at ${current}: ${e}`);
4919
+ }
4920
+ // yield a tick every few ops to avoid blocking
4921
+ if (current % 25 === 0)
4922
+ await this.#sleep(1);
4923
+ }
4924
+ };
4925
+ const workers = Array.from({ length: Math.min(concurrency, Math.max(1, total)) }, () => worker());
4926
+ await Promise.all(workers);
4927
+ }
4928
+ // Execute a single transaction with a timeout to avoid indefinite hangs
4929
+ async #executeWithTimeout(transaction, timeoutMs) {
4930
+ const timeout = timeoutMs ?? this.resolveTimeout ?? 5000;
4931
+ let timer;
4932
+ try {
4933
+ return await Promise.race([
4934
+ this.#machine.execute(transaction.decoded.to, transaction.decoded.method, transaction.decoded.params),
4935
+ new Promise((_, reject) => {
4936
+ timer = setTimeout(() => reject(new Error('transaction execution timeout')), timeout);
4937
+ })
4938
+ ]);
4939
+ }
4940
+ finally {
4941
+ if (timer)
4942
+ clearTimeout(timer);
4943
+ }
4944
+ }
4865
4945
  async #buildBlockChain(latestHash, maxBlocks = 1000) {
4866
4946
  const chain = [];
4867
4947
  let currentHash = latestHash;
@@ -4924,7 +5004,11 @@ class State extends Contract {
4924
5004
  const blockChain = await this.#buildBlockChain(hash);
4925
5005
  debug$1(`Built chain of ${blockChain.length} blocks`);
4926
5006
  if (blockChain.length > 0) {
4927
- await this.#resolveBlocksInParallel(blockChain);
5007
+ // If a previous resolve job is still running, cancel it
5008
+ if (this.jobber?.busy && this.jobber.destroy)
5009
+ await this.jobber.destroy();
5010
+ // Run the parallel resolution inside a timed jobber task
5011
+ await this.jobber.add(() => this.#resolveBlocksInParallel(blockChain));
4928
5012
  }
4929
5013
  }
4930
5014
  catch (error) {
@@ -4942,7 +5026,17 @@ class State extends Contract {
4942
5026
  const localBlock = await globalThis.chainStore.get('lastBlock');
4943
5027
  const hash = new TextDecoder().decode(localBlock);
4944
5028
  if (hash && hash !== '0x0') {
4945
- await this.resolveBlock(hash);
5029
+ // Cancel any in-flight job before starting a new one
5030
+ if (this.jobber?.busy && this.jobber.destroy)
5031
+ await this.jobber.destroy();
5032
+ // Build chain and resolve in parallel under jobber control
5033
+ const run = async () => {
5034
+ const chain = await this.#buildBlockChain(hash);
5035
+ if (chain.length > 0) {
5036
+ await this.#resolveBlocksInParallel(chain);
5037
+ }
5038
+ };
5039
+ await this.jobber.add(run);
4946
5040
  }
4947
5041
  }
4948
5042
  catch (error) {
@@ -5043,7 +5137,7 @@ class State extends Contract {
5043
5137
  // todo throw error
5044
5138
  async #_executeTransaction(transaction) {
5045
5139
  try {
5046
- await this.#machine.execute(transaction.decoded.to, transaction.decoded.method, transaction.decoded.params);
5140
+ await this.#executeWithTimeout(transaction);
5047
5141
  // await globalThis.accountsStore.put(transaction.decoded.from, String(transaction.decoded.nonce))
5048
5142
  // if (transaction.decoded.to === nativeToken) {
5049
5143
  // this.#nativeCalls += 1
@@ -5109,7 +5203,8 @@ class State extends Contract {
5109
5203
  }
5110
5204
  transactions = transactions.filter((transaction) => !transaction.decoded.priority);
5111
5205
  debug$1(`executing ${transactions.length} transactions for block ${block.index}`);
5112
- await Promise.all(transactions.map((transaction) => this.#_executeTransaction(transaction)));
5206
+ // Concurrency-limited execution to avoid blocking on large blocks
5207
+ await this.#runWithConcurrency(transactions, this.#txConcurrency, async (tx) => this.#_executeTransaction(tx));
5113
5208
  this.#blocks[block.index].loaded = true;
5114
5209
  debug$1(`executed transactions for block ${block.index}`);
5115
5210
  if (Number(block.index) === 0)
package/exports/chain.js CHANGED
@@ -330,7 +330,7 @@ class Machine {
330
330
  break;
331
331
  }
332
332
  case 'debug': {
333
- debug$2(data.message);
333
+ // debug(data.message)
334
334
  if (data.message.includes('loaded transactions for block:')) {
335
335
  pubsub.publish('block-loaded', data.message.replace('loaded transactions for block: ', '').split(' @')[0]);
336
336
  }
@@ -739,6 +739,36 @@ class Machine {
739
739
  }
740
740
  }
741
741
 
742
+ class Jobber {
743
+ constructor(timeout) {
744
+ this.busy = false;
745
+ this.timeout = timeout;
746
+ }
747
+ add(fn) {
748
+ this.busy = true;
749
+ return new Promise(async (resolve, reject) => {
750
+ const timeout = setTimeout(() => {
751
+ reject('timeout');
752
+ }, this.timeout);
753
+ this.destroy = () => {
754
+ clearTimeout(timeout);
755
+ this.busy = false;
756
+ resolve('stopped');
757
+ };
758
+ try {
759
+ const result = await fn();
760
+ clearTimeout(timeout);
761
+ this.busy = false;
762
+ resolve(result);
763
+ }
764
+ catch (error) {
765
+ clearTimeout(timeout);
766
+ reject(error);
767
+ }
768
+ });
769
+ }
770
+ }
771
+
742
772
  const debug$1 = createDebugger('leofcoin/state');
743
773
  class State extends Contract {
744
774
  #blockHashMap;
@@ -751,6 +781,7 @@ class State extends Contract {
751
781
  // Block resolution state
752
782
  #resolvingBlocks;
753
783
  #maxConcurrentResolves;
784
+ #txConcurrency;
754
785
  #totalSize;
755
786
  #lastResolved;
756
787
  #lastResolvedTime;
@@ -837,6 +868,7 @@ class State extends Contract {
837
868
  // Block resolution state
838
869
  this.#resolvingBlocks = new Set();
839
870
  this.#maxConcurrentResolves = 10;
871
+ this.#txConcurrency = 25;
840
872
  this.knownBlocks = [];
841
873
  this.#totalSize = 0;
842
874
  this._wantList = [];
@@ -884,6 +916,8 @@ class State extends Contract {
884
916
  #lastBlockHandler;
885
917
  #knownBlocksHandler;
886
918
  async init() {
919
+ // Initialize jobber for timed, cancelable tasks
920
+ this.jobber = new Jobber(this.resolveTimeout);
887
921
  // Register request handlers
888
922
  await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler.bind(this));
889
923
  await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler.bind(this));
@@ -1003,6 +1037,52 @@ class State extends Contract {
1003
1037
  this.#resolvingBlocks.delete(hash);
1004
1038
  }
1005
1039
  }
1040
+ // Small utility to keep event loop responsive
1041
+ async #sleep(ms) {
1042
+ return new Promise((resolve) => setTimeout(resolve, ms));
1043
+ }
1044
+ // Run a list of tasks with bounded concurrency
1045
+ async #runWithConcurrency(items, concurrency, handler) {
1046
+ let index = 0;
1047
+ const total = items.length;
1048
+ const worker = async () => {
1049
+ while (true) {
1050
+ const current = index++;
1051
+ if (current >= total)
1052
+ break;
1053
+ try {
1054
+ await handler(items[current], current);
1055
+ if (current % 50 === 0)
1056
+ debug$1(`executed ${current}/${total}`);
1057
+ }
1058
+ catch (e) {
1059
+ debug$1(`transaction handler error at ${current}: ${e}`);
1060
+ }
1061
+ // yield a tick every few ops to avoid blocking
1062
+ if (current % 25 === 0)
1063
+ await this.#sleep(1);
1064
+ }
1065
+ };
1066
+ const workers = Array.from({ length: Math.min(concurrency, Math.max(1, total)) }, () => worker());
1067
+ await Promise.all(workers);
1068
+ }
1069
+ // Execute a single transaction with a timeout to avoid indefinite hangs
1070
+ async #executeWithTimeout(transaction, timeoutMs) {
1071
+ const timeout = timeoutMs ?? this.resolveTimeout ?? 5000;
1072
+ let timer;
1073
+ try {
1074
+ return await Promise.race([
1075
+ this.#machine.execute(transaction.decoded.to, transaction.decoded.method, transaction.decoded.params),
1076
+ new Promise((_, reject) => {
1077
+ timer = setTimeout(() => reject(new Error('transaction execution timeout')), timeout);
1078
+ })
1079
+ ]);
1080
+ }
1081
+ finally {
1082
+ if (timer)
1083
+ clearTimeout(timer);
1084
+ }
1085
+ }
1006
1086
  async #buildBlockChain(latestHash, maxBlocks = 1000) {
1007
1087
  const chain = [];
1008
1088
  let currentHash = latestHash;
@@ -1065,7 +1145,11 @@ class State extends Contract {
1065
1145
  const blockChain = await this.#buildBlockChain(hash);
1066
1146
  debug$1(`Built chain of ${blockChain.length} blocks`);
1067
1147
  if (blockChain.length > 0) {
1068
- await this.#resolveBlocksInParallel(blockChain);
1148
+ // If a previous resolve job is still running, cancel it
1149
+ if (this.jobber?.busy && this.jobber.destroy)
1150
+ await this.jobber.destroy();
1151
+ // Run the parallel resolution inside a timed jobber task
1152
+ await this.jobber.add(() => this.#resolveBlocksInParallel(blockChain));
1069
1153
  }
1070
1154
  }
1071
1155
  catch (error) {
@@ -1083,7 +1167,17 @@ class State extends Contract {
1083
1167
  const localBlock = await globalThis.chainStore.get('lastBlock');
1084
1168
  const hash = new TextDecoder().decode(localBlock);
1085
1169
  if (hash && hash !== '0x0') {
1086
- await this.resolveBlock(hash);
1170
+ // Cancel any in-flight job before starting a new one
1171
+ if (this.jobber?.busy && this.jobber.destroy)
1172
+ await this.jobber.destroy();
1173
+ // Build chain and resolve in parallel under jobber control
1174
+ const run = async () => {
1175
+ const chain = await this.#buildBlockChain(hash);
1176
+ if (chain.length > 0) {
1177
+ await this.#resolveBlocksInParallel(chain);
1178
+ }
1179
+ };
1180
+ await this.jobber.add(run);
1087
1181
  }
1088
1182
  }
1089
1183
  catch (error) {
@@ -1184,7 +1278,7 @@ class State extends Contract {
1184
1278
  // todo throw error
1185
1279
  async #_executeTransaction(transaction) {
1186
1280
  try {
1187
- await this.#machine.execute(transaction.decoded.to, transaction.decoded.method, transaction.decoded.params);
1281
+ await this.#executeWithTimeout(transaction);
1188
1282
  // await globalThis.accountsStore.put(transaction.decoded.from, String(transaction.decoded.nonce))
1189
1283
  // if (transaction.decoded.to === nativeToken) {
1190
1284
  // this.#nativeCalls += 1
@@ -1250,7 +1344,8 @@ class State extends Contract {
1250
1344
  }
1251
1345
  transactions = transactions.filter((transaction) => !transaction.decoded.priority);
1252
1346
  debug$1(`executing ${transactions.length} transactions for block ${block.index}`);
1253
- await Promise.all(transactions.map((transaction) => this.#_executeTransaction(transaction)));
1347
+ // Concurrency-limited execution to avoid blocking on large blocks
1348
+ await this.#runWithConcurrency(transactions, this.#txConcurrency, async (tx) => this.#_executeTransaction(tx));
1254
1349
  this.#blocks[block.index].loaded = true;
1255
1350
  debug$1(`executed transactions for block ${block.index}`);
1256
1351
  if (Number(block.index) === 0)
@@ -1,9 +1,11 @@
1
1
  import { BlockMessage } from '@leofcoin/messages';
2
2
  import Contract from './contract.js';
3
3
  import Machine from './machine.js';
4
+ import Jobber from './jobs/jobber.js';
4
5
  import { BlockHash } from './types.js';
5
6
  export default class State extends Contract {
6
7
  #private;
8
+ jobber: Jobber;
7
9
  knownBlocks: BlockHash[];
8
10
  _wantList: any[];
9
11
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leofcoin/chain",
3
- "version": "1.7.152",
3
+ "version": "1.7.154",
4
4
  "description": "Official javascript implementation",
5
5
  "private": false,
6
6
  "exports": {