@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/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 +460 -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 +453 -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);
|
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1914
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2015
|
-
|
|
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
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
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) {
|