@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.
- package/exports/chain.js +1071 -0
- package/exports/node.js +23 -0
- package/exports/typings/chain.d.ts +83 -0
- package/exports/typings/config/config.d.ts +1 -0
- package/exports/typings/config/main.d.ts +5 -0
- package/exports/typings/config/protocol.d.ts +6 -0
- package/exports/typings/contract.d.ts +29 -0
- package/exports/typings/fee/config.d.ts +4 -0
- package/exports/typings/machine.d.ts +26 -0
- package/exports/typings/node.d.ts +9 -0
- package/exports/typings/protocol.d.ts +4 -0
- package/exports/typings/state.d.ts +5 -0
- package/exports/typings/transaction.d.ts +47 -0
- package/exports/typings/typer.d.ts +6 -0
- package/package.json +12 -6
- package/CHANGELOG.md +0 -14
- package/demo/index.html +0 -25
- package/examples/contracts/token.js +0 -7
- package/plugins/bundle.js +0 -18
- package/src/chain.js +0 -716
- package/src/config/config.js +0 -14
- package/src/config/main.js +0 -4
- package/src/config/protocol.js +0 -5
- package/src/contract.js +0 -52
- package/src/fee/config.js +0 -3
- package/src/machine.js +0 -215
- package/src/node.js +0 -24
- package/src/protocol.js +0 -4
- package/src/state.js +0 -31
- package/src/transaction.js +0 -234
- package/src/type.index.d.ts +0 -21
- package/src/typer.js +0 -19
- package/test/chain.js +0 -120
- package/test/contracts/token.js +0 -40
- package/test/create-genesis.js +0 -66
- package/test/index.js +0 -1
- package/tsconfig.js +0 -15
- package/workers/block-worker.js +0 -40
- package/workers/machine-worker.js +0 -219
- package/workers/pool-worker.js +0 -28
- package/workers/transaction-worker.js +0 -20
- package/workers/workers.js +0 -9
package/exports/chain.js
ADDED
|
@@ -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 };
|