@leofcoin/chain 1.4.22 → 1.4.23

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.
Files changed (42) hide show
  1. package/exports/chain.js +1071 -0
  2. package/exports/node.js +23 -0
  3. package/exports/typings/chain.d.ts +83 -0
  4. package/exports/typings/config/config.d.ts +1 -0
  5. package/exports/typings/config/main.d.ts +5 -0
  6. package/exports/typings/config/protocol.d.ts +6 -0
  7. package/exports/typings/contract.d.ts +29 -0
  8. package/exports/typings/fee/config.d.ts +4 -0
  9. package/exports/typings/machine.d.ts +26 -0
  10. package/exports/typings/node.d.ts +9 -0
  11. package/exports/typings/protocol.d.ts +4 -0
  12. package/exports/typings/state.d.ts +5 -0
  13. package/exports/typings/transaction.d.ts +47 -0
  14. package/exports/typings/typer.d.ts +6 -0
  15. package/package.json +12 -6
  16. package/CHANGELOG.md +0 -14
  17. package/demo/index.html +0 -25
  18. package/examples/contracts/token.js +0 -7
  19. package/plugins/bundle.js +0 -18
  20. package/src/chain.js +0 -716
  21. package/src/config/config.js +0 -14
  22. package/src/config/main.js +0 -4
  23. package/src/config/protocol.js +0 -5
  24. package/src/contract.js +0 -52
  25. package/src/fee/config.js +0 -3
  26. package/src/machine.js +0 -215
  27. package/src/node.js +0 -24
  28. package/src/protocol.js +0 -4
  29. package/src/state.js +0 -31
  30. package/src/transaction.js +0 -234
  31. package/src/type.index.d.ts +0 -21
  32. package/src/typer.js +0 -19
  33. package/test/chain.js +0 -120
  34. package/test/contracts/token.js +0 -40
  35. package/test/create-genesis.js +0 -66
  36. package/test/index.js +0 -1
  37. package/tsconfig.js +0 -15
  38. package/workers/block-worker.js +0 -40
  39. package/workers/machine-worker.js +0 -219
  40. package/workers/pool-worker.js +0 -28
  41. package/workers/transaction-worker.js +0 -20
  42. package/workers/workers.js +0 -9
@@ -0,0 +1,1071 @@
1
+ import { formatBytes, BigNumber, formatUnits, parseUnits } from '@leofcoin/utils';
2
+ import addresses, { contractFactory, nativeToken, validators, nameService } from '@leofcoin/addresses';
3
+ import { randombytes } from '@leofcoin/crypto';
4
+ import EasyWorker from '@vandeurenglenn/easy-worker';
5
+ import { ContractMessage, TransactionMessage, BlockMessage, BWMessage, BWRequestMessage } from '@leofcoin/messages';
6
+ import { calculateFee, createContractMessage, contractFactoryMessage, nativeTokenMessage, validatorsMessage, nameServiceMessage, signTransaction } from '@leofcoin/lib';
7
+ import pako from 'pako';
8
+
9
+ // import State from './state'
10
+ class Machine {
11
+ #contracts = {};
12
+ #nonces = {};
13
+ lastBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
14
+ constructor(blocks) {
15
+ return this.#init(blocks);
16
+ }
17
+ #createMessage(sender = peernet.selectedAccount) {
18
+ return {
19
+ sender,
20
+ call: this.execute,
21
+ staticCall: this.get.bind(this)
22
+ };
23
+ }
24
+ async #onmessage(data) {
25
+ switch (data.type) {
26
+ case 'contractError': {
27
+ console.warn(`removing contract ${await data.hash()}`);
28
+ await contractStore.delete(await data.hash());
29
+ break;
30
+ }
31
+ case 'initError': {
32
+ console.error(`init error: ${data.message}`);
33
+ break;
34
+ }
35
+ case 'executionError': {
36
+ // console.warn(`error executing transaction ${data.message}`);
37
+ pubsub.publish(data.id, { error: data.message });
38
+ break;
39
+ }
40
+ case 'debug': {
41
+ for (const message of data.messages)
42
+ debug(message);
43
+ break;
44
+ }
45
+ case 'machine-ready': {
46
+ this.lastBlock = data.lastBlock;
47
+ pubsub.publish('machine.ready', true);
48
+ break;
49
+ }
50
+ case 'response': {
51
+ pubsub.publish(data.id, data.value || false);
52
+ break;
53
+ }
54
+ }
55
+ }
56
+ async #init(blocks) {
57
+ return new Promise(async (resolve) => {
58
+ const machineReady = () => {
59
+ pubsub.unsubscribe('machine.ready', machineReady);
60
+ resolve(this);
61
+ };
62
+ pubsub.subscribe('machine.ready', machineReady);
63
+ this.worker = await new EasyWorker('node_modules/@leofcoin/workers/src/machine-worker.js', { serialization: 'advanced', type: 'module' });
64
+ this.worker.onmessage(this.#onmessage.bind(this));
65
+ // const blocks = await blockStore.values()
66
+ const contracts = await Promise.all([
67
+ contractStore.get(contractFactory),
68
+ contractStore.get(nativeToken),
69
+ contractStore.get(validators),
70
+ contractStore.get(nameService)
71
+ ]);
72
+ const message = {
73
+ type: 'init',
74
+ input: {
75
+ contracts,
76
+ blocks,
77
+ peerid: peernet.peerId
78
+ }
79
+ };
80
+ this.worker.postMessage(message);
81
+ });
82
+ }
83
+ async #runContract(contractMessage) {
84
+ const hash = await contractMessage.hash();
85
+ return new Promise((resolve, reject) => {
86
+ const id = randombytes(20).toString('hex');
87
+ const onmessage = message => {
88
+ pubsub.unsubscribe(id, onmessage);
89
+ if (message?.error)
90
+ reject(message.error);
91
+ else
92
+ resolve(message);
93
+ };
94
+ pubsub.subscribe(id, onmessage);
95
+ this.worker.postMessage({
96
+ type: 'run',
97
+ id,
98
+ input: {
99
+ decoded: contractMessage.decoded,
100
+ encoded: contractMessage.encoded,
101
+ hash
102
+ }
103
+ });
104
+ });
105
+ }
106
+ /**
107
+ *
108
+ * @param {Address} contract
109
+ * @param {String} method
110
+ * @param {Array} parameters
111
+ * @returns Promise<message>
112
+ */
113
+ async execute(contract, method, parameters) {
114
+ try {
115
+ if (contract === contractFactory && method === 'registerContract') {
116
+ if (await this.has(parameters[0]))
117
+ throw new Error(`duplicate contract @${parameters[0]}`);
118
+ let message;
119
+ if (!await contractStore.has(parameters[0])) {
120
+ message = await peernet.get(parameters[0], 'contract');
121
+ message = await new ContractMessage(message);
122
+ await contractStore.put(await message.hash(), message.encoded);
123
+ }
124
+ if (!message) {
125
+ message = await contractStore.get(parameters[0]);
126
+ message = await new ContractMessage(message);
127
+ }
128
+ if (!await this.has(await message.hash()))
129
+ await this.#runContract(message);
130
+ }
131
+ }
132
+ catch (error) {
133
+ throw new Error(`contract deployment failed for ${parameters[0]}\n${error.message}`);
134
+ }
135
+ return new Promise((resolve, reject) => {
136
+ const id = randombytes(20).toString('hex');
137
+ const onmessage = message => {
138
+ pubsub.unsubscribe(id, onmessage);
139
+ if (message?.error)
140
+ reject(message.error);
141
+ else
142
+ resolve(message);
143
+ };
144
+ pubsub.subscribe(id, onmessage);
145
+ this.worker.postMessage({
146
+ type: 'execute',
147
+ id,
148
+ input: {
149
+ contract,
150
+ method,
151
+ params: parameters
152
+ }
153
+ });
154
+ });
155
+ }
156
+ get(contract, method, parameters) {
157
+ return new Promise((resolve, reject) => {
158
+ const id = randombytes(20).toString();
159
+ const onmessage = message => {
160
+ pubsub.unsubscribe(id, onmessage);
161
+ resolve(message);
162
+ };
163
+ pubsub.subscribe(id, onmessage);
164
+ this.worker.postMessage({
165
+ type: 'get',
166
+ id,
167
+ input: {
168
+ contract,
169
+ method,
170
+ params: parameters
171
+ }
172
+ });
173
+ });
174
+ }
175
+ async has(address) {
176
+ return new Promise((resolve, reject) => {
177
+ const id = randombytes(20).toString('hex');
178
+ const onmessage = message => {
179
+ pubsub.unsubscribe(id, onmessage);
180
+ if (message?.error)
181
+ reject(message.error);
182
+ else
183
+ resolve(message);
184
+ };
185
+ pubsub.subscribe(id, onmessage);
186
+ this.worker.postMessage({
187
+ type: 'has',
188
+ id,
189
+ input: {
190
+ address
191
+ }
192
+ });
193
+ });
194
+ }
195
+ async delete(hash) {
196
+ return contractStore.delete(hash);
197
+ }
198
+ /**
199
+ *
200
+ * @returns Promise
201
+ */
202
+ async deleteAll() {
203
+ let hashes = await contractStore.get();
204
+ hashes = Object.keys(hashes).map(hash => this.delete(hash));
205
+ return Promise.all(hashes);
206
+ }
207
+ }
208
+
209
+ class State {
210
+ constructor() {
211
+ // return this.#init()
212
+ }
213
+ // async #init() {
214
+ // const state = await stateStore.get()
215
+ // for (const [key, value] of Object.entries(state)) {
216
+ //
217
+ // }
218
+ //
219
+ // return this
220
+ // }
221
+ async put(key, value, isCompressed = true) {
222
+ value = isCompressed ? value : await pako.deflate(value);
223
+ await stateStore.put(key, value);
224
+ }
225
+ async get(key, isCompressed = true) {
226
+ const value = await stateStore.get(key);
227
+ return isCompressed = pako.inflate(value) ;
228
+ }
229
+ updateState(block) {
230
+ // block.decoded.index
231
+ // this.#isUpdateNeeded()
232
+ }
233
+ }
234
+
235
+ class Protocol {
236
+ limit = 1800;
237
+ transactionLimit = 1800;
238
+ }
239
+
240
+ class Transaction extends Protocol {
241
+ constructor() {
242
+ super();
243
+ }
244
+ /**
245
+ *
246
+ * @param {Address[]} transactions
247
+ * @returns transactions to include
248
+ */
249
+ async getTransactions(transactions) {
250
+ return new Promise(async (resolve, reject) => {
251
+ let size = 0;
252
+ const _transactions = [];
253
+ await Promise.all(transactions
254
+ .map(async (tx) => {
255
+ tx = await new TransactionMessage(tx);
256
+ size += tx.encoded.length;
257
+ if (!formatBytes(size).includes('MB') || formatBytes(size).includes('MB') && Number(formatBytes(size).split(' MB')[0]) <= 0.75)
258
+ _transactions.push({ ...tx.decoded, hash: await tx.hash() });
259
+ else
260
+ resolve(_transactions);
261
+ }));
262
+ return resolve(_transactions);
263
+ });
264
+ }
265
+ /**
266
+ *
267
+ * @param {Transaction[]} transactions An array containing Transactions
268
+ * @returns {TransactionMessage}
269
+ */
270
+ async promiseTransactions(transactions) {
271
+ transactions = await Promise.all(transactions.map(tx => new TransactionMessage(tx)));
272
+ return transactions;
273
+ }
274
+ /**
275
+ *
276
+ * @param {Transaction[]} transactions An array containing Transactions
277
+ * @returns {Object} {transaction.decoded, transaction.hash}
278
+ */
279
+ async promiseTransactionsContent(transactions) {
280
+ transactions = await Promise.all(transactions.map(tx => new Promise(async (resolve, reject) => {
281
+ resolve({ ...tx.decoded, hash: await tx.hash() });
282
+ })));
283
+ return transactions;
284
+ }
285
+ /**
286
+ * When a nonce isn't found for an address fallback to just checking the transactionnPoolStore
287
+ * @param {Address} address
288
+ * @returns {Number} nonce
289
+ */
290
+ async #getNonceFallback(address) {
291
+ let transactions = await globalThis.transactionPoolStore.values();
292
+ transactions = await this.promiseTransactions(transactions);
293
+ transactions = transactions.filter(tx => tx.decoded.from === address);
294
+ transactions = await this.promiseTransactionsContent(transactions);
295
+ if (this.lastBlock?.hash && transactions.length === 0 && this.lastBlock.hash !== '0x0') {
296
+ let block = await peernet.get(this.lastBlock.hash);
297
+ block = await new BlockMessage(block);
298
+ // for (let tx of block.decoded?.transactions) {
299
+ // tx = await peernet.get(tx, 'transaction')
300
+ // transactions.push(new TransactionMessage(tx))
301
+ // }
302
+ transactions = transactions.filter(tx => tx.from === address);
303
+ while (transactions.length === 0 && block.decoded.index !== 0 && block.decoded.previousHash !== '0x0') {
304
+ block = await globalThis.blockStore.get(block.decoded.previousHash);
305
+ block = await new BlockMessage(block);
306
+ transactions = block.decoded.transactions.filter(tx => tx.from === address);
307
+ }
308
+ }
309
+ if (transactions.length === 0)
310
+ return 0;
311
+ transactions = transactions.sort((a, b) => a.timestamp - b.timestamp);
312
+ return transactions[transactions.length - 1].nonce;
313
+ }
314
+ /**
315
+ * Get amount of transactions by address
316
+ * @param {Address} address The address to get the nonce for
317
+ * @returns {Number} nonce
318
+ */
319
+ async getNonce(address) {
320
+ if (!await globalThis.accountsStore.has(address)) {
321
+ const nonce = await this.#getNonceFallback(address);
322
+ await globalThis.accountsStore.put(address, new TextEncoder().encode(String(nonce)));
323
+ }
324
+ // todo: are those in the pool in cluded also ? they need to be included!!!
325
+ let nonce = await globalThis.accountsStore.get(address);
326
+ nonce = new TextDecoder().decode(nonce);
327
+ let transactions = await globalThis.transactionPoolStore.values();
328
+ transactions = await this.promiseTransactions(transactions);
329
+ transactions = transactions.filter(tx => tx.decoded.from === address);
330
+ transactions = await this.promiseTransactionsContent(transactions);
331
+ for (const transaction of transactions) {
332
+ if (transaction.nonce > nonce)
333
+ nonce = transaction.nonce;
334
+ }
335
+ return Number(nonce);
336
+ }
337
+ async validateNonce(address, nonce) {
338
+ let previousNonce = await globalThis.accountsStore.get(address);
339
+ previousNonce = Number(new TextDecoder().decode(previousNonce));
340
+ if (previousNonce > nonce)
341
+ throw new Error(`a transaction with a higher nonce already exists`);
342
+ if (previousNonce === nonce)
343
+ throw new Error(`a transaction with the same nonce already exists`);
344
+ let transactions = await globalThis.transactionPoolStore.values();
345
+ transactions = await this.promiseTransactions(transactions);
346
+ transactions = transactions.filter(tx => tx.decoded.from === address);
347
+ for (const transaction of transactions) {
348
+ if (transaction.decoded.nonce > nonce)
349
+ throw new Error(`a transaction with a higher nonce already exists`);
350
+ if (transaction.decoded.nonce === nonce)
351
+ throw new Error(`a transaction with the same nonce already exists`);
352
+ }
353
+ }
354
+ isTransactionMessage(message) {
355
+ if (message instanceof TransactionMessage)
356
+ return true;
357
+ return false;
358
+ }
359
+ async createTransaction(transaction) {
360
+ return {
361
+ from: transaction.from,
362
+ to: transaction.to,
363
+ method: transaction.method,
364
+ params: transaction.params,
365
+ timestamp: transaction.timestamp || Date.now(),
366
+ nonce: transaction.nonce || (await this.getNonce(transaction.from)) + 1
367
+ };
368
+ }
369
+ async sendTransaction(message) {
370
+ if (!this.isTransactionMessage(message))
371
+ message = new TransactionMessage(message);
372
+ if (!message.decoded.signature)
373
+ throw new Error(`transaction not signed`);
374
+ if (message.decoded.nonce === undefined)
375
+ throw new Error(`nonce required`);
376
+ try {
377
+ await this.validateNonce(message.decoded.from, message.decoded.nonce);
378
+ // todo check if signature is valid
379
+ const hash = await message.hash();
380
+ let data;
381
+ const wait = new Promise(async (resolve, reject) => {
382
+ if (pubsub.subscribers[`transaction.completed.${hash}`]) {
383
+ const result = pubsub.subscribers[`transaction.completed.${hash}`].value;
384
+ result.status === 'fulfilled' ? resolve(result.hash) : reject({ hash: result.hash, error: result.error });
385
+ }
386
+ else {
387
+ const completed = async (result) => {
388
+ result.status === 'fulfilled' ? resolve(result.hash) : reject({ hash: result.hash, error: result.error });
389
+ setTimeout(async () => {
390
+ pubsub.unsubscribe(`transaction.completed.${hash}`, completed);
391
+ }, 10_000);
392
+ };
393
+ pubsub.subscribe(`transaction.completed.${hash}`, completed);
394
+ }
395
+ });
396
+ await globalThis.transactionPoolStore.put(hash, message.encoded);
397
+ // debug(`Added ${hash} to the transaction pool`)
398
+ peernet.publish('add-transaction', message.encoded);
399
+ return { hash: hash, data, fee: await calculateFee(message.decoded), wait, message };
400
+ }
401
+ catch (error) {
402
+ throw error;
403
+ }
404
+ }
405
+ }
406
+
407
+ /**
408
+ * @extends {Transaction}
409
+ */
410
+ class Contract extends Transaction {
411
+ constructor() {
412
+ super();
413
+ }
414
+ /**
415
+ *
416
+ * @param {Address} creator
417
+ * @param {String} contract
418
+ * @param {Array} constructorParameters
419
+ * @returns lib.createContractMessage
420
+ */
421
+ async createContractMessage(creator, contract, constructorParameters = []) {
422
+ return createContractMessage(creator, contract, constructorParameters);
423
+ }
424
+ /**
425
+ *
426
+ * @param {Address} creator
427
+ * @param {String} contract
428
+ * @param {Array} constructorParameters
429
+ * @returns {Address}
430
+ */
431
+ async createContractAddress(creator, contract, constructorParameters = []) {
432
+ contract = await this.createContractMessage(creator, contract, constructorParameters);
433
+ return contract.hash();
434
+ }
435
+ /**
436
+ *
437
+ * @param {String} contract
438
+ * @param {Array} parameters
439
+ * @returns
440
+ */
441
+ async deployContract(contract, constructorParameters = []) {
442
+ const message = await createContractMessage(peernet.selectedAccount, contract, constructorParameters);
443
+ try {
444
+ await contractStore.put(await message.hash(), message.encoded);
445
+ }
446
+ catch (error) {
447
+ throw error;
448
+ }
449
+ return this.createTransactionFrom(peernet.selectedAccount, addresses.contractFactory, 'registerContract', [await message.hash()]);
450
+ }
451
+ }
452
+
453
+ globalThis.BigNumber = BigNumber;
454
+ // check if browser or local
455
+ class Chain extends Contract {
456
+ /** {Address[]} */
457
+ #validators = [];
458
+ /** {Block[]} */
459
+ #blocks = [];
460
+ #machine;
461
+ /** {Boolean} */
462
+ #runningEpoch = false;
463
+ /** {Boolean} */
464
+ #chainSyncing = false;
465
+ /** {Number} */
466
+ #totalSize = 0;
467
+ /**
468
+ * {Block} {index, hash, previousHash}
469
+ */
470
+ #lastBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
471
+ /**
472
+ * amount the native token has been iteracted with
473
+ */
474
+ #nativeCalls = 0;
475
+ /**
476
+ * amount the native token has been iteracted with
477
+ */
478
+ #nativeTransfers = 0;
479
+ /**
480
+ * amount of native token burned
481
+ * {Number}
482
+ */
483
+ #nativeBurns = 0;
484
+ /**
485
+ * amount of native tokens minted
486
+ * {Number}
487
+ */
488
+ #nativeMints = 0;
489
+ /**
490
+ * total amount of transactions
491
+ * {Number}
492
+ */
493
+ #totalTransactions = 0;
494
+ #participants = [];
495
+ #participating = false;
496
+ #jail = [];
497
+ constructor() {
498
+ super();
499
+ return this.#init();
500
+ }
501
+ get nativeMints() {
502
+ return this.#nativeMints;
503
+ }
504
+ get nativeBurns() {
505
+ return this.#nativeBurns;
506
+ }
507
+ get nativeTransfers() {
508
+ return this.#nativeTransfers;
509
+ }
510
+ get totalTransactions() {
511
+ return this.#totalTransactions;
512
+ }
513
+ get nativeCalls() {
514
+ return this.#nativeCalls;
515
+ }
516
+ get totalSize() {
517
+ return this.#totalSize;
518
+ }
519
+ get lib() {
520
+ return lib;
521
+ }
522
+ get lastBlock() {
523
+ return this.#lastBlock;
524
+ }
525
+ get nativeToken() {
526
+ return addresses.nativeToken;
527
+ }
528
+ get validators() {
529
+ return [...this.#validators];
530
+ }
531
+ get blocks() {
532
+ return [...this.#blocks];
533
+ }
534
+ async hasTransactionToHandle() {
535
+ const size = await transactionPoolStore.size();
536
+ if (size > 0)
537
+ return true;
538
+ return false;
539
+ }
540
+ async #runEpoch() {
541
+ this.#runningEpoch = true;
542
+ console.log('epoch');
543
+ const validators = await this.staticCall(addresses.validators, 'validators');
544
+ console.log({ validators });
545
+ if (!validators[globalThis.peernet.selectedAccount]?.active)
546
+ return;
547
+ const start = Date.now();
548
+ try {
549
+ await this.#createBlock();
550
+ }
551
+ catch (error) {
552
+ console.error(error);
553
+ }
554
+ const end = Date.now();
555
+ console.log(((end - start) / 1000) + ' s');
556
+ if (await this.hasTransactionToHandle())
557
+ return this.#runEpoch();
558
+ this.#runningEpoch = false;
559
+ // if (await this.hasTransactionToHandle() && !this.#runningEpoch) return this.#runEpoch()
560
+ }
561
+ async #setup() {
562
+ const contracts = [{
563
+ address: addresses.contractFactory,
564
+ message: contractFactoryMessage
565
+ }, {
566
+ address: addresses.nativeToken,
567
+ message: nativeTokenMessage
568
+ }, {
569
+ address: addresses.validators,
570
+ message: validatorsMessage
571
+ }, {
572
+ address: addresses.nameService,
573
+ message: nameServiceMessage
574
+ }];
575
+ await Promise.all(contracts.map(async ({ address, message }) => {
576
+ // console.log({message});
577
+ message = await new ContractMessage(Uint8Array.from(message.split(',').map(string => Number(string))));
578
+ await contractStore.put(address, message.encoded);
579
+ }));
580
+ console.log('handle native contracts');
581
+ // handle native contracts
582
+ }
583
+ promiseRequests(promises) {
584
+ return new Promise(async (resolve, reject) => {
585
+ const timeout = setTimeout(() => {
586
+ resolve([{ index: 0, hash: '0x0' }]);
587
+ debug('sync timed out');
588
+ }, 10_000);
589
+ promises = await Promise.allSettled(promises);
590
+ promises = promises.filter(({ status }) => status === 'fulfilled');
591
+ clearTimeout(timeout);
592
+ promises = promises.map(({ value }) => new peernet.protos['peernet-response'](value));
593
+ promises = await Promise.all(promises);
594
+ promises = promises.map(node => node.decoded.response);
595
+ resolve(promises);
596
+ });
597
+ }
598
+ getLatestBlock() {
599
+ return this.#getLatestBlock();
600
+ }
601
+ async #getLatestBlock() {
602
+ let promises = [];
603
+ let data = await new peernet.protos['peernet-request']({ request: 'lastBlock' });
604
+ const node = await peernet.prepareMessage(data);
605
+ for (const peer of peernet.connections) {
606
+ if (peer.connected && peer.readyState === 'open' && peer.peerId !== this.id) {
607
+ promises.push(peer.request(node.encoded));
608
+ }
609
+ else if (!peer.connected || peer.readyState !== 'open') ;
610
+ }
611
+ promises = await this.promiseRequests(promises);
612
+ let latest = { index: 0, hash: '0x0' };
613
+ for (const value of promises) {
614
+ if (value.index > latest.index) {
615
+ latest.index = value.index;
616
+ latest.hash = await value.hash();
617
+ }
618
+ }
619
+ if (latest.hash && latest.hash !== '0x0') {
620
+ latest = await peernet.get(latest.hash, block);
621
+ latest = await new BlockMessage(latest);
622
+ }
623
+ return latest;
624
+ }
625
+ async #init() {
626
+ // this.node = await new Node()
627
+ this.#participants = [];
628
+ this.#participating = false;
629
+ const initialized = await contractStore.has(addresses.contractFactory);
630
+ if (!initialized)
631
+ await this.#setup();
632
+ this.utils = { BigNumber, formatUnits, parseUnits };
633
+ this.state = new State();
634
+ try {
635
+ let localBlock;
636
+ try {
637
+ localBlock = await chainStore.get('lastBlock');
638
+ }
639
+ catch {
640
+ await chainStore.put('lastBlock', '0x0');
641
+ localBlock = await chainStore.get('lastBlock');
642
+ }
643
+ localBlock = new TextDecoder().decode(localBlock);
644
+ if (localBlock && localBlock !== '0x0') {
645
+ localBlock = await peernet.get(localBlock, 'block');
646
+ localBlock = await new BlockMessage(localBlock);
647
+ this.#lastBlock = { ...localBlock.decoded, hash: await localBlock.hash() };
648
+ }
649
+ else {
650
+ const latestBlock = await this.#getLatestBlock();
651
+ await this.#syncChain(latestBlock);
652
+ }
653
+ }
654
+ catch (error) {
655
+ console.log({ e: error });
656
+ }
657
+ await peernet.addRequestHandler('bw-request-message', () => {
658
+ return new BWMessage(peernet.client.bw) || { up: 0, down: 0 };
659
+ });
660
+ await peernet.addRequestHandler('lastBlock', this.#lastBlockHandler.bind(this));
661
+ peernet.subscribe('add-block', this.#addBlock.bind(this));
662
+ peernet.subscribe('add-transaction', this.#addTransaction.bind(this));
663
+ peernet.subscribe('validator:timeout', this.#validatorTimeout.bind(this));
664
+ pubsub.subscribe('peer:connected', this.#peerConnected.bind(this));
665
+ // load local blocks
666
+ await this.resolveBlocks();
667
+ this.#machine = await new Machine(this.#blocks);
668
+ await this.#loadBlocks(this.#blocks);
669
+ return this;
670
+ }
671
+ async #validatorTimeout(validatorInfo) {
672
+ setTimeout(() => {
673
+ this.#jail.splice(this.jail.indexOf(validatorInfo.address), 1);
674
+ }, validatorInfo.timeout);
675
+ this.#jail.push(validatorInfo.address);
676
+ }
677
+ async #syncChain(lastBlock) {
678
+ if (this.#chainSyncing || !lastBlock || !lastBlock.hash || !lastBlock.hash)
679
+ return;
680
+ if (!this.lastBlock || Number(this.lastBlock.index) < Number(lastBlock.index)) {
681
+ this.#chainSyncing = true;
682
+ // TODO: check if valid
683
+ const localIndex = this.lastBlock ? this.lastBlock.index : 0;
684
+ const index = lastBlock.index;
685
+ await this.resolveBlock(lastBlock.hash);
686
+ let blocksSynced = localIndex > 0 ? (localIndex > index ? localIndex - index : index - localIndex) : index;
687
+ debug(`synced ${blocksSynced} ${blocksSynced > 1 ? 'blocks' : 'block'}`);
688
+ this.#blocks.length;
689
+ const start = (this.#blocks.length - blocksSynced) - 1;
690
+ await this.#loadBlocks(this.blocks.slice(start));
691
+ await this.#updateState(new BlockMessage(this.#blocks[this.#blocks.length - 1]));
692
+ this.#chainSyncing = false;
693
+ }
694
+ }
695
+ async #peerConnected(peer) {
696
+ let node = await new peernet.protos['peernet-request']({ request: 'lastBlock' });
697
+ node = await peernet.prepareMessage(node);
698
+ let response = await peer.request(node.encoded);
699
+ response = await new globalThis.peernet.protos['peernet-response'](response);
700
+ let lastBlock = response.decoded.response;
701
+ this.#syncChain(lastBlock);
702
+ }
703
+ #epochTimeout;
704
+ async #lastBlockHandler() {
705
+ return new peernet.protos['peernet-response']({ response: { hash: this.#lastBlock?.hash, index: this.#lastBlock?.index } });
706
+ }
707
+ async resolveBlock(hash) {
708
+ if (!hash)
709
+ throw new Error(`expected hash, got: ${hash}`);
710
+ let block = await peernet.get(hash, 'block');
711
+ block = await new BlockMessage(block);
712
+ if (!await peernet.has(hash, 'block'))
713
+ await peernet.put(hash, block.encoded, 'block');
714
+ const size = block.encoded.length > 0 ? block.encoded.length : block.encoded.byteLength;
715
+ this.#totalSize += size;
716
+ block = { ...block.decoded, hash };
717
+ if (this.#blocks[block.index] && this.#blocks[block.index].hash !== block.hash)
718
+ throw `invalid block ${hash} @${block.index}`;
719
+ this.#blocks[block.index] = block;
720
+ console.log(`resolved block: ${hash} @${block.index} ${formatBytes(size)}`);
721
+ if (block.previousHash !== '0x0') {
722
+ return this.resolveBlock(block.previousHash);
723
+ }
724
+ }
725
+ async resolveBlocks() {
726
+ try {
727
+ const localBlock = await chainStore.get('lastBlock');
728
+ const hash = new TextDecoder().decode(localBlock);
729
+ if (hash && hash !== '0x0')
730
+ await this.resolveBlock(hash);
731
+ this.#lastBlock = this.#blocks[this.#blocks.length - 1];
732
+ }
733
+ catch {
734
+ await chainStore.put('lastBlock', new TextEncoder().encode('0x0'));
735
+ return this.resolveBlocks();
736
+ // console.log(e);
737
+ }
738
+ }
739
+ /**
740
+ *
741
+ * @param {Block[]} blocks
742
+ */
743
+ async #loadBlocks(blocks) {
744
+ for (const block of blocks) {
745
+ if (block && !block.loaded) {
746
+ for (const transaction of block.transactions) {
747
+ try {
748
+ await this.#machine.execute(transaction.to, transaction.method, transaction.params);
749
+ if (transaction.to === nativeToken) {
750
+ this.#nativeCalls += 1;
751
+ if (transaction.method === 'burn')
752
+ this.#nativeBurns += 1;
753
+ if (transaction.method === 'mint')
754
+ this.#nativeMints += 1;
755
+ if (transaction.method === 'transfer')
756
+ this.#nativeTransfers += 1;
757
+ }
758
+ this.#totalTransactions += 1;
759
+ }
760
+ catch (error) {
761
+ console.log(error);
762
+ }
763
+ }
764
+ this.#blocks[block.index].loaded = true;
765
+ debug(`loaded block: ${block.hash} @${block.index}`);
766
+ }
767
+ }
768
+ }
769
+ async #executeTransaction({ hash, from, to, method, params, nonce }) {
770
+ try {
771
+ let result = await this.#machine.execute(to, method, params, from, nonce);
772
+ // if (!result) result = this.#machine.state
773
+ pubsub.publish(`transaction.completed.${hash}`, { status: 'fulfilled', hash });
774
+ return result || 'no state change';
775
+ }
776
+ catch (error) {
777
+ console.log(error);
778
+ pubsub.publish(`transaction.completed.${hash}`, { status: 'fail', hash, error: error });
779
+ throw error;
780
+ }
781
+ }
782
+ async #addBlock(block) {
783
+ // console.log(block);
784
+ const blockMessage = await new BlockMessage(block);
785
+ await Promise.all(blockMessage.decoded.transactions
786
+ .map(async (transaction) => transactionPoolStore.delete(transaction.hash)));
787
+ const hash = await blockMessage.hash();
788
+ await blockStore.put(hash, blockMessage.encoded);
789
+ if (this.lastBlock.index < blockMessage.decoded.index)
790
+ await this.#updateState(blockMessage);
791
+ debug(`added block: ${hash}`);
792
+ let promises = [];
793
+ let contracts = [];
794
+ for (let transaction of blockMessage.decoded.transactions) {
795
+ // await transactionStore.put(transaction.hash, transaction.encoded)
796
+ const index = contracts.indexOf(transaction.to);
797
+ if (index === -1)
798
+ contracts.push(transaction.to);
799
+ // Todo: go trough all accounts
800
+ promises.push(this.#executeTransaction(transaction));
801
+ }
802
+ try {
803
+ promises = await Promise.allSettled(promises);
804
+ for (let transaction of blockMessage.decoded.transactions) {
805
+ pubsub.publish('transaction-processed', transaction);
806
+ if (transaction.to === peernet.selectedAccount)
807
+ pubsub.publish('account-transaction-processed', transaction);
808
+ await accountsStore.put(transaction.from, String(transaction.nonce));
809
+ }
810
+ // todo finish state
811
+ // for (const contract of contracts) {
812
+ // const state = await this.#machine.get(contract, 'state')
813
+ // // await stateStore.put(contract, state)
814
+ // console.log(state);
815
+ // }
816
+ pubsub.publish('block-processed', blockMessage.decoded);
817
+ }
818
+ catch (error) {
819
+ console.log({ e: error });
820
+ }
821
+ }
822
+ async #updateState(message) {
823
+ const hash = await message.hash();
824
+ this.#lastBlock = { hash, ...message.decoded };
825
+ await this.state.updateState(message);
826
+ await chainStore.put('lastBlock', hash);
827
+ }
828
+ async participate(address) {
829
+ // TODO: validate participant
830
+ // hold min amount of 50k ART for 7 days
831
+ // lock the 50k
832
+ // introduce peer-reputation
833
+ // peerReputation(peerId)
834
+ // {bandwith: {up, down}, uptime}
835
+ this.#participating = true;
836
+ if (!await this.staticCall(addresses.validators, 'has', [address])) {
837
+ const rawTransaction = {
838
+ from: address,
839
+ to: addresses.validators,
840
+ method: 'addValidator',
841
+ params: [address],
842
+ nonce: (await this.getNonce(address)) + 1,
843
+ timestamp: Date.now()
844
+ };
845
+ const transaction = await signTransaction(rawTransaction, peernet.identity);
846
+ await this.sendTransaction(transaction);
847
+ }
848
+ if (await this.hasTransactionToHandle() && !this.#runningEpoch)
849
+ await this.#runEpoch();
850
+ }
851
+ // todo filter tx that need to wait on prev nonce
852
+ async #createBlock(limit = 1800) {
853
+ // vote for transactions
854
+ if (await transactionPoolStore.size() === 0)
855
+ return;
856
+ let transactions = await transactionPoolStore.values(this.transactionLimit);
857
+ if (Object.keys(transactions)?.length === 0)
858
+ return;
859
+ let block = {
860
+ transactions: [],
861
+ validators: [],
862
+ fees: BigNumber.from(0)
863
+ };
864
+ // exclude failing tx
865
+ transactions = await this.promiseTransactions(transactions);
866
+ transactions = transactions.sort((a, b) => a.nonce - b.nonce);
867
+ for (let transaction of transactions) {
868
+ const hash = await transaction.hash();
869
+ try {
870
+ await this.#executeTransaction({ ...transaction.decoded, hash });
871
+ block.transactions.push({ hash, ...transaction.decoded });
872
+ block.fees += Number(calculateFee(transaction.decoded));
873
+ await accountsStore.put(transaction.decoded.from, new TextEncoder().encode(String(transaction.decoded.nonce)));
874
+ }
875
+ catch (e) {
876
+ await transactionPoolStore.delete(hash);
877
+ }
878
+ }
879
+ // don't add empty block
880
+ if (block.transactions.length === 0)
881
+ return;
882
+ const validators = await this.staticCall(addresses.validators, 'validators');
883
+ console.log({ validators });
884
+ // block.validators = Object.keys(block.validators).reduce((set, key) => {
885
+ // if (block.validators[key].active) {
886
+ // push({
887
+ // address: key
888
+ // })
889
+ // }
890
+ // }, [])
891
+ const peers = {};
892
+ for (const entry of peernet.peerEntries) {
893
+ peers[entry[0]] = entry[1];
894
+ }
895
+ for (const validator of Object.keys(validators)) {
896
+ if (validators[validator].active) {
897
+ const peer = peers[validator];
898
+ if (peer && peer.connected) {
899
+ let data = await new BWRequestMessage();
900
+ const node = await peernet.prepareMessage(validator, data.encoded);
901
+ try {
902
+ const bw = await peer.request(node.encoded);
903
+ console.log({ bw });
904
+ block.validators.push({
905
+ address: validator,
906
+ bw: bw.up + bw.down
907
+ });
908
+ }
909
+ catch { }
910
+ }
911
+ else if (peernet.selectedAccount === validator) {
912
+ block.validators.push({
913
+ address: peernet.selectedAccount,
914
+ bw: peernet.bw.up + peernet.bw.down
915
+ });
916
+ }
917
+ }
918
+ }
919
+ console.log({ validators: block.validators });
920
+ block.reward = 150;
921
+ block.validators = block.validators.map(validator => {
922
+ validator.reward = String(Number(block.fees) + block.reward / block.validators.length);
923
+ delete validator.bw;
924
+ return validator;
925
+ });
926
+ // block.validators = calculateValidatorReward(block.validators, block.fees)
927
+ block.index = this.lastBlock?.index;
928
+ if (block.index === undefined)
929
+ block.index = 0;
930
+ else
931
+ block.index += 1;
932
+ block.previousHash = this.lastBlock?.hash || '0x0';
933
+ block.timestamp = Date.now();
934
+ const parts = String(block.fees).split('.');
935
+ let decimals = 0;
936
+ if (parts[1]) {
937
+ const potentional = parts[1].split('e');
938
+ if (potentional[0] === parts[1]) {
939
+ decimals = parts[1].length;
940
+ }
941
+ else {
942
+ parts[1] = potentional[0];
943
+ decimals = Number(potentional[1]?.replace(/[+-]/g, '')) + Number(potentional[0].length);
944
+ }
945
+ }
946
+ block.fees = Number.parseFloat(String(block.fees)).toFixed(decimals);
947
+ try {
948
+ await Promise.all(block.transactions
949
+ .map(async (transaction) => transactionPoolStore.delete(transaction.hash)));
950
+ let blockMessage = await new BlockMessage(block);
951
+ const hash = await blockMessage.hash();
952
+ await peernet.put(hash, blockMessage.encoded, 'block');
953
+ await this.#updateState(blockMessage);
954
+ debug(`created block: ${hash}`);
955
+ peernet.publish('add-block', blockMessage.encoded);
956
+ pubsub.publish('add-block', blockMessage.decoded);
957
+ }
958
+ catch (error) {
959
+ throw new Error(`invalid block ${block}`);
960
+ }
961
+ // data = await this.#machine.execute(to, method, params)
962
+ // transactionStore.put(message.hash, message.encoded)
963
+ }
964
+ async #addTransaction(transaction) {
965
+ try {
966
+ transaction = await new TransactionMessage(transaction);
967
+ const hash = await transaction.hash();
968
+ const has = await transactionPoolStore.has(hash);
969
+ if (!has)
970
+ await transactionPoolStore.put(hash, transaction.encoded);
971
+ if (this.#participating && !this.#runningEpoch)
972
+ this.#runEpoch();
973
+ }
974
+ catch (e) {
975
+ console.log(e);
976
+ throw new Error('invalid transaction');
977
+ }
978
+ }
979
+ /**
980
+ * every tx done is trough contracts so no need for amount
981
+ * data is undefined when nothing is returned
982
+ * error is thrown on error so undefined data doesn't mean there is an error...
983
+ **/
984
+ async sendTransaction(transaction) {
985
+ const event = await super.sendTransaction(transaction);
986
+ this.#addTransaction(event.message.encoded);
987
+ return event;
988
+ }
989
+ async addContract(contractMessage) {
990
+ const hash = await contractMessage.hash();
991
+ await this.staticCall(hash, 'get');
992
+ }
993
+ /**
994
+ *
995
+ * @param {Address} sender
996
+ * @returns {globalMessage}
997
+ */
998
+ #createMessage(sender = globalThis.peernet.selectedAccount) {
999
+ return {
1000
+ sender,
1001
+ call: this.call,
1002
+ staticCall: this.staticCall,
1003
+ delegate: this.delegate,
1004
+ staticDelegate: this.staticDelegate
1005
+ };
1006
+ }
1007
+ /**
1008
+ *
1009
+ * @param {Address} sender
1010
+ * @param {Address} contract
1011
+ * @param {String} method
1012
+ * @param {Array} parameters
1013
+ * @returns
1014
+ */
1015
+ internalCall(sender, contract, method, parameters) {
1016
+ globalThis.msg = this.#createMessage(sender);
1017
+ return this.#machine.execute(contract, method, parameters);
1018
+ }
1019
+ /**
1020
+ *
1021
+ * @param {Address} contract
1022
+ * @param {String} method
1023
+ * @param {Array} parameters
1024
+ * @returns
1025
+ */
1026
+ call(contract, method, parameters) {
1027
+ globalThis.msg = this.#createMessage();
1028
+ return this.#machine.execute(contract, method, parameters);
1029
+ }
1030
+ staticCall(contract, method, parameters) {
1031
+ globalThis.msg = this.#createMessage();
1032
+ return this.#machine.get(contract, method, parameters);
1033
+ }
1034
+ delegate(contract, method, parameters) {
1035
+ globalThis.msg = this.#createMessage();
1036
+ return this.#machine.execute(contract, method, parameters);
1037
+ }
1038
+ staticDelegate(contract, method, parameters) {
1039
+ globalThis.msg = this.#createMessage();
1040
+ return this.#machine.get(contract, method, parameters);
1041
+ }
1042
+ mint(to, amount) {
1043
+ return this.call(addresses.nativeToken, 'mint', [to, amount]);
1044
+ }
1045
+ transfer(from, to, amount) {
1046
+ return this.call(addresses.nativeToken, 'transfer', [from, to, amount]);
1047
+ }
1048
+ get balances() {
1049
+ return this.staticCall(addresses.nativeToken, 'balances');
1050
+ }
1051
+ get contracts() {
1052
+ return this.staticCall(addresses.contractFactory, 'contracts');
1053
+ }
1054
+ deleteAll() {
1055
+ return this.#machine.deleteAll();
1056
+ }
1057
+ /**
1058
+ * lookup an address for a registered name using the builtin nameService
1059
+ * @check nameService
1060
+ *
1061
+ * @param {String} - contractName
1062
+ * @returns {String} - address
1063
+ *
1064
+ * @example chain.lookup('myCoolContractName') // qmqsfddfdgfg...
1065
+ */
1066
+ lookup(name) {
1067
+ return this.call(addresses.nameService, 'lookup', [name]);
1068
+ }
1069
+ }
1070
+
1071
+ export { Chain as default };