@leofcoin/chain 1.4.73 → 1.4.75
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/chain.js +1663 -7714
- package/exports/browser/{index-502d190c-6a5c8f2f.js → index-502d190c-60cfcc02.js} +2 -2
- package/exports/browser/{index-2d1bd076.js → index-fa2c6beb.js} +1 -1
- package/exports/browser/{messages-cd01524c-1e050da8.js → messages-cd01524c-8d88d84b.js} +2 -2
- package/exports/browser/{node-browser-2a314e0c.js → node-browser-b35d8f46.js} +3 -3
- package/exports/browser/node-browser.js +2 -2
- package/exports/chain.js +594 -605
- package/exports/typings/chain.d.ts +14 -42
- package/exports/typings/contract.d.ts +1 -0
- package/exports/typings/jobs/sync.d.ts +3 -0
- package/exports/typings/{state.d.ts → machine-state.d.ts} +2 -1
- package/exports/typings/protocol.d.ts +2 -0
- package/package.json +2 -1
package/exports/chain.js
CHANGED
|
@@ -1,242 +1,18 @@
|
|
|
1
1
|
import { formatBytes, BigNumber, formatUnits, parseUnits } from '@leofcoin/utils';
|
|
2
|
+
import { TransactionMessage, BlockMessage, ContractMessage, BWMessage, BWRequestMessage } from '@leofcoin/messages';
|
|
2
3
|
import addresses, { contractFactory, nativeToken, validators, nameService } from '@leofcoin/addresses';
|
|
4
|
+
import { calculateFee, createContractMessage, contractFactoryMessage, nativeTokenMessage, validatorsMessage, nameServiceMessage, signTransaction } from '@leofcoin/lib';
|
|
5
|
+
import PQueue from 'p-queue';
|
|
3
6
|
import { randombytes } from '@leofcoin/crypto';
|
|
4
7
|
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
8
|
|
|
235
9
|
const limit = 1800;
|
|
236
10
|
const transactionLimit = 1800;
|
|
237
11
|
const requestTimeout = 30_000;
|
|
238
12
|
const syncTimeout = 30_000;
|
|
239
13
|
class Protocol {
|
|
14
|
+
version;
|
|
15
|
+
resolveTimeout = 30_000;
|
|
240
16
|
get limit() {
|
|
241
17
|
return limit;
|
|
242
18
|
}
|
|
@@ -425,6 +201,8 @@ class Contract extends Transaction {
|
|
|
425
201
|
constructor() {
|
|
426
202
|
super();
|
|
427
203
|
}
|
|
204
|
+
async init() {
|
|
205
|
+
}
|
|
428
206
|
/**
|
|
429
207
|
*
|
|
430
208
|
* @param {Address} creator
|
|
@@ -466,98 +244,588 @@ class Contract extends Transaction {
|
|
|
466
244
|
}
|
|
467
245
|
}
|
|
468
246
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
#
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
#
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
#
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
247
|
+
// import State from './state'
|
|
248
|
+
class Machine {
|
|
249
|
+
#contracts = {};
|
|
250
|
+
#nonces = {};
|
|
251
|
+
lastBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
|
|
252
|
+
constructor(blocks) {
|
|
253
|
+
return this.#init(blocks);
|
|
254
|
+
}
|
|
255
|
+
#createMessage(sender = peernet.selectedAccount) {
|
|
256
|
+
return {
|
|
257
|
+
sender,
|
|
258
|
+
call: this.execute,
|
|
259
|
+
staticCall: this.get.bind(this)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
async #onmessage(data) {
|
|
263
|
+
switch (data.type) {
|
|
264
|
+
case 'contractError': {
|
|
265
|
+
console.warn(`removing contract ${await data.hash()}`);
|
|
266
|
+
await contractStore.delete(await data.hash());
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
case 'initError': {
|
|
270
|
+
console.error(`init error: ${data.message}`);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case 'executionError': {
|
|
274
|
+
// console.warn(`error executing transaction ${data.message}`);
|
|
275
|
+
pubsub.publish(data.id, { error: data.message });
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
case 'debug': {
|
|
279
|
+
for (const message of data.messages)
|
|
280
|
+
debug(message);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case 'machine-ready': {
|
|
284
|
+
this.lastBlock = data.lastBlock;
|
|
285
|
+
pubsub.publish('machine.ready', true);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
case 'response': {
|
|
289
|
+
pubsub.publish(data.id, data.value || false);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async #init(blocks) {
|
|
295
|
+
return new Promise(async (resolve) => {
|
|
296
|
+
const machineReady = () => {
|
|
297
|
+
pubsub.unsubscribe('machine.ready', machineReady);
|
|
298
|
+
resolve(this);
|
|
299
|
+
};
|
|
300
|
+
pubsub.subscribe('machine.ready', machineReady);
|
|
301
|
+
this.worker = await new EasyWorker('node_modules/@leofcoin/workers/src/machine-worker.js', { serialization: 'advanced', type: 'module' });
|
|
302
|
+
this.worker.onmessage(this.#onmessage.bind(this));
|
|
303
|
+
// const blocks = await blockStore.values()
|
|
304
|
+
const contracts = await Promise.all([
|
|
305
|
+
contractStore.get(contractFactory),
|
|
306
|
+
contractStore.get(nativeToken),
|
|
307
|
+
contractStore.get(validators),
|
|
308
|
+
contractStore.get(nameService)
|
|
309
|
+
]);
|
|
310
|
+
const message = {
|
|
311
|
+
type: 'init',
|
|
312
|
+
input: {
|
|
313
|
+
contracts,
|
|
314
|
+
blocks,
|
|
315
|
+
peerid: peernet.peerId
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
this.worker.postMessage(message);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
async #runContract(contractMessage) {
|
|
322
|
+
const hash = await contractMessage.hash();
|
|
323
|
+
return new Promise((resolve, reject) => {
|
|
324
|
+
const id = randombytes(20).toString('hex');
|
|
325
|
+
const onmessage = message => {
|
|
326
|
+
pubsub.unsubscribe(id, onmessage);
|
|
327
|
+
if (message?.error)
|
|
328
|
+
reject(message.error);
|
|
329
|
+
else
|
|
330
|
+
resolve(message);
|
|
331
|
+
};
|
|
332
|
+
pubsub.subscribe(id, onmessage);
|
|
333
|
+
this.worker.postMessage({
|
|
334
|
+
type: 'run',
|
|
335
|
+
id,
|
|
336
|
+
input: {
|
|
337
|
+
decoded: contractMessage.decoded,
|
|
338
|
+
encoded: contractMessage.encoded,
|
|
339
|
+
hash
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
*
|
|
346
|
+
* @param {Address} contract
|
|
347
|
+
* @param {String} method
|
|
348
|
+
* @param {Array} parameters
|
|
349
|
+
* @returns Promise<message>
|
|
350
|
+
*/
|
|
351
|
+
async execute(contract, method, parameters) {
|
|
352
|
+
try {
|
|
353
|
+
if (contract === contractFactory && method === 'registerContract') {
|
|
354
|
+
if (await this.has(parameters[0]))
|
|
355
|
+
throw new Error(`duplicate contract @${parameters[0]}`);
|
|
356
|
+
let message;
|
|
357
|
+
if (!await contractStore.has(parameters[0])) {
|
|
358
|
+
message = await peernet.get(parameters[0], 'contract');
|
|
359
|
+
message = await new ContractMessage(message);
|
|
360
|
+
await contractStore.put(await message.hash(), message.encoded);
|
|
361
|
+
}
|
|
362
|
+
if (!message) {
|
|
363
|
+
message = await contractStore.get(parameters[0]);
|
|
364
|
+
message = await new ContractMessage(message);
|
|
365
|
+
}
|
|
366
|
+
if (!await this.has(await message.hash()))
|
|
367
|
+
await this.#runContract(message);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
throw new Error(`contract deployment failed for ${parameters[0]}\n${error.message}`);
|
|
372
|
+
}
|
|
373
|
+
return new Promise((resolve, reject) => {
|
|
374
|
+
const id = randombytes(20).toString('hex');
|
|
375
|
+
const onmessage = message => {
|
|
376
|
+
pubsub.unsubscribe(id, onmessage);
|
|
377
|
+
if (message?.error)
|
|
378
|
+
reject(message.error);
|
|
379
|
+
else
|
|
380
|
+
resolve(message);
|
|
381
|
+
};
|
|
382
|
+
pubsub.subscribe(id, onmessage);
|
|
383
|
+
this.worker.postMessage({
|
|
384
|
+
type: 'execute',
|
|
385
|
+
id,
|
|
386
|
+
input: {
|
|
387
|
+
contract,
|
|
388
|
+
method,
|
|
389
|
+
params: parameters
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
get(contract, method, parameters) {
|
|
395
|
+
return new Promise((resolve, reject) => {
|
|
396
|
+
const id = randombytes(20).toString();
|
|
397
|
+
const onmessage = message => {
|
|
398
|
+
pubsub.unsubscribe(id, onmessage);
|
|
399
|
+
resolve(message);
|
|
400
|
+
};
|
|
401
|
+
pubsub.subscribe(id, onmessage);
|
|
402
|
+
this.worker.postMessage({
|
|
403
|
+
type: 'get',
|
|
404
|
+
id,
|
|
405
|
+
input: {
|
|
406
|
+
contract,
|
|
407
|
+
method,
|
|
408
|
+
params: parameters
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
async has(address) {
|
|
414
|
+
return new Promise((resolve, reject) => {
|
|
415
|
+
const id = randombytes(20).toString('hex');
|
|
416
|
+
const onmessage = message => {
|
|
417
|
+
pubsub.unsubscribe(id, onmessage);
|
|
418
|
+
if (message?.error)
|
|
419
|
+
reject(message.error);
|
|
420
|
+
else
|
|
421
|
+
resolve(message);
|
|
422
|
+
};
|
|
423
|
+
pubsub.subscribe(id, onmessage);
|
|
424
|
+
this.worker.postMessage({
|
|
425
|
+
type: 'has',
|
|
426
|
+
id,
|
|
427
|
+
input: {
|
|
428
|
+
address
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
async delete(hash) {
|
|
434
|
+
return contractStore.delete(hash);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
*
|
|
438
|
+
* @returns Promise
|
|
439
|
+
*/
|
|
440
|
+
async deleteAll() {
|
|
441
|
+
let hashes = await contractStore.get();
|
|
442
|
+
hashes = Object.keys(hashes).map(hash => this.delete(hash));
|
|
443
|
+
return Promise.all(hashes);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const queue = new PQueue({ concurrency: 1, throwOnTimeout: true });
|
|
448
|
+
class State extends Contract {
|
|
449
|
+
#resolveErrored;
|
|
450
|
+
#lastResolvedTime;
|
|
451
|
+
#lastResolved;
|
|
452
|
+
#resolving = false;
|
|
453
|
+
#resolveErrorCount = 0;
|
|
454
|
+
#syncState;
|
|
455
|
+
#lastBlockInQue;
|
|
456
|
+
#syncErrorCount = 0;
|
|
457
|
+
#blockHashMap = new Map();
|
|
458
|
+
#chainSyncing = false;
|
|
459
|
+
#lastBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
|
|
460
|
+
#blocks = [];
|
|
461
|
+
#knownBlocks = [];
|
|
462
|
+
#totalSize = 0;
|
|
463
|
+
#machine;
|
|
464
|
+
/**
|
|
465
|
+
* amount the native token has been iteracted with
|
|
466
|
+
*/
|
|
467
|
+
#nativeCalls = 0;
|
|
468
|
+
/**
|
|
469
|
+
* amount the native token has been iteracted with
|
|
470
|
+
*/
|
|
471
|
+
#nativeTransfers = 0;
|
|
472
|
+
/**
|
|
473
|
+
* amount of native token burned
|
|
474
|
+
* {Number}
|
|
475
|
+
*/
|
|
476
|
+
#nativeBurns = 0;
|
|
477
|
+
/**
|
|
478
|
+
* amount of native tokens minted
|
|
479
|
+
* {Number}
|
|
480
|
+
*/
|
|
481
|
+
#nativeMints = 0;
|
|
482
|
+
/**
|
|
483
|
+
* total amount of transactions
|
|
484
|
+
* {Number}
|
|
485
|
+
*/
|
|
486
|
+
#totalTransactions = 0;
|
|
487
|
+
get nativeMints() {
|
|
488
|
+
return this.#nativeMints;
|
|
489
|
+
}
|
|
490
|
+
get nativeBurns() {
|
|
491
|
+
return this.#nativeBurns;
|
|
492
|
+
}
|
|
493
|
+
get nativeTransfers() {
|
|
494
|
+
return this.#nativeTransfers;
|
|
495
|
+
}
|
|
496
|
+
get totalTransactions() {
|
|
497
|
+
return this.#totalTransactions;
|
|
498
|
+
}
|
|
499
|
+
get nativeCalls() {
|
|
500
|
+
return this.#nativeCalls;
|
|
501
|
+
}
|
|
502
|
+
get blocks() {
|
|
503
|
+
return [...this.#blocks];
|
|
504
|
+
}
|
|
505
|
+
get lastBlock() {
|
|
506
|
+
return this.#lastBlock;
|
|
507
|
+
}
|
|
508
|
+
get totalSize() {
|
|
509
|
+
return this.#totalSize;
|
|
510
|
+
}
|
|
511
|
+
get machine() {
|
|
512
|
+
return this.#machine;
|
|
513
|
+
}
|
|
514
|
+
constructor() {
|
|
515
|
+
super();
|
|
516
|
+
}
|
|
517
|
+
async init() {
|
|
518
|
+
if (super.init)
|
|
519
|
+
await super.init();
|
|
520
|
+
await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler.bind(this));
|
|
521
|
+
await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler.bind(this));
|
|
522
|
+
try {
|
|
523
|
+
let localBlock;
|
|
524
|
+
try {
|
|
525
|
+
localBlock = await globalThis.chainStore.get('lastBlock');
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
await globalThis.chainStore.put('lastBlock', '0x0');
|
|
529
|
+
localBlock = await globalThis.chainStore.get('lastBlock');
|
|
530
|
+
}
|
|
531
|
+
localBlock = new TextDecoder().decode(localBlock);
|
|
532
|
+
if (localBlock && localBlock !== '0x0') {
|
|
533
|
+
localBlock = await globalThis.peernet.get(localBlock, 'block');
|
|
534
|
+
localBlock = await new BlockMessage(localBlock);
|
|
535
|
+
this.#lastBlock = { ...localBlock.decoded, hash: await localBlock.hash() };
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
if (globalThis.peernet?.connections.length > 0) {
|
|
539
|
+
const latestBlock = await this.#getLatestBlock();
|
|
540
|
+
await this.#syncChain(latestBlock);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
console.log({ e: error });
|
|
546
|
+
}
|
|
547
|
+
// load local blocks
|
|
548
|
+
await this.resolveBlocks();
|
|
549
|
+
this.#machine = await new Machine(this.#blocks);
|
|
550
|
+
await this.#loadBlocks(this.#blocks);
|
|
551
|
+
}
|
|
552
|
+
async updateState(message) {
|
|
553
|
+
const hash = await message.hash();
|
|
554
|
+
this.#lastBlock = { hash, ...message.decoded };
|
|
555
|
+
// await this.state.updateState(message)
|
|
556
|
+
await globalThis.chainStore.put('lastBlock', hash);
|
|
557
|
+
}
|
|
558
|
+
async #lastBlockHandler() {
|
|
559
|
+
return new globalThis.peernet.protos['peernet-response']({ response: { hash: this.#lastBlock?.hash, index: this.#lastBlock?.index } });
|
|
560
|
+
}
|
|
561
|
+
async #knownBlocksHandler() {
|
|
562
|
+
return new globalThis.peernet.protos['peernet-response']({ response: { blocks: this.#blocks.map((block) => block.hash) } });
|
|
563
|
+
}
|
|
564
|
+
getLatestBlock() {
|
|
565
|
+
return this.#getLatestBlock();
|
|
566
|
+
}
|
|
567
|
+
async getAndPutBlock(hash) {
|
|
568
|
+
// todo peernet resolves undefined blocks....
|
|
569
|
+
let block = await globalThis.peernet.get(hash, 'block');
|
|
570
|
+
if (block !== undefined) {
|
|
571
|
+
block = await new BlockMessage(block);
|
|
572
|
+
const { index } = block.decoded;
|
|
573
|
+
if (this.#blocks[index] && this.#blocks[index].hash !== block.hash)
|
|
574
|
+
throw `invalid block ${hash} @${index}`;
|
|
575
|
+
if (!await globalThis.peernet.has(hash, 'block'))
|
|
576
|
+
await globalThis.peernet.put(hash, block.encoded, 'block');
|
|
577
|
+
}
|
|
578
|
+
return block;
|
|
579
|
+
}
|
|
580
|
+
async #resolveBlock(hash) {
|
|
581
|
+
let index = this.#blockHashMap.get(hash);
|
|
582
|
+
if (this.#blocks[index]) {
|
|
583
|
+
if (this.#blocks[index].previousHash !== '0x0') {
|
|
584
|
+
return this.resolveBlock(this.#blocks[index].previousHash);
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const block = await this.getAndPutBlock(hash);
|
|
591
|
+
index = block.decoded.index;
|
|
592
|
+
const size = block.encoded.length > 0 ? block.encoded.length : block.encoded.byteLength;
|
|
593
|
+
this.#totalSize += size;
|
|
594
|
+
this.#blocks[index] = { hash, ...block.decoded };
|
|
595
|
+
this.#blockHashMap.set(hash, index);
|
|
596
|
+
globalThis.debug(`resolved block: ${hash} @${index} ${formatBytes(size)}`);
|
|
597
|
+
this.#lastResolved = this.#blocks[index];
|
|
598
|
+
this.#lastResolvedTime = Date.now();
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
async resolveBlock(hash) {
|
|
602
|
+
if (!hash)
|
|
603
|
+
throw new Error(`expected hash, got: ${hash}`);
|
|
604
|
+
if (hash === '0x0')
|
|
605
|
+
return;
|
|
606
|
+
if (this.#resolving)
|
|
607
|
+
return 'already resolving';
|
|
608
|
+
this.#resolving = true;
|
|
609
|
+
await queue.add(() => this.#resolveBlock(hash));
|
|
610
|
+
this.#resolving = false;
|
|
611
|
+
if (!this.#blockHashMap.has(this.#lastResolved.previousHash) && this.#lastResolved.previousHash !== '0x0')
|
|
612
|
+
return this.resolveBlock(this.#lastResolved.previousHash);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
async resolveBlocks() {
|
|
616
|
+
try {
|
|
617
|
+
const localBlock = await globalThis.chainStore.get('lastBlock');
|
|
618
|
+
const hash = new TextDecoder().decode(localBlock);
|
|
619
|
+
if (hash && hash !== '0x0')
|
|
620
|
+
await this.resolveBlock(hash);
|
|
621
|
+
this.#lastBlock = this.#blocks[this.#blocks.length - 1];
|
|
622
|
+
}
|
|
623
|
+
catch {
|
|
624
|
+
this.#resolveErrored = true;
|
|
625
|
+
this.#resolveErrorCount += 1;
|
|
626
|
+
this.#resolving = false;
|
|
627
|
+
// console.log(e);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
async syncChain(lastBlock) {
|
|
631
|
+
if (!this.shouldSync) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
await queue.clear();
|
|
635
|
+
this.#chainSyncing = true;
|
|
636
|
+
if (!lastBlock)
|
|
637
|
+
lastBlock = await this.#getLatestBlock();
|
|
638
|
+
console.log('starting sync');
|
|
639
|
+
if (globalThis.peernet.connections.length === 0)
|
|
640
|
+
return 'connectionless';
|
|
641
|
+
try {
|
|
642
|
+
await this.#syncChain(lastBlock);
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
this.#syncErrorCount += 1;
|
|
646
|
+
if (this.#syncErrorCount < 3)
|
|
647
|
+
await this.#syncChain(lastBlock);
|
|
648
|
+
this.#chainSyncing = false;
|
|
649
|
+
this.#syncErrorCount = 0;
|
|
650
|
+
return 'errored';
|
|
651
|
+
}
|
|
652
|
+
if (lastBlock.index === this.#lastBlockInQue?.index)
|
|
653
|
+
this.#lastBlockInQue = undefined;
|
|
654
|
+
this.#syncErrorCount = 0;
|
|
655
|
+
this.#chainSyncing = false;
|
|
656
|
+
if (this.#lastBlockInQue)
|
|
657
|
+
return this.syncChain(this.#lastBlockInQue);
|
|
658
|
+
return 'synced';
|
|
659
|
+
}
|
|
660
|
+
async #syncChain(lastBlock) {
|
|
661
|
+
try {
|
|
662
|
+
if (this.#knownBlocks?.length === Number(lastBlock.index) + 1) {
|
|
663
|
+
let promises = [];
|
|
664
|
+
promises = await Promise.allSettled(this.#knownBlocks.map(async (address) => {
|
|
665
|
+
const has = await globalThis.peernet.has(address, 'block');
|
|
666
|
+
return { has, address };
|
|
667
|
+
}));
|
|
668
|
+
promises = promises.filter(({ status, value }) => status === 'fulfilled' && !value.has);
|
|
669
|
+
await Promise.allSettled(promises.map(({ value }) => this.getAndPutBlock(value.address)));
|
|
670
|
+
}
|
|
671
|
+
if (!this.#lastBlock || Number(this.#lastBlock.index) < Number(lastBlock.index)) {
|
|
672
|
+
// TODO: check if valid
|
|
673
|
+
const localIndex = this.#lastBlock ? this.lastBlock.index : 0;
|
|
674
|
+
const index = lastBlock.index;
|
|
675
|
+
await this.resolveBlock(lastBlock.hash);
|
|
676
|
+
console.log('ok');
|
|
677
|
+
let blocksSynced = localIndex > 0 ? (localIndex > index ? localIndex - index : index - localIndex) : index;
|
|
678
|
+
globalThis.debug(`synced ${blocksSynced} ${blocksSynced > 1 ? 'blocks' : 'block'}`);
|
|
679
|
+
const start = (this.#blocks.length - blocksSynced) - 1;
|
|
680
|
+
if (this.#machine)
|
|
681
|
+
await this.#loadBlocks(this.blocks.slice(start));
|
|
682
|
+
await this.updateState(new BlockMessage(this.#blocks[this.#blocks.length - 1]));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
console.log(error);
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
async #getLatestBlock() {
|
|
691
|
+
let promises = [];
|
|
692
|
+
let data = await new globalThis.peernet.protos['peernet-request']({ request: 'lastBlock' });
|
|
693
|
+
let node = await globalThis.peernet.prepareMessage(data);
|
|
694
|
+
for (const peer of globalThis.peernet?.connections) {
|
|
695
|
+
if (peer.connected && peer.version === this.version) {
|
|
696
|
+
promises.push(async () => {
|
|
697
|
+
try {
|
|
698
|
+
const result = await peer.request(node.encoded);
|
|
699
|
+
return { result, peer };
|
|
700
|
+
}
|
|
701
|
+
catch (error) {
|
|
702
|
+
throw error;
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
promises = await this.promiseRequests(promises);
|
|
708
|
+
let latest = { index: 0, hash: '0x0', previousHash: '0x0' };
|
|
709
|
+
promises = promises.sort((a, b) => b.index - a.index);
|
|
710
|
+
if (promises.length > 0)
|
|
711
|
+
latest = promises[0].value;
|
|
712
|
+
if (latest.hash && latest.hash !== '0x0') {
|
|
713
|
+
let message = await globalThis.peernet.get(latest.hash, 'block');
|
|
714
|
+
message = await new BlockMessage(message);
|
|
715
|
+
const hash = await message.hash();
|
|
716
|
+
if (hash !== latest.hash)
|
|
717
|
+
throw new Error('invalid block @getLatestBlock');
|
|
718
|
+
latest = { ...message.decoded, hash };
|
|
719
|
+
const peer = promises[0].peer;
|
|
720
|
+
if (peer.connected && peer.version === this.version) {
|
|
721
|
+
let data = await new globalThis.peernet.protos['peernet-request']({ request: 'knownBlocks' });
|
|
722
|
+
let node = await globalThis.peernet.prepareMessage(data);
|
|
723
|
+
let message = await peer.request(node);
|
|
724
|
+
message = await new globalThis.peernet.protos['peernet-response'](message);
|
|
725
|
+
this.#knownBlocks = message.decoded.response;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return latest;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
*
|
|
732
|
+
* @param {Block[]} blocks
|
|
733
|
+
*/
|
|
734
|
+
async #loadBlocks(blocks) {
|
|
735
|
+
let poolTransactionKeys = await globalThis.transactionPoolStore.keys();
|
|
736
|
+
for (const block of blocks) {
|
|
737
|
+
if (block && !block.loaded) {
|
|
738
|
+
for (const transaction of block.transactions) {
|
|
739
|
+
if (poolTransactionKeys.includes(transaction.hash))
|
|
740
|
+
await globalThis.transactionPoolStore.delete(transaction.hash);
|
|
741
|
+
try {
|
|
742
|
+
await this.#machine.execute(transaction.to, transaction.method, transaction.params);
|
|
743
|
+
if (transaction.to === nativeToken) {
|
|
744
|
+
this.#nativeCalls += 1;
|
|
745
|
+
if (transaction.method === 'burn')
|
|
746
|
+
this.#nativeBurns += 1;
|
|
747
|
+
if (transaction.method === 'mint')
|
|
748
|
+
this.#nativeMints += 1;
|
|
749
|
+
if (transaction.method === 'transfer')
|
|
750
|
+
this.#nativeTransfers += 1;
|
|
751
|
+
}
|
|
752
|
+
this.#totalTransactions += 1;
|
|
753
|
+
}
|
|
754
|
+
catch (error) {
|
|
755
|
+
console.log(error);
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
this.#blocks[block.index].loaded = true;
|
|
760
|
+
globalThis.debug(`loaded block: ${block.hash} @${block.index}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
promiseRequests(promises) {
|
|
766
|
+
return new Promise(async (resolve, reject) => {
|
|
767
|
+
const timeout = setTimeout(() => {
|
|
768
|
+
resolve([{ index: 0, hash: '0x0' }]);
|
|
769
|
+
globalThis.debug('sync timed out');
|
|
770
|
+
}, this.requestTimeout);
|
|
771
|
+
promises = await Promise.allSettled(promises);
|
|
772
|
+
promises = promises.filter(({ status }) => status === 'fulfilled');
|
|
773
|
+
clearTimeout(timeout);
|
|
774
|
+
if (promises.length > 0) {
|
|
775
|
+
promises = promises.map(async ({ value }) => {
|
|
776
|
+
const node = await new globalThis.peernet.protos['peernet-response'](value);
|
|
777
|
+
return { value: node.decoded.response, peer: value.peer };
|
|
778
|
+
});
|
|
779
|
+
promises = await Promise.all(promises);
|
|
780
|
+
resolve(promises);
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
resolve([]);
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
get canSync() {
|
|
788
|
+
if (this.#chainSyncing)
|
|
789
|
+
return false;
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
get shouldSync() {
|
|
793
|
+
if (this.canSync ||
|
|
794
|
+
this.#resolveErrored ||
|
|
795
|
+
!this.canSync && this.#lastResolvedTime + this.resolveTimeout > new Date().getTime())
|
|
796
|
+
return true;
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
async triggerSync() {
|
|
800
|
+
const latest = await this.#getLatestBlock();
|
|
801
|
+
return this.syncChain(latest);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
globalThis.BigNumber = BigNumber;
|
|
806
|
+
// check if browser or local
|
|
807
|
+
class Chain extends State {
|
|
808
|
+
#state;
|
|
809
|
+
#slotTime = 10000;
|
|
810
|
+
id;
|
|
811
|
+
utils;
|
|
812
|
+
/** {Address[]} */
|
|
813
|
+
#validators = [];
|
|
814
|
+
/** {Boolean} */
|
|
815
|
+
#runningEpoch = false;
|
|
816
|
+
#participants = [];
|
|
521
817
|
#participating = false;
|
|
522
818
|
#jail = [];
|
|
523
|
-
#knownBlocks = [];
|
|
524
819
|
constructor() {
|
|
525
820
|
super();
|
|
526
821
|
return this.#init();
|
|
527
822
|
}
|
|
528
|
-
get nativeMints() {
|
|
529
|
-
return this.#nativeMints;
|
|
530
|
-
}
|
|
531
|
-
get nativeBurns() {
|
|
532
|
-
return this.#nativeBurns;
|
|
533
|
-
}
|
|
534
|
-
get nativeTransfers() {
|
|
535
|
-
return this.#nativeTransfers;
|
|
536
|
-
}
|
|
537
|
-
get totalTransactions() {
|
|
538
|
-
return this.#totalTransactions;
|
|
539
|
-
}
|
|
540
|
-
get nativeCalls() {
|
|
541
|
-
return this.#nativeCalls;
|
|
542
|
-
}
|
|
543
|
-
get totalSize() {
|
|
544
|
-
return this.#totalSize;
|
|
545
|
-
}
|
|
546
|
-
get lib() {
|
|
547
|
-
return lib;
|
|
548
|
-
}
|
|
549
|
-
get lastBlock() {
|
|
550
|
-
return this.#lastBlock;
|
|
551
|
-
}
|
|
552
823
|
get nativeToken() {
|
|
553
824
|
return addresses.nativeToken;
|
|
554
825
|
}
|
|
555
826
|
get validators() {
|
|
556
827
|
return [...this.#validators];
|
|
557
828
|
}
|
|
558
|
-
get blocks() {
|
|
559
|
-
return [...this.#blocks];
|
|
560
|
-
}
|
|
561
829
|
async hasTransactionToHandle() {
|
|
562
830
|
const size = await globalThis.transactionPoolStore.size();
|
|
563
831
|
if (size > 0)
|
|
@@ -607,71 +875,6 @@ class Chain extends Contract {
|
|
|
607
875
|
console.log('handle native contracts');
|
|
608
876
|
// handle native contracts
|
|
609
877
|
}
|
|
610
|
-
promiseRequests(promises) {
|
|
611
|
-
return new Promise(async (resolve, reject) => {
|
|
612
|
-
const timeout = setTimeout(() => {
|
|
613
|
-
resolve([{ index: 0, hash: '0x0' }]);
|
|
614
|
-
globalThis.debug('sync timed out');
|
|
615
|
-
}, this.requestTimeout);
|
|
616
|
-
promises = await Promise.allSettled(promises);
|
|
617
|
-
promises = promises.filter(({ status }) => status === 'fulfilled');
|
|
618
|
-
clearTimeout(timeout);
|
|
619
|
-
if (promises.length > 0) {
|
|
620
|
-
promises = promises.map(async ({ value }) => {
|
|
621
|
-
const node = await new globalThis.peernet.protos['peernet-response'](value.result);
|
|
622
|
-
return { value: node.decoded.response, peer: value.peer };
|
|
623
|
-
});
|
|
624
|
-
promises = await Promise.all(promises);
|
|
625
|
-
resolve(promises);
|
|
626
|
-
}
|
|
627
|
-
else {
|
|
628
|
-
resolve([]);
|
|
629
|
-
}
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
getLatestBlock() {
|
|
633
|
-
return this.#getLatestBlock();
|
|
634
|
-
}
|
|
635
|
-
async #getLatestBlock() {
|
|
636
|
-
let promises = [];
|
|
637
|
-
let data = await new globalThis.peernet.protos['peernet-request']({ request: 'lastBlock' });
|
|
638
|
-
let node = await globalThis.peernet.prepareMessage(data);
|
|
639
|
-
for (const peer of globalThis.peernet?.connections) {
|
|
640
|
-
if (peer.connected && peer.version === this.version) {
|
|
641
|
-
promises.push(async () => {
|
|
642
|
-
try {
|
|
643
|
-
const result = await peer.request(node.encoded);
|
|
644
|
-
return { result, peer };
|
|
645
|
-
}
|
|
646
|
-
catch (error) {
|
|
647
|
-
throw error;
|
|
648
|
-
}
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
promises = await this.promiseRequests(promises);
|
|
653
|
-
let latest = { index: 0, hash: '0x0', previousHash: '0x0' };
|
|
654
|
-
promises = promises.sort((a, b) => b.index - a.index);
|
|
655
|
-
if (promises.length > 0)
|
|
656
|
-
latest = promises[0].value;
|
|
657
|
-
if (latest.hash && latest.hash !== '0x0') {
|
|
658
|
-
let message = await globalThis.peernet.get(latest.hash, 'block');
|
|
659
|
-
message = await new BlockMessage(message);
|
|
660
|
-
const hash = await message.hash();
|
|
661
|
-
if (hash !== latest.hash)
|
|
662
|
-
throw new Error('invalid block @getLatestBlock');
|
|
663
|
-
latest = { ...message.decoded, hash };
|
|
664
|
-
const peer = promises[0].peer;
|
|
665
|
-
if (peer.connected && peer.version === this.version) {
|
|
666
|
-
let data = await new globalThis.peernet.protos['peernet-request']({ request: 'knownBlocks' });
|
|
667
|
-
let node = await globalThis.peernet.prepareMessage(data);
|
|
668
|
-
let message = await peer.request(node);
|
|
669
|
-
message = await new globalThis.peernet.protos['peernet-response'](message);
|
|
670
|
-
this.#knownBlocks = message.decoded.response;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
return latest;
|
|
674
|
-
}
|
|
675
878
|
async #clearPool() {
|
|
676
879
|
await globalThis.transactionPoolStore.clear();
|
|
677
880
|
}
|
|
@@ -703,7 +906,7 @@ class Chain extends Contract {
|
|
|
703
906
|
if (!initialized)
|
|
704
907
|
await this.#setup();
|
|
705
908
|
this.utils = { BigNumber, formatUnits, parseUnits };
|
|
706
|
-
this.#state = new State()
|
|
909
|
+
// this.#state = new State()
|
|
707
910
|
await globalThis.peernet.addRequestHandler('bw-request-message', () => {
|
|
708
911
|
return new BWMessage(globalThis.peernet.client.bw) || { up: 0, down: 0 };
|
|
709
912
|
});
|
|
@@ -713,43 +916,13 @@ class Chain extends Contract {
|
|
|
713
916
|
// })
|
|
714
917
|
await globalThis.peernet.addRequestHandler('transactionPool', this.#transactionPoolHandler.bind(this));
|
|
715
918
|
await globalThis.peernet.addRequestHandler('version', this.#versionHandler.bind(this));
|
|
716
|
-
await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler.bind(this));
|
|
717
|
-
await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler.bind(this));
|
|
718
919
|
globalThis.peernet.subscribe('add-block', this.#addBlock.bind(this));
|
|
719
920
|
globalThis.peernet.subscribe('invalid-transaction', this.#invalidTransaction.bind(this));
|
|
720
921
|
globalThis.peernet.subscribe('send-transaction', this.#sendTransaction.bind(this));
|
|
721
922
|
globalThis.peernet.subscribe('validator:timeout', this.#validatorTimeout.bind(this));
|
|
722
923
|
globalThis.pubsub.subscribe('peer:connected', this.#peerConnected.bind(this));
|
|
723
924
|
// todo some functions rely on state
|
|
724
|
-
|
|
725
|
-
let localBlock;
|
|
726
|
-
try {
|
|
727
|
-
localBlock = await globalThis.chainStore.get('lastBlock');
|
|
728
|
-
}
|
|
729
|
-
catch {
|
|
730
|
-
await globalThis.chainStore.put('lastBlock', '0x0');
|
|
731
|
-
localBlock = await globalThis.chainStore.get('lastBlock');
|
|
732
|
-
}
|
|
733
|
-
localBlock = new TextDecoder().decode(localBlock);
|
|
734
|
-
if (localBlock && localBlock !== '0x0') {
|
|
735
|
-
localBlock = await globalThis.peernet.get(localBlock, 'block');
|
|
736
|
-
localBlock = await new BlockMessage(localBlock);
|
|
737
|
-
this.#lastBlock = { ...localBlock.decoded, hash: await localBlock.hash() };
|
|
738
|
-
}
|
|
739
|
-
else {
|
|
740
|
-
if (globalThis.peernet?.connections.length > 0) {
|
|
741
|
-
const latestBlock = await this.#getLatestBlock();
|
|
742
|
-
await this.#syncChain(latestBlock);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
catch (error) {
|
|
747
|
-
console.log({ e: error });
|
|
748
|
-
}
|
|
749
|
-
// load local blocks
|
|
750
|
-
await this.resolveBlocks();
|
|
751
|
-
this.#machine = await new Machine(this.#blocks);
|
|
752
|
-
await this.#loadBlocks(this.#blocks);
|
|
925
|
+
await super.init();
|
|
753
926
|
globalThis.globalThis.pubsub.publish('chain:ready', true);
|
|
754
927
|
return this;
|
|
755
928
|
}
|
|
@@ -763,82 +936,6 @@ class Chain extends Contract {
|
|
|
763
936
|
}, validatorInfo.timeout);
|
|
764
937
|
this.#jail.push(validatorInfo.address);
|
|
765
938
|
}
|
|
766
|
-
async triggerSync() {
|
|
767
|
-
const latest = await this.#getLatestBlock();
|
|
768
|
-
return this.syncChain(latest);
|
|
769
|
-
}
|
|
770
|
-
async syncChain(lastBlock) {
|
|
771
|
-
if (!lastBlock)
|
|
772
|
-
lastBlock = await this.#getLatestBlock();
|
|
773
|
-
if (this.#chainSyncing) {
|
|
774
|
-
console.log('already syncing');
|
|
775
|
-
if (!this.#lastBlockInQue || lastBlock.index > this.#lastBlockInQue.index)
|
|
776
|
-
this.#lastBlockInQue = lastBlock;
|
|
777
|
-
return 'syncing';
|
|
778
|
-
}
|
|
779
|
-
console.log('starting sync');
|
|
780
|
-
this.#chainSyncing = true;
|
|
781
|
-
const syncPromise = (lastBlock) => new Promise(async (resolve, reject) => {
|
|
782
|
-
const timeout = setTimeout(() => {
|
|
783
|
-
this.#chainSyncing = false;
|
|
784
|
-
this.#syncErrorCount = 0;
|
|
785
|
-
reject('timedOut');
|
|
786
|
-
}, 60000);
|
|
787
|
-
try {
|
|
788
|
-
await this.#syncChain(lastBlock);
|
|
789
|
-
clearTimeout(timeout);
|
|
790
|
-
resolve(true);
|
|
791
|
-
}
|
|
792
|
-
catch (error) {
|
|
793
|
-
clearTimeout(timeout);
|
|
794
|
-
reject(error);
|
|
795
|
-
}
|
|
796
|
-
});
|
|
797
|
-
if (globalThis.peernet.connections.length === 0)
|
|
798
|
-
return 'connectionless';
|
|
799
|
-
try {
|
|
800
|
-
await syncPromise(lastBlock);
|
|
801
|
-
}
|
|
802
|
-
catch (error) {
|
|
803
|
-
console.log(error);
|
|
804
|
-
this.#syncErrorCount += 1;
|
|
805
|
-
if (this.#syncErrorCount < 3)
|
|
806
|
-
await syncPromise(lastBlock);
|
|
807
|
-
this.#chainSyncing = false;
|
|
808
|
-
this.#syncErrorCount = 0;
|
|
809
|
-
return 'errored';
|
|
810
|
-
}
|
|
811
|
-
if (lastBlock.index === this.#lastBlockInQue?.index)
|
|
812
|
-
this.#lastBlockInQue = undefined;
|
|
813
|
-
this.#syncErrorCount = 0;
|
|
814
|
-
this.#chainSyncing = false;
|
|
815
|
-
if (this.#lastBlockInQue)
|
|
816
|
-
return this.syncChain(this.#lastBlockInQue);
|
|
817
|
-
return 'synced';
|
|
818
|
-
}
|
|
819
|
-
async #syncChain(lastBlock) {
|
|
820
|
-
if (this.#knownBlocks?.length === Number(lastBlock.index) + 1) {
|
|
821
|
-
let promises = [];
|
|
822
|
-
promises = await Promise.allSettled(this.#knownBlocks.map(async (address) => {
|
|
823
|
-
const has = await globalThis.peernet.has(address, 'block');
|
|
824
|
-
return { has, address };
|
|
825
|
-
}));
|
|
826
|
-
promises = promises.filter(({ status, value }) => status === 'fulfilled' && !value.has);
|
|
827
|
-
await Promise.allSettled(promises.map(({ value }) => this.getAndPutBlock(value.address)));
|
|
828
|
-
}
|
|
829
|
-
if (!this.lastBlock || Number(this.lastBlock.index) < Number(lastBlock.index)) {
|
|
830
|
-
// TODO: check if valid
|
|
831
|
-
const localIndex = this.lastBlock ? this.lastBlock.index : 0;
|
|
832
|
-
const index = lastBlock.index;
|
|
833
|
-
await this.resolveBlock(lastBlock.hash);
|
|
834
|
-
let blocksSynced = localIndex > 0 ? (localIndex > index ? localIndex - index : index - localIndex) : index;
|
|
835
|
-
globalThis.debug(`synced ${blocksSynced} ${blocksSynced > 1 ? 'blocks' : 'block'}`);
|
|
836
|
-
const start = (this.#blocks.length - blocksSynced) - 1;
|
|
837
|
-
if (this.#machine)
|
|
838
|
-
await this.#loadBlocks(this.blocks.slice(start));
|
|
839
|
-
await this.#updateState(new BlockMessage(this.#blocks[this.#blocks.length - 1]));
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
939
|
async #prepareRequest(request) {
|
|
843
940
|
let node = await new globalThis.peernet.protos['peernet-request']({ request });
|
|
844
941
|
return globalThis.peernet.prepareMessage(node);
|
|
@@ -854,12 +951,10 @@ class Chain extends Contract {
|
|
|
854
951
|
return;
|
|
855
952
|
const lastBlock = await this.#makeRequest(peer, 'lastBlock');
|
|
856
953
|
if (Object.keys(lastBlock).length > 0) {
|
|
857
|
-
if (this
|
|
954
|
+
if (!this.lastBlock || !this.blocks[this.blocks.length - 1].loaded || lastBlock && lastBlock.index > this.lastBlock?.index) {
|
|
858
955
|
// this.#knownBlocks = await this.#makeRequest(peer, 'knownBlocks')
|
|
859
|
-
this.#resolveErrored = false;
|
|
860
956
|
await this.syncChain(lastBlock);
|
|
861
|
-
if (await this.hasTransactionToHandle() &&
|
|
862
|
-
this.#runEpoch();
|
|
957
|
+
// if (await this.hasTransactionToHandle() && this.#participating) this.#runEpoch()
|
|
863
958
|
}
|
|
864
959
|
}
|
|
865
960
|
}
|
|
@@ -871,110 +966,10 @@ class Chain extends Contract {
|
|
|
871
966
|
async #versionHandler() {
|
|
872
967
|
return new globalThis.peernet.protos['peernet-response']({ response: { version: this.version } });
|
|
873
968
|
}
|
|
874
|
-
async #lastBlockHandler() {
|
|
875
|
-
return new globalThis.peernet.protos['peernet-response']({ response: { hash: this.#lastBlock?.hash, index: this.#lastBlock?.index } });
|
|
876
|
-
}
|
|
877
|
-
async #knownBlocksHandler() {
|
|
878
|
-
return new globalThis.peernet.protos['peernet-response']({ response: { blocks: this.#blocks.map((block) => block.hash) } });
|
|
879
|
-
}
|
|
880
|
-
async getAndPutBlock(hash) {
|
|
881
|
-
// todo peernet resolves undefined blocks....
|
|
882
|
-
let block = await globalThis.peernet.get(hash, 'block');
|
|
883
|
-
if (block !== undefined) {
|
|
884
|
-
block = await new BlockMessage(block);
|
|
885
|
-
const { index } = block.decoded;
|
|
886
|
-
if (this.#blocks[index] && this.#blocks[index].hash !== block.hash)
|
|
887
|
-
throw `invalid block ${hash} @${index}`;
|
|
888
|
-
if (!await globalThis.peernet.has(hash, 'block'))
|
|
889
|
-
await globalThis.peernet.put(hash, block.encoded, 'block');
|
|
890
|
-
}
|
|
891
|
-
return block;
|
|
892
|
-
}
|
|
893
|
-
async resolveBlock(hash) {
|
|
894
|
-
if (!hash)
|
|
895
|
-
throw new Error(`expected hash, got: ${hash}`);
|
|
896
|
-
if (hash === '0x0')
|
|
897
|
-
return;
|
|
898
|
-
const index = this.#blockHashMap.get(hash);
|
|
899
|
-
if (this.#blocks[index]) {
|
|
900
|
-
if (this.#blocks[index].previousHash !== '0x0') {
|
|
901
|
-
return this.resolveBlock(this.#blocks[index].previousHash);
|
|
902
|
-
}
|
|
903
|
-
else {
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
try {
|
|
908
|
-
const block = await this.getAndPutBlock(hash);
|
|
909
|
-
const { previousHash, index } = block.decoded;
|
|
910
|
-
const size = block.encoded.length > 0 ? block.encoded.length : block.encoded.byteLength;
|
|
911
|
-
this.#totalSize += size;
|
|
912
|
-
this.#blocks[index] = { hash, ...block.decoded };
|
|
913
|
-
this.#blockHashMap.set(hash, index);
|
|
914
|
-
console.log(`resolved block: ${hash} @${index} ${formatBytes(size)}`);
|
|
915
|
-
this.#lastResolved = Date.now();
|
|
916
|
-
if (previousHash !== '0x0') {
|
|
917
|
-
return this.resolveBlock(previousHash);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
catch (error) {
|
|
921
|
-
this.#resolveErrored = true;
|
|
922
|
-
console.error(error);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
async resolveBlocks() {
|
|
926
|
-
try {
|
|
927
|
-
const localBlock = await globalThis.chainStore.get('lastBlock');
|
|
928
|
-
const hash = new TextDecoder().decode(localBlock);
|
|
929
|
-
if (hash && hash !== '0x0')
|
|
930
|
-
await this.resolveBlock(hash);
|
|
931
|
-
this.#lastBlock = this.#blocks[this.#blocks.length - 1];
|
|
932
|
-
}
|
|
933
|
-
catch {
|
|
934
|
-
await globalThis.chainStore.put('lastBlock', new TextEncoder().encode('0x0'));
|
|
935
|
-
return this.resolveBlocks();
|
|
936
|
-
// console.log(e);
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
/**
|
|
940
|
-
*
|
|
941
|
-
* @param {Block[]} blocks
|
|
942
|
-
*/
|
|
943
|
-
async #loadBlocks(blocks) {
|
|
944
|
-
let poolTransactionKeys = await globalThis.transactionPoolStore.keys();
|
|
945
|
-
for (const block of blocks) {
|
|
946
|
-
if (block && !block.loaded) {
|
|
947
|
-
for (const transaction of block.transactions) {
|
|
948
|
-
if (poolTransactionKeys.includes(transaction.hash))
|
|
949
|
-
await globalThis.transactionPoolStore.delete(transaction.hash);
|
|
950
|
-
try {
|
|
951
|
-
await this.#machine.execute(transaction.to, transaction.method, transaction.params);
|
|
952
|
-
if (transaction.to === nativeToken) {
|
|
953
|
-
this.#nativeCalls += 1;
|
|
954
|
-
if (transaction.method === 'burn')
|
|
955
|
-
this.#nativeBurns += 1;
|
|
956
|
-
if (transaction.method === 'mint')
|
|
957
|
-
this.#nativeMints += 1;
|
|
958
|
-
if (transaction.method === 'transfer')
|
|
959
|
-
this.#nativeTransfers += 1;
|
|
960
|
-
}
|
|
961
|
-
this.#totalTransactions += 1;
|
|
962
|
-
}
|
|
963
|
-
catch (error) {
|
|
964
|
-
console.log(error);
|
|
965
|
-
return false;
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
this.#blocks[block.index].loaded = true;
|
|
969
|
-
globalThis.debug(`loaded block: ${block.hash} @${block.index}`);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
return true;
|
|
973
|
-
}
|
|
974
969
|
async #executeTransaction({ hash, from, to, method, params, nonce }) {
|
|
975
970
|
try {
|
|
976
|
-
let result = await this
|
|
977
|
-
// if (!result) result = this
|
|
971
|
+
let result = await this.machine.execute(to, method, params);
|
|
972
|
+
// if (!result) result = this.machine.state
|
|
978
973
|
globalThis.pubsub.publish(`transaction.completed.${hash}`, { status: 'fulfilled', hash });
|
|
979
974
|
return result || 'no state change';
|
|
980
975
|
}
|
|
@@ -992,7 +987,7 @@ class Chain extends Contract {
|
|
|
992
987
|
const hash = await blockMessage.hash();
|
|
993
988
|
await globalThis.blockStore.put(hash, blockMessage.encoded);
|
|
994
989
|
if (this.lastBlock.index < blockMessage.decoded.index)
|
|
995
|
-
await this
|
|
990
|
+
await this.updateState(blockMessage);
|
|
996
991
|
globalThis.debug(`added block: ${hash}`);
|
|
997
992
|
let promises = [];
|
|
998
993
|
let contracts = [];
|
|
@@ -1014,7 +1009,7 @@ class Chain extends Contract {
|
|
|
1014
1009
|
}
|
|
1015
1010
|
// todo finish state
|
|
1016
1011
|
// for (const contract of contracts) {
|
|
1017
|
-
// const state = await this
|
|
1012
|
+
// const state = await this.machine.get(contract, 'state')
|
|
1018
1013
|
// // await stateStore.put(contract, state)
|
|
1019
1014
|
// console.log(state);
|
|
1020
1015
|
// }
|
|
@@ -1024,12 +1019,6 @@ class Chain extends Contract {
|
|
|
1024
1019
|
console.log({ e: error });
|
|
1025
1020
|
}
|
|
1026
1021
|
}
|
|
1027
|
-
async #updateState(message) {
|
|
1028
|
-
const hash = await message.hash();
|
|
1029
|
-
this.#lastBlock = { hash, ...message.decoded };
|
|
1030
|
-
// await this.state.updateState(message)
|
|
1031
|
-
await globalThis.chainStore.put('lastBlock', hash);
|
|
1032
|
-
}
|
|
1033
1022
|
async participate(address) {
|
|
1034
1023
|
// TODO: validate participant
|
|
1035
1024
|
// hold min amount of 50k ART for 7 days
|
|
@@ -1077,7 +1066,7 @@ class Chain extends Contract {
|
|
|
1077
1066
|
for (let transaction of transactions) {
|
|
1078
1067
|
const hash = await transaction.hash();
|
|
1079
1068
|
const doubleTransactions = [];
|
|
1080
|
-
for (const block of this
|
|
1069
|
+
for (const block of this.blocks) {
|
|
1081
1070
|
for (const transaction of block.transactions) {
|
|
1082
1071
|
if (transaction.hash === hash) {
|
|
1083
1072
|
doubleTransactions.push(hash);
|
|
@@ -1168,7 +1157,7 @@ class Chain extends Contract {
|
|
|
1168
1157
|
let blockMessage = await new BlockMessage(block);
|
|
1169
1158
|
const hash = await blockMessage.hash();
|
|
1170
1159
|
await globalThis.peernet.put(hash, blockMessage.encoded, 'block');
|
|
1171
|
-
await this
|
|
1160
|
+
await this.updateState(blockMessage);
|
|
1172
1161
|
globalThis.debug(`created block: ${hash}`);
|
|
1173
1162
|
globalThis.peernet.publish('add-block', blockMessage.encoded);
|
|
1174
1163
|
globalThis.pubsub.publish('add-block', blockMessage.decoded);
|
|
@@ -1177,7 +1166,7 @@ class Chain extends Contract {
|
|
|
1177
1166
|
console.log(error);
|
|
1178
1167
|
throw new Error(`invalid block ${block}`);
|
|
1179
1168
|
}
|
|
1180
|
-
// data = await this
|
|
1169
|
+
// data = await this.machine.execute(to, method, params)
|
|
1181
1170
|
// transactionStore.put(message.hash, message.encoded)
|
|
1182
1171
|
}
|
|
1183
1172
|
async #sendTransaction(transaction) {
|
|
@@ -1241,7 +1230,7 @@ class Chain extends Contract {
|
|
|
1241
1230
|
*/
|
|
1242
1231
|
internalCall(sender, contract, method, parameters) {
|
|
1243
1232
|
globalThis.msg = this.#createMessage(sender);
|
|
1244
|
-
return this
|
|
1233
|
+
return this.machine.execute(contract, method, parameters);
|
|
1245
1234
|
}
|
|
1246
1235
|
/**
|
|
1247
1236
|
*
|
|
@@ -1252,19 +1241,19 @@ class Chain extends Contract {
|
|
|
1252
1241
|
*/
|
|
1253
1242
|
call(contract, method, parameters) {
|
|
1254
1243
|
globalThis.msg = this.#createMessage();
|
|
1255
|
-
return this
|
|
1244
|
+
return this.machine.execute(contract, method, parameters);
|
|
1256
1245
|
}
|
|
1257
1246
|
staticCall(contract, method, parameters) {
|
|
1258
1247
|
globalThis.msg = this.#createMessage();
|
|
1259
|
-
return this
|
|
1248
|
+
return this.machine.get(contract, method, parameters);
|
|
1260
1249
|
}
|
|
1261
1250
|
delegate(contract, method, parameters) {
|
|
1262
1251
|
globalThis.msg = this.#createMessage();
|
|
1263
|
-
return this
|
|
1252
|
+
return this.machine.execute(contract, method, parameters);
|
|
1264
1253
|
}
|
|
1265
1254
|
staticDelegate(contract, method, parameters) {
|
|
1266
1255
|
globalThis.msg = this.#createMessage();
|
|
1267
|
-
return this
|
|
1256
|
+
return this.machine.get(contract, method, parameters);
|
|
1268
1257
|
}
|
|
1269
1258
|
mint(to, amount) {
|
|
1270
1259
|
return this.call(addresses.nativeToken, 'mint', [to, amount]);
|
|
@@ -1279,7 +1268,7 @@ class Chain extends Contract {
|
|
|
1279
1268
|
return this.staticCall(addresses.contractFactory, 'contracts');
|
|
1280
1269
|
}
|
|
1281
1270
|
deleteAll() {
|
|
1282
|
-
return this
|
|
1271
|
+
return this.machine.deleteAll();
|
|
1283
1272
|
}
|
|
1284
1273
|
/**
|
|
1285
1274
|
* lookup an address for a registered name using the builtin nameService
|