@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/browser/{browser-DQlwTLRn-h2IyYbfg.js → browser-CfYI-6aD-DHRKebpJ.js} +1 -1
- package/exports/browser/{browser-BHbuEZJu-DB_cOj5W.js → browser-Qcpp3EKK-DOtgsScX.js} +1 -1
- package/exports/browser/chain.js +463 -50
- package/exports/browser/{client-DCeU_UX5-CR7iKpxy.js → client-CWkdUcxK-Cki9t2ip.js} +5 -5
- package/exports/browser/{identity-B6BHwSTU-5rsAWMHo.js → identity-nIyW_Xm8-BU8xakCv.js} +2 -2
- package/exports/browser/index-ChRjMyiM-EjbBu23l.js +36 -0
- package/exports/browser/{index-DYdP5D9L-D_bByMvp.js → index-DTbjK0sK-BK_5FT46.js} +1 -1
- package/exports/browser/{messages-CiR1YiV5-BOjOxVgq.js → messages-C507MMRx-DCBd2pRi.js} +2 -2
- package/exports/browser/{node-browser-Bv-OkGUJ.js → node-browser-CRH9Pg7V.js} +8 -3
- package/exports/browser/node-browser.js +2 -2
- package/exports/browser/workers/machine-worker.js +8 -8
- package/exports/chain.d.ts +2 -0
- package/exports/chain.js +456 -42
- package/exports/contract-registry.d.ts +106 -0
- package/exports/contract-utils.d.ts +49 -0
- package/exports/contract.d.ts +46 -0
- package/exports/examples/contract-inheritance-example.d.ts +58 -0
- package/exports/examples/hash-based-contracts.d.ts +82 -0
- package/exports/flags.d.ts +1 -1
- package/exports/machine.d.ts +4 -1
- package/exports/node.d.ts +1 -0
- package/exports/node.js +1 -2
- package/exports/protocol.d.ts +1 -1
- package/exports/workers/machine-worker.js +8 -8
- package/package.json +3 -3
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 =
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
1913
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2014
|
-
|
|
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
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
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) {
|