@leofcoin/chain 1.8.3 → 1.8.5

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/chain.js CHANGED
@@ -7,9 +7,11 @@ import semver from 'semver';
7
7
  import { randombytes } from '@leofcoin/crypto';
8
8
  import EasyWorker from '@vandeurenglenn/easy-worker';
9
9
  import { ContractDeploymentError, ExecutionError, isResolveError, ResolveError, isExecutionError } from '@leofcoin/errors';
10
+ import { log } from 'console';
11
+ import { log as log$1 } from 'node:console';
10
12
 
11
13
  const limit = 1800;
12
- const transactionLimit = 1000;
14
+ const transactionLimit = 2500;
13
15
  const requestTimeout = 30_000;
14
16
  const syncTimeout = 30_000;
15
17
  class Protocol {
@@ -90,12 +92,16 @@ class Transaction extends Protocol {
90
92
  // @ts-ignore
91
93
  if (this.lastBlock?.hash && transactions.length === 0 && this.lastBlock.hash !== '0x0') {
92
94
  // @ts-ignore
93
- let block = await peernet.get(this.lastBlock.hash, 'block');
95
+ let block;
96
+ try {
97
+ block = await globalThis.peernet.get(this.lastBlock.hash, 'block');
98
+ }
99
+ catch (error) {
100
+ block = undefined;
101
+ }
102
+ if (block === undefined)
103
+ return []; // Fallback if block unavailable
94
104
  block = await new BlockMessage(block);
95
- // for (let tx of block.decoded?.transactions) {
96
- // tx = await peernet.get(tx, 'transaction')
97
- // transactions.push(new TransactionMessage(tx))
98
- // }
99
105
  transactions = transactions.filter((tx) => tx.from === address);
100
106
  while (transactions.length === 0 && block.decoded.index !== 0 && block.decoded.previousHash !== '0x0') {
101
107
  block = await globalThis.blockStore.get(block.decoded.previousHash);
@@ -105,8 +111,13 @@ class Transaction extends Protocol {
105
111
  }
106
112
  if (transactions.length === 0)
107
113
  return 0;
108
- transactions = transactions.sort((a, b) => a.timestamp - b.timestamp);
109
- return transactions[transactions.length - 1].nonce;
114
+ // Optimize: find max nonce instead of sorting entire array
115
+ let maxNonce = 0;
116
+ for (const tx of transactions) {
117
+ if (tx.nonce > maxNonce)
118
+ maxNonce = tx.nonce;
119
+ }
120
+ return maxNonce;
110
121
  }
111
122
  /**
112
123
  * Get amount of transactions by address
@@ -114,6 +125,8 @@ class Transaction extends Protocol {
114
125
  * @returns {Number} nonce
115
126
  */
116
127
  async getNonce(address) {
128
+ // DO NOT use nonce cache here - multiple parallel calls could create race conditions
129
+ // Instead, optimize the store queries and pool filtering
117
130
  try {
118
131
  if (!(await globalThis.accountsStore.has(address))) {
119
132
  const nonce = await this.#getNonceFallback(address);
@@ -215,6 +228,235 @@ class Transaction extends Protocol {
215
228
  }
216
229
  }
217
230
 
231
+ /**
232
+ * Utility functions for contract inheritance and code reuse
233
+ */
234
+ /**
235
+ * Check if a class is derived from another class
236
+ * @param derivedClass The class to check
237
+ * @param baseClass The potential base class
238
+ * @returns true if derivedClass extends baseClass
239
+ */
240
+ const isDerivedFrom = (derivedClass, baseClass) => {
241
+ if (!derivedClass || !baseClass)
242
+ return false;
243
+ let proto = Object.getPrototypeOf(derivedClass);
244
+ while (proto) {
245
+ if (proto === baseClass)
246
+ return true;
247
+ proto = Object.getPrototypeOf(proto);
248
+ }
249
+ return false;
250
+ };
251
+ /**
252
+ * Parse contract inheritance information from contract code
253
+ * @param contractCode The contract code to parse
254
+ * @returns Information about the contract's inheritance
255
+ */
256
+ const parseContractInheritance = (contractCode) => {
257
+ const classMatch = contractCode.match(/class\s+(\w+)(?:\s+extends\s+(\w+))?/);
258
+ if (!classMatch) {
259
+ return { className: null, baseClass: null, hasInheritance: false };
260
+ }
261
+ return {
262
+ className: classMatch[1],
263
+ baseClass: classMatch[2] || null,
264
+ hasInheritance: !!classMatch[2]
265
+ };
266
+ };
267
+
268
+ /**
269
+ * Registry for managing base contracts and contract inheritance
270
+ */
271
+ class ContractRegistry {
272
+ constructor() {
273
+ this.baseContracts = new Map(); // hash -> ContractMessage
274
+ this.contractNames = new Map(); // name -> hash
275
+ this.contractDependencies = new Map();
276
+ }
277
+ /**
278
+ * Register a base contract that can be reused
279
+ * Uses the contract code hash as the identifier and registers the name in nameService
280
+ * @param name The name to register in nameService
281
+ * @param message The contract message
282
+ * @returns The contract hash
283
+ */
284
+ async registerBaseContract(name, message) {
285
+ const hash = await message.hash();
286
+ // Store by hash (the actual identifier)
287
+ this.baseContracts.set(hash, message);
288
+ this.contractNames.set(name, hash);
289
+ // Store in contract store
290
+ if (globalThis.contractStore) {
291
+ await globalThis.contractStore.put(hash, message.encoded);
292
+ }
293
+ return hash;
294
+ }
295
+ /**
296
+ * Register a name for a contract hash in the nameService
297
+ * @param name The name to register
298
+ * @param hash The contract hash
299
+ * @param chain The chain instance (for making transactions)
300
+ * @param signer The wallet to sign the transaction
301
+ */
302
+ async registerNameInNameService(name, hash, chain, signer) {
303
+ // Register the name -> hash mapping in the on-chain nameService contract
304
+ ({
305
+ from: await signer.address,
306
+ to: addresses.nameService});
307
+ // This would need to be signed and sent through the chain
308
+ // Implementation depends on your chain's transaction flow
309
+ }
310
+ /**
311
+ * Get a base contract by name
312
+ * First checks local registry, then queries nameService if available
313
+ * @param name The name of the base contract
314
+ * @returns The contract message or undefined
315
+ */
316
+ getBaseContract(name) {
317
+ // Check local name mapping
318
+ const hash = this.contractNames.get(name);
319
+ if (hash) {
320
+ return this.baseContracts.get(hash);
321
+ }
322
+ // If not found locally, try to lookup in nameService
323
+ // This would require access to the chain instance to call nameService
324
+ return undefined;
325
+ }
326
+ /**
327
+ * Get a base contract by hash
328
+ * @param hash The hash of the contract
329
+ * @returns The contract message or undefined
330
+ */
331
+ getBaseContractByHash(hash) {
332
+ return this.baseContracts.get(hash);
333
+ }
334
+ /**
335
+ * Resolve a name to a hash (first local, then nameService)
336
+ * @param name The contract name
337
+ * @returns The contract hash or undefined
338
+ */
339
+ resolveNameToHash(name) {
340
+ return this.contractNames.get(name);
341
+ }
342
+ /**
343
+ * Register contract dependencies (inheritance chain)
344
+ * @param contractHash The hash of the contract
345
+ * @param dependencies Array of base contract hashes this contract depends on
346
+ */
347
+ registerDependencies(contractHash, dependencies) {
348
+ this.contractDependencies.set(contractHash, dependencies);
349
+ }
350
+ /**
351
+ * Get all dependencies for a contract
352
+ * @param contractHash The hash of the contract
353
+ * @returns Array of dependency hashes
354
+ */
355
+ getDependencies(contractHash) {
356
+ return this.contractDependencies.get(contractHash) || [];
357
+ }
358
+ /**
359
+ * Check if all dependencies for a contract are available
360
+ * @param contractHash The hash of the contract
361
+ * @returns true if all dependencies are available
362
+ */
363
+ async areDependenciesAvailable(contractHash) {
364
+ const dependencies = this.getDependencies(contractHash);
365
+ if (dependencies.length === 0)
366
+ return true;
367
+ for (const depHash of dependencies) {
368
+ try {
369
+ if (globalThis.contractStore) {
370
+ await globalThis.contractStore.get(depHash);
371
+ }
372
+ else {
373
+ return false;
374
+ }
375
+ }
376
+ catch {
377
+ return false;
378
+ }
379
+ }
380
+ return true;
381
+ }
382
+ /**
383
+ * Get all registered contract hashes
384
+ * @returns Array of contract hashes
385
+ */
386
+ getBaseContractHashes() {
387
+ return Array.from(this.baseContracts.keys());
388
+ }
389
+ /**
390
+ * Get hash for a registered name
391
+ * @param name The contract name
392
+ * @returns The hash or undefined
393
+ */
394
+ getHashForName(name) {
395
+ return this.contractNames.get(name);
396
+ }
397
+ /**
398
+ * Analyze contract code and extract inheritance information
399
+ * @param contractCode The contract code to analyze
400
+ * @returns Inheritance information
401
+ */
402
+ analyzeContract(contractCode) {
403
+ return parseContractInheritance(contractCode);
404
+ }
405
+ /**
406
+ * Build a complete contract by combining base contracts
407
+ * @param contractCode The derived contract code
408
+ * @param baseContractIdentifiers Names or hashes of base contracts to include
409
+ * @returns Combined contract code
410
+ */
411
+ async buildContract(contractCode, baseContractIdentifiers = []) {
412
+ let combinedCode = '';
413
+ // Add base contracts first
414
+ for (const identifier of baseContractIdentifiers) {
415
+ // Try as name first, then as hash
416
+ let baseContract = await this.getBaseContract(identifier);
417
+ if (!baseContract) {
418
+ baseContract = this.getBaseContractByHash(identifier);
419
+ }
420
+ if (baseContract) {
421
+ const decoded = baseContract.decoded;
422
+ combinedCode += `// Base contract: ${identifier}\n`;
423
+ combinedCode += decoded.contract + '\n\n';
424
+ }
425
+ }
426
+ // Add the derived contract
427
+ combinedCode += contractCode;
428
+ return combinedCode;
429
+ }
430
+ /**
431
+ * Clear all registered contracts
432
+ */
433
+ clear() {
434
+ this.baseContracts.clear();
435
+ this.contractDependencies.clear();
436
+ }
437
+ /**
438
+ * Get all registered base contract names
439
+ * @returns Array of base contract names
440
+ */
441
+ getBaseContractNames() {
442
+ return Array.from(this.contractNames.keys());
443
+ }
444
+ /**
445
+ * Get name for a hash if registered
446
+ * @param hash The contract hash
447
+ * @returns The name or undefined
448
+ */
449
+ getNameForHash(hash) {
450
+ for (const [name, h] of this.contractNames.entries()) {
451
+ if (h === hash)
452
+ return name;
453
+ }
454
+ return undefined;
455
+ }
456
+ }
457
+ // Export a singleton instance
458
+ const contractRegistry = new ContractRegistry();
459
+
218
460
  /**
219
461
  * @extends {Transaction}
220
462
  */
@@ -253,6 +495,58 @@ class Contract extends Transaction {
253
495
  const message = await createContractMessage(await signer.address, contract, constructorParameters);
254
496
  return this.deployContractMessage(signer, message);
255
497
  }
498
+ /**
499
+ * Register a base contract for reuse by other contracts
500
+ * The contract is stored by its hash, enabling inheritance and code reuse
501
+ * @param {String} name The name to register for this contract
502
+ * @param {String} contract The contract code
503
+ * @param {Array} constructorParameters Constructor parameters
504
+ * @returns {Promise<string>} The contract hash
505
+ */
506
+ async registerBaseContract(name, contract, constructorParameters = []) {
507
+ const creator = addresses.contractFactory;
508
+ // Base contracts are hashed without constructor parameters
509
+ const message = await createContractMessage(creator, contract, []);
510
+ const hash = await contractRegistry.registerBaseContract(name, message);
511
+ // Store the contract by its hash
512
+ try {
513
+ await globalThis.contractStore.put(hash, message.encoded);
514
+ }
515
+ catch (error) {
516
+ throw error;
517
+ }
518
+ return hash;
519
+ }
520
+ /**
521
+ * Deploy a contract with optional base contracts for inheritance
522
+ * @param {MultiWallet} signer The wallet to sign with
523
+ * @param {String} contract The contract code
524
+ * @param {Array} constructorParameters Constructor parameters
525
+ * @param {Array<string>} baseContracts Optional array of base contract names or hashes
526
+ * @returns Transaction result
527
+ */
528
+ async deployDerivedContract(signer, contract, baseContractIdentifier, constructorParameters = []) {
529
+ // Try to get base contract by name or hash
530
+ if (baseContractIdentifier) {
531
+ const baseContract = contractRegistry.getBaseContract(baseContractIdentifier);
532
+ if (!baseContract) {
533
+ throw new Error(`Base contract '${baseContractIdentifier}' not found. Register it first using registerBaseContract.`);
534
+ }
535
+ }
536
+ // Parse contract to check for inheritance
537
+ const inheritanceInfo = parseContractInheritance(contract);
538
+ // Deploy the contract
539
+ const message = await createContractMessage(await signer.address, contract, constructorParameters);
540
+ // If contract has dependencies, register them
541
+ if (inheritanceInfo.hasInheritance && inheritanceInfo.baseClass && baseContractIdentifier) {
542
+ const baseHash = contractRegistry.getHashForName(inheritanceInfo.baseClass);
543
+ if (baseHash) {
544
+ const contractHash = await message.hash();
545
+ contractRegistry.registerDependencies(contractHash, [baseHash]);
546
+ }
547
+ }
548
+ return this.deployContractMessage(signer, message);
549
+ }
256
550
  async deployContractMessage(signer, message) {
257
551
  try {
258
552
  await globalThis.contractStore.put(await message.hash(), message.encoded);
@@ -269,6 +563,30 @@ class Contract extends Transaction {
269
563
  transaction = await signTransaction(await this.createTransaction(transaction), signer);
270
564
  return this.sendTransaction(transaction);
271
565
  }
566
+ /**
567
+ * Check if a class is derived from another class
568
+ * @param derivedClass The class to check
569
+ * @param baseClass The potential base class
570
+ * @returns true if derivedClass extends baseClass
571
+ */
572
+ isDerivedFrom(derivedClass, baseClass) {
573
+ return isDerivedFrom(derivedClass, baseClass);
574
+ }
575
+ /**
576
+ * Parse contract code to extract inheritance information
577
+ * @param contractCode The contract code to parse
578
+ * @returns Inheritance information
579
+ */
580
+ parseContractInheritance(contractCode) {
581
+ return parseContractInheritance(contractCode);
582
+ }
583
+ /**
584
+ * Get the contract registry instance
585
+ * @returns {ContractRegistry} The contract registry
586
+ */
587
+ getRegistry() {
588
+ return contractRegistry;
589
+ }
272
590
  }
273
591
 
274
592
  // import State from './state'
@@ -295,6 +613,12 @@ class Machine {
295
613
  }
296
614
  };
297
615
  this.wantList = [];
616
+ this.ready = new Promise((resolve) => {
617
+ this.readyResolve = resolve;
618
+ });
619
+ this.init(blocks);
620
+ }
621
+ init(blocks) {
298
622
  // @ts-ignore
299
623
  return this.#init(blocks);
300
624
  }
@@ -354,8 +678,14 @@ class Machine {
354
678
  case 'ask': {
355
679
  if (data.question === 'contract' || data.question === 'transaction') {
356
680
  try {
357
- const input = await peernet.get(data.input);
358
- this.worker.postMessage({ id: data.id, input });
681
+ const input = await peernet.get(data.input, data.question);
682
+ if (input === undefined) {
683
+ this.worker.postMessage({ id: data.id, error: `could not get ${data.question} @${data.input}` });
684
+ this.wantList.push(data.input);
685
+ }
686
+ else {
687
+ this.worker.postMessage({ id: data.id, input });
688
+ }
359
689
  }
360
690
  catch (error) {
361
691
  console.error(error);
@@ -530,6 +860,7 @@ class Machine {
530
860
  }
531
861
  };
532
862
  this.worker.postMessage(message);
863
+ this.readyResolve(this);
533
864
  });
534
865
  }
535
866
  async #runContract(contractMessage) {
@@ -571,6 +902,8 @@ class Machine {
571
902
  let message;
572
903
  if (!(await globalThis.contractStore.has(parameters[0]))) {
573
904
  message = await peernet.get(parameters[0], 'contract');
905
+ if (!message)
906
+ throw new Error(`contract ${parameters[0]} not available`);
574
907
  message = await new ContractMessage(message);
575
908
  await globalThis.contractStore.put(await message.hash(), message.encoded);
576
909
  }
@@ -588,7 +921,12 @@ class Machine {
588
921
  return new Promise((resolve, reject) => {
589
922
  // @ts-ignore
590
923
  const id = randombytes(20).toString('hex');
924
+ const timeout = setTimeout(() => {
925
+ pubsub.unsubscribe(id, onmessage);
926
+ reject(new Error(`Machine.execute timeout for ${contract}.${method}`));
927
+ }, 30000); // 30 second timeout for machine operations
591
928
  const onmessage = (message) => {
929
+ clearTimeout(timeout);
592
930
  pubsub.unsubscribe(id, onmessage);
593
931
  if (message?.error)
594
932
  reject(new ExecutionError(message.error));
@@ -611,7 +949,12 @@ class Machine {
611
949
  get(contract, method, parameters) {
612
950
  return new Promise((resolve, reject) => {
613
951
  const id = randombytes(20).toString();
952
+ const timeout = setTimeout(() => {
953
+ pubsub.unsubscribe(id, onmessage);
954
+ reject(new Error(`Machine.get timeout for ${contract}.${method}`));
955
+ }, 30000); // 30 second timeout for machine operations
614
956
  const onmessage = (message) => {
957
+ clearTimeout(timeout);
615
958
  pubsub.unsubscribe(id, onmessage);
616
959
  resolve(message);
617
960
  };
@@ -631,7 +974,12 @@ class Machine {
631
974
  return new Promise((resolve, reject) => {
632
975
  // @ts-ignore
633
976
  const id = randombytes(20).toString('hex');
977
+ const timeout = setTimeout(() => {
978
+ pubsub.unsubscribe(id, onmessage);
979
+ reject(new Error(`Machine.has timeout for ${address}`));
980
+ }, 10000); // 10 second timeout
634
981
  const onmessage = (message) => {
982
+ clearTimeout(timeout);
635
983
  pubsub.unsubscribe(id, onmessage);
636
984
  if (message?.error)
637
985
  reject(message.error);
@@ -652,7 +1000,12 @@ class Machine {
652
1000
  return new Promise((resolve, reject) => {
653
1001
  // @ts-ignore
654
1002
  const id = randombytes(20).toString('hex');
1003
+ const timeout = setTimeout(() => {
1004
+ pubsub.unsubscribe(id, onmessage);
1005
+ reject(new Error(`Machine.#askWorker timeout for ${type}`));
1006
+ }, 30000);
655
1007
  const onmessage = (message) => {
1008
+ clearTimeout(timeout);
656
1009
  pubsub.unsubscribe(id, onmessage);
657
1010
  if (message?.error)
658
1011
  reject(message.error);
@@ -717,7 +1070,6 @@ class Machine {
717
1070
  }
718
1071
  async addLoadedBlock(block) {
719
1072
  debug$2(`adding loaded block: ${block.index}@${block.hash}`);
720
- debug$2(JSON.stringify(block, jsonStringifyBigInt));
721
1073
  if (block.decoded)
722
1074
  block = { ...block.decoded, hash: await block.hash() };
723
1075
  return this.#askWorker('addLoadedBlock', JSON.stringify(block, jsonStringifyBigInt));
@@ -890,7 +1242,7 @@ class State extends Contract {
890
1242
  response: { blocks: await globalThis.blockStore.keys() }
891
1243
  });
892
1244
  };
893
- this.#loadBlockTransactions = (transactions) => Promise.all(transactions.map(async (transaction) => new TransactionMessage(await peernet.get(transaction))));
1245
+ this.#loadBlockTransactions = (transactions) => Promise.all(transactions.map(async (transaction) => new TransactionMessage(await peernet.get(transaction, 'transaction'))));
894
1246
  this.#getLastTransactions = async () => {
895
1247
  let lastTransactions = (await Promise.all((await this.blocks)
896
1248
  // @ts-ignore
@@ -919,6 +1271,7 @@ class State extends Contract {
919
1271
  #lastBlockHandler;
920
1272
  #knownBlocksHandler;
921
1273
  async init() {
1274
+ log('State init start');
922
1275
  this.jobber = new Jobber(this.resolveTimeout);
923
1276
  await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler);
924
1277
  await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler);
@@ -926,31 +1279,42 @@ class State extends Contract {
926
1279
  let localBlockHash;
927
1280
  let blockMessage;
928
1281
  let localBlock;
1282
+ log('State init before try-catch');
929
1283
  try {
930
1284
  const rawBlock = await globalThis.chainStore.has('lastBlock');
1285
+ log('State init after has lastBlock check');
931
1286
  if (rawBlock) {
1287
+ log('State init after has lastBlock found');
1288
+ log(rawBlock);
932
1289
  localBlockHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
1290
+ console.log(localBlockHash);
933
1291
  if (localBlockHash !== '0x0') {
934
- blockMessage = await globalThis.peernet.get(localBlockHash, 'block');
1292
+ blockMessage = await globalThis.blockStore.get(localBlockHash);
1293
+ console.log(blockMessage);
935
1294
  blockMessage = await new BlockMessage(blockMessage);
936
1295
  localBlock = { ...blockMessage.decoded, hash: localBlockHash };
937
1296
  }
1297
+ log('State init after localBlock set');
938
1298
  }
939
1299
  else {
940
1300
  localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
941
1301
  }
942
1302
  }
943
1303
  catch {
1304
+ console.log('e');
1305
+ log('State init middle');
944
1306
  localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
945
1307
  }
1308
+ log('State init middle');
946
1309
  try {
1310
+ log('fetching known blocks from blockStore');
947
1311
  this.knownBlocks = await blockStore.keys();
948
1312
  }
949
1313
  catch (error) {
950
1314
  debug$1('no local known blocks found');
951
1315
  }
952
1316
  try {
953
- if (localBlock.hash && localBlock.hash !== '0x0') {
1317
+ if (localBlock?.hash && localBlock.hash !== '0x0') {
954
1318
  try {
955
1319
  const states = {
956
1320
  lastBlock: JSON.parse(new TextDecoder().decode(await globalThis.stateStore.get('lastBlock')))
@@ -966,7 +1330,10 @@ class State extends Contract {
966
1330
  else {
967
1331
  await this.resolveBlocks();
968
1332
  }
969
- this.#machine = await new Machine(this.#blocks);
1333
+ const machine = new Machine(this.#blocks);
1334
+ console.log(machine);
1335
+ await machine.ready;
1336
+ this.#machine = machine;
970
1337
  const lastBlock = await this.#machine.lastBlock;
971
1338
  if (lastBlock.hash !== '0x0') {
972
1339
  this.updateState(new BlockMessage(lastBlock));
@@ -987,8 +1354,12 @@ class State extends Contract {
987
1354
  const hash = await message.hash();
988
1355
  await globalThis.chainStore.put('lastBlock', hash);
989
1356
  globalThis.pubsub.publish('lastBlock', message.encoded);
990
- if (!this.#machine)
991
- this.#machine = await new Machine(this.#blocks);
1357
+ if (!this.#machine) {
1358
+ const machine = new Machine(this.#blocks);
1359
+ console.log(machine);
1360
+ await machine.ready;
1361
+ this.#machine = machine;
1362
+ }
992
1363
  await this.#machine.updateState();
993
1364
  }
994
1365
  catch (error) {
@@ -1452,7 +1823,10 @@ class State extends Contract {
1452
1823
  }
1453
1824
  async triggerLoad() {
1454
1825
  if (this.#blocks?.length > 0) {
1455
- this.#machine = await new Machine(this.#blocks);
1826
+ const machine = new Machine(this.#blocks);
1827
+ console.log(machine);
1828
+ await machine.ready;
1829
+ this.#machine = machine;
1456
1830
  }
1457
1831
  }
1458
1832
  }
@@ -1848,6 +2222,7 @@ const debug = createDebugger('leofcoin/chain');
1848
2222
  class Chain extends VersionControl {
1849
2223
  #state;
1850
2224
  #slotTime;
2225
+ #blockTime; // 6 second target block time
1851
2226
  /** {Address[]} */
1852
2227
  #validators;
1853
2228
  /** {Boolean} */
@@ -1862,6 +2237,7 @@ class Chain extends VersionControl {
1862
2237
  constructor(config) {
1863
2238
  super(config);
1864
2239
  this.#slotTime = 10000;
2240
+ this.#blockTime = 6000; // 6 second target block time
1865
2241
  this.utils = {};
1866
2242
  /** {Address[]} */
1867
2243
  this.#validators = [];
@@ -1873,14 +2249,16 @@ class Chain extends VersionControl {
1873
2249
  this.#peerConnectionRetries = new Map();
1874
2250
  this.#maxPeerRetries = 5;
1875
2251
  this.#peerRetryDelay = 5000;
2252
+ this.ready = new Promise((resolve) => {
2253
+ this.readyResolve = resolve;
2254
+ });
1876
2255
  this.#addTransaction = async (message) => {
1877
2256
  const transaction = new TransactionMessage(message);
1878
2257
  const hash = await transaction.hash();
1879
2258
  // if (await transactionPoolStore.has(hash)) await transactionPoolStore.delete(hash)
1880
2259
  debug(`added ${hash}`);
1881
2260
  };
1882
- // @ts-ignore
1883
- return this.#init();
2261
+ this.#init();
1884
2262
  }
1885
2263
  get nativeToken() {
1886
2264
  return addresses.nativeToken;
@@ -1894,13 +2272,20 @@ class Chain extends VersionControl {
1894
2272
  return true;
1895
2273
  return false;
1896
2274
  }
2275
+ #sleep(ms) {
2276
+ return new Promise((resolve) => setTimeout(resolve, ms));
2277
+ }
1897
2278
  async #runEpoch() {
2279
+ if (this.#runningEpoch)
2280
+ return;
1898
2281
  this.#runningEpoch = true;
1899
2282
  console.log('epoch');
1900
2283
  const validators = await this.staticCall(addresses.validators, 'validators');
1901
2284
  console.log({ validators });
1902
- if (!validators.includes(peernet.selectedAccount))
2285
+ if (!validators.includes(peernet.selectedAccount)) {
2286
+ this.#runningEpoch = false;
1903
2287
  return;
2288
+ }
1904
2289
  const start = Date.now();
1905
2290
  try {
1906
2291
  await this.#createBlock();
@@ -1910,10 +2295,16 @@ class Chain extends VersionControl {
1910
2295
  }
1911
2296
  const end = Date.now();
1912
2297
  console.log((end - start) / 1000 + ' s');
1913
- if (await this.hasTransactionToHandle())
1914
- return this.#runEpoch();
2298
+ // enforce target block time to avoid tight loops
2299
+ const elapsed = end - start;
2300
+ const remaining = this.#blockTime - elapsed;
2301
+ const hasMore = await this.hasTransactionToHandle();
2302
+ // Only delay if there's no backlog; if backlog exists, continue immediately
2303
+ if (!hasMore && remaining > 0)
2304
+ await this.#sleep(remaining);
1915
2305
  this.#runningEpoch = false;
1916
- // if (await this.hasTransactionToHandle() && !this.#runningEpoch) return this.#runEpoch()
2306
+ if (hasMore)
2307
+ return this.#runEpoch();
1917
2308
  }
1918
2309
  async #setup() {
1919
2310
  const contracts = [
@@ -1948,13 +2339,17 @@ class Chain extends VersionControl {
1948
2339
  this.#participants = [];
1949
2340
  this.#participating = false;
1950
2341
  this.#connectionMonitor = new ConnectionMonitor();
2342
+ log$1('[chain] init:start');
1951
2343
  const initialized = await globalThis.contractStore.has(addresses.contractFactory);
2344
+ log$1(`chain initialized: ${initialized}`);
1952
2345
  if (!initialized)
1953
2346
  await this.#setup();
1954
2347
  this.utils = { formatUnits, parseUnits };
1955
2348
  // this.#state = new State()
2349
+ console.log('init');
1956
2350
  // todo some functions rely on state
1957
2351
  await super.init();
2352
+ log$1('super init done');
1958
2353
  // Start connection monitoring
1959
2354
  this.#connectionMonitor.start(this.version);
1960
2355
  await globalThis.peernet.addRequestHandler('bw-request-message', () => {
@@ -1977,7 +2372,8 @@ class Chain extends VersionControl {
1977
2372
  globalThis.peernet.subscribe('validator:timeout', this.#validatorTimeout.bind(this));
1978
2373
  globalThis.pubsub.subscribe('peer:connected', this.#peerConnected.bind(this));
1979
2374
  globalThis.pubsub.publish('chain:ready', true);
1980
- return this;
2375
+ console.log('[chain] init:done');
2376
+ this.readyResolve(true);
1981
2377
  }
1982
2378
  async #invalidTransaction(hash) {
1983
2379
  hash = new TextDecoder().decode(hash);
@@ -2011,8 +2407,16 @@ class Chain extends VersionControl {
2011
2407
  const transactions = await globalThis.transactionPoolStore.keys();
2012
2408
  const transactionsToGet = [];
2013
2409
  for (const key of transactionsInPool) {
2014
- !transactions.includes(key) &&
2015
- transactionsToGet.push(transactionPoolStore.put(key, await peernet.get(key, 'transaction')));
2410
+ let txData;
2411
+ try {
2412
+ txData = await globalThis.peernet.get(key, 'transaction');
2413
+ }
2414
+ catch (error) {
2415
+ debug(`Failed to get transaction ${key}:`, error?.message ?? error);
2416
+ }
2417
+ if (txData !== undefined && !transactions.includes(key)) {
2418
+ transactionsToGet.push(transactionPoolStore.put(key, txData));
2419
+ }
2016
2420
  }
2017
2421
  return Promise.all(transactionsToGet);
2018
2422
  }
@@ -2178,23 +2582,29 @@ class Chain extends VersionControl {
2178
2582
  // peerReputation(peerId)
2179
2583
  // {bandwith: {up, down}, uptime}
2180
2584
  this.#participating = true;
2181
- if (!(await this.staticCall(addresses.validators, 'has', [address]))) {
2182
- const rawTransaction = {
2183
- from: address,
2184
- to: addresses.validators,
2185
- method: 'addValidator',
2186
- params: [address],
2187
- nonce: (await this.getNonce(address)) + 1,
2188
- timestamp: Date.now()
2189
- };
2190
- const transaction = await signTransaction(rawTransaction, globalThis.peernet.identity);
2191
- try {
2192
- await this.sendTransaction(transaction);
2193
- }
2194
- catch (error) {
2195
- console.error(error);
2585
+ try {
2586
+ if (!(await this.staticCall(addresses.validators, 'has', [address]))) {
2587
+ const rawTransaction = {
2588
+ from: address,
2589
+ to: addresses.validators,
2590
+ method: 'addValidator',
2591
+ params: [address],
2592
+ nonce: (await this.getNonce(address)) + 1,
2593
+ timestamp: Date.now()
2594
+ };
2595
+ const transaction = await signTransaction(rawTransaction, globalThis.peernet.identity);
2596
+ try {
2597
+ await this.sendTransaction(transaction);
2598
+ }
2599
+ catch (error) {
2600
+ console.error(error);
2601
+ }
2196
2602
  }
2197
2603
  }
2604
+ catch (error) {
2605
+ debug('Error in participate:', error.message);
2606
+ // Continue anyway - validator check is optional
2607
+ }
2198
2608
  if ((await this.hasTransactionToHandle()) && !this.#runningEpoch && this.#participating)
2199
2609
  await this.#runEpoch();
2200
2610
  }
@@ -2217,6 +2627,7 @@ class Chain extends VersionControl {
2217
2627
  block.fees = block.fees += await calculateFee(transaction.decoded);
2218
2628
  }
2219
2629
  await globalThis.accountsStore.put(transaction.decoded.from, new TextEncoder().encode(String(transaction.decoded.nonce)));
2630
+ // Don't cache nonce during parallel processing - always query pool
2220
2631
  await transactionStore.put(hash, await transaction.encode());
2221
2632
  }
2222
2633
  catch (e) {