@leofcoin/chain 1.8.4 → 1.8.6

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);
@@ -889,7 +1242,7 @@ class State extends Contract {
889
1242
  response: { blocks: await globalThis.blockStore.keys() }
890
1243
  });
891
1244
  };
892
- 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'))));
893
1246
  this.#getLastTransactions = async () => {
894
1247
  let lastTransactions = (await Promise.all((await this.blocks)
895
1248
  // @ts-ignore
@@ -918,6 +1271,7 @@ class State extends Contract {
918
1271
  #lastBlockHandler;
919
1272
  #knownBlocksHandler;
920
1273
  async init() {
1274
+ log('State init start');
921
1275
  this.jobber = new Jobber(this.resolveTimeout);
922
1276
  await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler);
923
1277
  await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler);
@@ -925,31 +1279,42 @@ class State extends Contract {
925
1279
  let localBlockHash;
926
1280
  let blockMessage;
927
1281
  let localBlock;
1282
+ log('State init before try-catch');
928
1283
  try {
929
1284
  const rawBlock = await globalThis.chainStore.has('lastBlock');
1285
+ log('State init after has lastBlock check');
930
1286
  if (rawBlock) {
1287
+ log('State init after has lastBlock found');
1288
+ log(rawBlock);
931
1289
  localBlockHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
1290
+ console.log(localBlockHash);
932
1291
  if (localBlockHash !== '0x0') {
933
- blockMessage = await globalThis.peernet.get(localBlockHash, 'block');
1292
+ blockMessage = await globalThis.blockStore.get(localBlockHash);
1293
+ console.log(blockMessage);
934
1294
  blockMessage = await new BlockMessage(blockMessage);
935
1295
  localBlock = { ...blockMessage.decoded, hash: localBlockHash };
936
1296
  }
1297
+ log('State init after localBlock set');
937
1298
  }
938
1299
  else {
939
1300
  localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
940
1301
  }
941
1302
  }
942
1303
  catch {
1304
+ console.log('e');
1305
+ log('State init middle');
943
1306
  localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
944
1307
  }
1308
+ log('State init middle');
945
1309
  try {
1310
+ log('fetching known blocks from blockStore');
946
1311
  this.knownBlocks = await blockStore.keys();
947
1312
  }
948
1313
  catch (error) {
949
1314
  debug$1('no local known blocks found');
950
1315
  }
951
1316
  try {
952
- if (localBlock.hash && localBlock.hash !== '0x0') {
1317
+ if (localBlock?.hash && localBlock.hash !== '0x0') {
953
1318
  try {
954
1319
  const states = {
955
1320
  lastBlock: JSON.parse(new TextDecoder().decode(await globalThis.stateStore.get('lastBlock')))
@@ -965,7 +1330,10 @@ class State extends Contract {
965
1330
  else {
966
1331
  await this.resolveBlocks();
967
1332
  }
968
- 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;
969
1337
  const lastBlock = await this.#machine.lastBlock;
970
1338
  if (lastBlock.hash !== '0x0') {
971
1339
  this.updateState(new BlockMessage(lastBlock));
@@ -986,8 +1354,12 @@ class State extends Contract {
986
1354
  const hash = await message.hash();
987
1355
  await globalThis.chainStore.put('lastBlock', hash);
988
1356
  globalThis.pubsub.publish('lastBlock', message.encoded);
989
- if (!this.#machine)
990
- 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
+ }
991
1363
  await this.#machine.updateState();
992
1364
  }
993
1365
  catch (error) {
@@ -1451,16 +1823,21 @@ class State extends Contract {
1451
1823
  }
1452
1824
  async triggerLoad() {
1453
1825
  if (this.#blocks?.length > 0) {
1454
- 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;
1455
1830
  }
1456
1831
  }
1457
1832
  }
1458
1833
 
1834
+ const currentVersion = '0.1.1';
1835
+
1459
1836
  class VersionControl extends State {
1460
1837
  constructor(config) {
1461
1838
  super(config);
1462
1839
  }
1463
- #currentVersion = '0.1.0';
1840
+ #currentVersion = currentVersion;
1464
1841
  #reachedOneZeroZero = false;
1465
1842
  async #setCurrentVersion() {
1466
1843
  this.version = this.#currentVersion;
@@ -1847,6 +2224,7 @@ const debug = createDebugger('leofcoin/chain');
1847
2224
  class Chain extends VersionControl {
1848
2225
  #state;
1849
2226
  #slotTime;
2227
+ #blockTime; // 6 second target block time
1850
2228
  /** {Address[]} */
1851
2229
  #validators;
1852
2230
  /** {Boolean} */
@@ -1861,6 +2239,7 @@ class Chain extends VersionControl {
1861
2239
  constructor(config) {
1862
2240
  super(config);
1863
2241
  this.#slotTime = 10000;
2242
+ this.#blockTime = 6000; // 6 second target block time
1864
2243
  this.utils = {};
1865
2244
  /** {Address[]} */
1866
2245
  this.#validators = [];
@@ -1872,14 +2251,16 @@ class Chain extends VersionControl {
1872
2251
  this.#peerConnectionRetries = new Map();
1873
2252
  this.#maxPeerRetries = 5;
1874
2253
  this.#peerRetryDelay = 5000;
2254
+ this.ready = new Promise((resolve) => {
2255
+ this.readyResolve = resolve;
2256
+ });
1875
2257
  this.#addTransaction = async (message) => {
1876
2258
  const transaction = new TransactionMessage(message);
1877
2259
  const hash = await transaction.hash();
1878
2260
  // if (await transactionPoolStore.has(hash)) await transactionPoolStore.delete(hash)
1879
2261
  debug(`added ${hash}`);
1880
2262
  };
1881
- // @ts-ignore
1882
- return this.#init();
2263
+ this.#init();
1883
2264
  }
1884
2265
  get nativeToken() {
1885
2266
  return addresses.nativeToken;
@@ -1893,13 +2274,20 @@ class Chain extends VersionControl {
1893
2274
  return true;
1894
2275
  return false;
1895
2276
  }
2277
+ #sleep(ms) {
2278
+ return new Promise((resolve) => setTimeout(resolve, ms));
2279
+ }
1896
2280
  async #runEpoch() {
2281
+ if (this.#runningEpoch)
2282
+ return;
1897
2283
  this.#runningEpoch = true;
1898
2284
  console.log('epoch');
1899
2285
  const validators = await this.staticCall(addresses.validators, 'validators');
1900
2286
  console.log({ validators });
1901
- if (!validators.includes(peernet.selectedAccount))
2287
+ if (!validators.includes(peernet.selectedAccount)) {
2288
+ this.#runningEpoch = false;
1902
2289
  return;
2290
+ }
1903
2291
  const start = Date.now();
1904
2292
  try {
1905
2293
  await this.#createBlock();
@@ -1909,10 +2297,16 @@ class Chain extends VersionControl {
1909
2297
  }
1910
2298
  const end = Date.now();
1911
2299
  console.log((end - start) / 1000 + ' s');
1912
- if (await this.hasTransactionToHandle())
1913
- return this.#runEpoch();
2300
+ // enforce target block time to avoid tight loops
2301
+ const elapsed = end - start;
2302
+ const remaining = this.#blockTime - elapsed;
2303
+ const hasMore = await this.hasTransactionToHandle();
2304
+ // Only delay if there's no backlog; if backlog exists, continue immediately
2305
+ if (!hasMore && remaining > 0)
2306
+ await this.#sleep(remaining);
1914
2307
  this.#runningEpoch = false;
1915
- // if (await this.hasTransactionToHandle() && !this.#runningEpoch) return this.#runEpoch()
2308
+ if (hasMore)
2309
+ return this.#runEpoch();
1916
2310
  }
1917
2311
  async #setup() {
1918
2312
  const contracts = [
@@ -1947,13 +2341,17 @@ class Chain extends VersionControl {
1947
2341
  this.#participants = [];
1948
2342
  this.#participating = false;
1949
2343
  this.#connectionMonitor = new ConnectionMonitor();
2344
+ log$1('[chain] init:start');
1950
2345
  const initialized = await globalThis.contractStore.has(addresses.contractFactory);
2346
+ log$1(`chain initialized: ${initialized}`);
1951
2347
  if (!initialized)
1952
2348
  await this.#setup();
1953
2349
  this.utils = { formatUnits, parseUnits };
1954
2350
  // this.#state = new State()
2351
+ console.log('init');
1955
2352
  // todo some functions rely on state
1956
2353
  await super.init();
2354
+ log$1('super init done');
1957
2355
  // Start connection monitoring
1958
2356
  this.#connectionMonitor.start(this.version);
1959
2357
  await globalThis.peernet.addRequestHandler('bw-request-message', () => {
@@ -1976,7 +2374,8 @@ class Chain extends VersionControl {
1976
2374
  globalThis.peernet.subscribe('validator:timeout', this.#validatorTimeout.bind(this));
1977
2375
  globalThis.pubsub.subscribe('peer:connected', this.#peerConnected.bind(this));
1978
2376
  globalThis.pubsub.publish('chain:ready', true);
1979
- return this;
2377
+ console.log('[chain] init:done');
2378
+ this.readyResolve(true);
1980
2379
  }
1981
2380
  async #invalidTransaction(hash) {
1982
2381
  hash = new TextDecoder().decode(hash);
@@ -2010,8 +2409,16 @@ class Chain extends VersionControl {
2010
2409
  const transactions = await globalThis.transactionPoolStore.keys();
2011
2410
  const transactionsToGet = [];
2012
2411
  for (const key of transactionsInPool) {
2013
- !transactions.includes(key) &&
2014
- transactionsToGet.push(transactionPoolStore.put(key, await peernet.get(key, 'transaction')));
2412
+ let txData;
2413
+ try {
2414
+ txData = await globalThis.peernet.get(key, 'transaction');
2415
+ }
2416
+ catch (error) {
2417
+ debug(`Failed to get transaction ${key}:`, error?.message ?? error);
2418
+ }
2419
+ if (txData !== undefined && !transactions.includes(key)) {
2420
+ transactionsToGet.push(transactionPoolStore.put(key, txData));
2421
+ }
2015
2422
  }
2016
2423
  return Promise.all(transactionsToGet);
2017
2424
  }
@@ -2177,23 +2584,29 @@ class Chain extends VersionControl {
2177
2584
  // peerReputation(peerId)
2178
2585
  // {bandwith: {up, down}, uptime}
2179
2586
  this.#participating = true;
2180
- if (!(await this.staticCall(addresses.validators, 'has', [address]))) {
2181
- const rawTransaction = {
2182
- from: address,
2183
- to: addresses.validators,
2184
- method: 'addValidator',
2185
- params: [address],
2186
- nonce: (await this.getNonce(address)) + 1,
2187
- timestamp: Date.now()
2188
- };
2189
- const transaction = await signTransaction(rawTransaction, globalThis.peernet.identity);
2190
- try {
2191
- await this.sendTransaction(transaction);
2192
- }
2193
- catch (error) {
2194
- console.error(error);
2587
+ try {
2588
+ if (!(await this.staticCall(addresses.validators, 'has', [address]))) {
2589
+ const rawTransaction = {
2590
+ from: address,
2591
+ to: addresses.validators,
2592
+ method: 'addValidator',
2593
+ params: [address],
2594
+ nonce: (await this.getNonce(address)) + 1,
2595
+ timestamp: Date.now()
2596
+ };
2597
+ const transaction = await signTransaction(rawTransaction, globalThis.peernet.identity);
2598
+ try {
2599
+ await this.sendTransaction(transaction);
2600
+ }
2601
+ catch (error) {
2602
+ console.error(error);
2603
+ }
2195
2604
  }
2196
2605
  }
2606
+ catch (error) {
2607
+ debug('Error in participate:', error.message);
2608
+ // Continue anyway - validator check is optional
2609
+ }
2197
2610
  if ((await this.hasTransactionToHandle()) && !this.#runningEpoch && this.#participating)
2198
2611
  await this.#runEpoch();
2199
2612
  }
@@ -2216,6 +2629,7 @@ class Chain extends VersionControl {
2216
2629
  block.fees = block.fees += await calculateFee(transaction.decoded);
2217
2630
  }
2218
2631
  await globalThis.accountsStore.put(transaction.decoded.from, new TextEncoder().encode(String(transaction.decoded.nonce)));
2632
+ // Don't cache nonce during parallel processing - always query pool
2219
2633
  await transactionStore.put(hash, await transaction.encode());
2220
2634
  }
2221
2635
  catch (e) {