trac-msb 0.1.76 → 0.1.78

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/dump/src/index.js DELETED
@@ -1,1005 +0,0 @@
1
- /** @typedef {import('pear-interface')} */ /* global Pear */
2
- import Autobase from 'autobase';
3
- import ReadyResource from 'ready-resource';
4
- import b4a from 'b4a';
5
- import Hyperbee from 'hyperbee';
6
- import readline from 'readline';
7
- import { verifyDag, sleep, createHash } from './utils/functions.js';
8
- import PeerWallet from "trac-wallet"
9
- import tty from 'tty';
10
- import Corestore from 'corestore';
11
- import MsgUtils from './utils/msgUtils.js';
12
- import {
13
- LISTENER_TIMEOUT,
14
- EntryType,
15
- OperationType,
16
- EventType,
17
- WHITELIST_SLEEP_INTERVAL,
18
- UPDATER_INTERVAL,
19
- MAX_INDEXERS,
20
- MIN_INDEXERS,
21
- WHITELIST_PREFIX,
22
- TRAC_NAMESPACE
23
- } from './utils/constants.js';
24
- import Network from './network.js';
25
- import Check from './utils/check.js';
26
- import Protomux from 'protomux'
27
- import c from 'compact-encoding'
28
- import Hypercore from 'hypercore'
29
-
30
- export class MainSettlementBus extends ReadyResource {
31
- // Internal flags
32
- #shouldListenToAdminEvents = false;
33
- #shouldListenToWriterEvents = false;
34
- #isStreaming = false;
35
-
36
- // internal attributes
37
- #STORES_DIRECTORY;
38
- #KEY_PAIR_PATH;
39
- #bootstrap;
40
- #channel;
41
- #store;
42
- #bee;
43
- #swarm;
44
- #dht_node;
45
- #dht_bootstrap;
46
- #base;
47
- #writingKey;
48
- #enable_txchannel;
49
- #is_indexer;
50
- #enable_wallet;
51
- #wallet;
52
- #replicate;
53
- #network;
54
- #opts;
55
- #signature_whitelist;
56
- #readline_instance;
57
- #enable_txlogs;
58
- #disable_rate_limit;
59
-
60
- constructor(options = {}) {
61
- super();
62
- this.check = new Check();
63
- this.#initInternalAttributes(options);
64
- this.msbListener();
65
- this.#boot();
66
- this.#setupInternalListeners();
67
- this.#network = new Network(this.#base);
68
- this.ready().catch(noop);
69
- }
70
-
71
- #initInternalAttributes(options) {
72
- this.#STORES_DIRECTORY = options.stores_directory;
73
- this.#KEY_PAIR_PATH = `${this.STORES_DIRECTORY}${options.store_name}/db/keypair.json`
74
- this.#bootstrap = options.bootstrap || null;
75
- this.#channel = b4a.alloc(32).fill(options.channel) || null;
76
- this.#store = new Corestore(this.STORES_DIRECTORY + options.store_name);
77
- this.#bee = null;
78
- this.#swarm = null;
79
- this.#dht_bootstrap = ['116.202.214.149:10001', '157.180.12.214:10001', 'node1.hyperdht.org:49737', 'node2.hyperdht.org:49737', 'node3.hyperdht.org:49737'];
80
- this.#dht_node = null;
81
- this.#base = null;
82
- this.#writingKey = null;
83
- this.#enable_txchannel = options.enable_txchannel !== false;
84
- this.#enable_txlogs = options.enable_txlogs === true;
85
- this.#is_indexer = false;
86
- this.#enable_wallet = options.enable_wallet !== false;
87
- this.#disable_rate_limit = options.disable_rate_limit === true;
88
- this.#wallet = new PeerWallet(options);
89
- this.#replicate = options.replicate !== false;
90
- this.#signature_whitelist = options.signature_whitelist !== undefined && Array.isArray(options.signature_whitelist) ? options.signature_whitelist : [];
91
- this.#opts = options;
92
- this.#readline_instance = null;
93
- this.enable_interactive_mode = options.enable_interactive_mode !== false;
94
- if (this.enable_interactive_mode !== false) {
95
- try {
96
- this.#readline_instance = readline.createInterface({
97
- input: new tty.ReadStream(0),
98
- output: new tty.WriteStream(1)
99
- });
100
- } catch (e) { }
101
- }
102
- }
103
-
104
- get STORES_DIRECTORY() {
105
- return this.#STORES_DIRECTORY;
106
- }
107
-
108
- get KEY_PAIR_PATH() {
109
- return this.#KEY_PAIR_PATH;
110
- }
111
-
112
- get base() {
113
- return this.#base;
114
- }
115
-
116
- get bootstrap() {
117
- return this.#bootstrap;
118
- }
119
-
120
- getChannel() {
121
- return this.#channel;
122
- }
123
-
124
- getSwarm() {
125
- return this.#swarm;
126
- }
127
-
128
- getNetwork() {
129
- return this.#network;
130
- }
131
-
132
- #boot() {
133
- const _this = this;
134
- this.#base = new Autobase(this.#store, this.#bootstrap, {
135
- ackInterval : 1000,
136
- valueEncoding: 'json',
137
- open: this.#setupHyperbee.bind(this),
138
- apply: this.#apply.bind(this),
139
- })
140
- this.#base.on(EventType.WARNING, (e) => console.log(e))
141
- }
142
-
143
- #setupHyperbee(store) {
144
- this.#bee = new Hyperbee(store.get('view'), {
145
- extension: false,
146
- keyEncoding: 'utf-8',
147
- valueEncoding: 'json'
148
- })
149
- return this.#bee;
150
- }
151
-
152
- async #apply(nodes, view, base) {
153
- const batch = view.batch();
154
- for (const node of nodes) {
155
- const op = node.value;
156
- const handler = this.#getApplyOperationHandler(op.type);
157
- if (handler) {
158
- await handler(op, view, base, node, batch);
159
- } else {
160
- console.warn(`Unknown operation type: ${op.type}`);
161
- }
162
- }
163
- await batch.flush();
164
- await batch.close();
165
- }
166
-
167
- #getApplyOperationHandler(type) {
168
- const handlers = {
169
- [OperationType.TX]: this.#handleApplyTxOperation.bind(this),
170
- [OperationType.ADD_ADMIN]: this.#handleApplyAddAdminOperation.bind(this),
171
- [OperationType.APPEND_WHITELIST]: this.#handleApplyAppendWhitelistOperation.bind(this),
172
- [OperationType.ADD_WRITER]: this.#handleApplyAddWriterOperation.bind(this),
173
- [OperationType.REMOVE_WRITER]: this.#handleApplyRemoveWriterOperation.bind(this),
174
- [OperationType.ADD_INDEXER]: this.#handleApplyAddIndexerOperation.bind(this),
175
- [OperationType.REMOVE_INDEXER]: this.#handleApplyRemoveIndexerOperation.bind(this),
176
- [OperationType.BAN_VALIDATOR]: this.#handleApplyBanValidatorOperation.bind(this),
177
- };
178
- return handlers[type] || null;
179
- }
180
-
181
- async #handleApplyTxOperation(op, view, base, node, batch) {
182
- const postTx = op.value;
183
- if (postTx.op === OperationType.POST_TX &&
184
- (this.#signature_whitelist.length === 0 || this.#signature_whitelist.includes(postTx.bs)) &&
185
- null === await batch.get(op.key) &&
186
- this.check.sanitizePostTx(op) &&
187
- op.key === postTx.tx &&
188
- this.#wallet.verify(b4a.from(postTx.is, 'hex'), b4a.from(postTx.tx + postTx.in), b4a.from(postTx.ipk, 'hex')) &&
189
- this.#wallet.verify(b4a.from(postTx.ws, 'hex'), b4a.from(postTx.tx + postTx.wn), b4a.from(postTx.wp, 'hex')) &&
190
- postTx.tx === await this.generateTx(postTx.bs, this.bootstrap, postTx.wp, postTx.i, postTx.ipk, postTx.ch, postTx.in) &&
191
- b4a.byteLength(JSON.stringify(postTx)) <= 4096
192
- ) {
193
- await batch.put(op.key, op.value);
194
- if (this.#enable_txlogs === true) {
195
- console.log(`TX: ${op.key} appended. Signed length: `, this.#base.view.core.signedLength);
196
- }
197
- }
198
- }
199
-
200
- async #handleApplyAddAdminOperation(op, view, base, node, batch) {
201
- if (!this.check.sanitizeAdminAndWritersOperations(op)) return;
202
- const adminEntry = await batch.get(EntryType.ADMIN);
203
- if (null === adminEntry) {
204
- await this.#addAdminIfNotSet(op, view, node, batch);
205
- }
206
- else if (adminEntry.value.tracPublicKey === op.key) {
207
- await this.#addAdminIfSet(adminEntry.value, op, view, base, batch);
208
- }
209
- }
210
-
211
- async #addAdminIfSet(adminEntry, op, view, base, batch) {
212
- const isMessageVerifed = await this.#verifyMessage(op.value.sig, adminEntry.tracPublicKey, MsgUtils.createMessage(adminEntry.tracPublicKey, op.value.wk, op.value.nonce, op.type));
213
- if (isMessageVerifed) {
214
- const indexersEntry = await batch.get(EntryType.INDEXERS);
215
- if (null !== indexersEntry && indexersEntry.value.includes(adminEntry.tracPublicKey)) {
216
- await base.removeWriter(b4a.from(adminEntry.wk, 'hex'));
217
- await base.addWriter(b4a.from(op.value.wk, 'hex'), { isIndexer: true })
218
- await batch.put(EntryType.ADMIN, {
219
- tracPublicKey: adminEntry.tracPublicKey,
220
- wk: op.value.wk
221
- })
222
- console.log(`Admin updated: ${adminEntry.tracPublicKey}:${op.value.wk}`);
223
- }
224
- }
225
- }
226
-
227
- async #addAdminIfNotSet(op, view, node, batch) {
228
- const isMessageVerifed = await this.#verifyMessage(op.value.sig, op.key, MsgUtils.createMessage(op.key, op.value.wk, op.value.nonce, op.type));
229
-
230
- if (node.from.key.toString('hex') === this.#bootstrap &&
231
- op.value.wk === this.#bootstrap &&
232
- isMessageVerifed
233
- ) {
234
- await batch.put(EntryType.ADMIN, {
235
- tracPublicKey: op.key,
236
- wk: this.#bootstrap
237
- })
238
- const initIndexers = [op.key];
239
- await batch.put(EntryType.INDEXERS, initIndexers);
240
- console.log(`Admin added: ${op.key}:${this.#bootstrap}`);
241
- }
242
- }
243
-
244
- async #handleApplyAppendWhitelistOperation(op, view, base, node, batch) {
245
- const adminEntry = await batch.get(EntryType.ADMIN);
246
- if (null === adminEntry || !this.check.sanitizeIndexerOrWhitelistOperations(op) || !this.#isAdmin(adminEntry.value, node)) return;
247
- // TODO: is the below an admin signature? - yes
248
- const isMessageVerifed = await this.#verifyMessage(op.value.sig, adminEntry.value.tracPublicKey, MsgUtils.createMessage(op.key, op.value.nonce, op.type));
249
- if (!isMessageVerifed) return;
250
- const isWhitelisted = await this.#isWhitelisted2(op.key, batch);
251
- if (isWhitelisted) return;
252
- await this.#createWhitelistEntry(batch, op.key);
253
- }
254
-
255
- async #createWhitelistEntry(batch, pubKey) {
256
- const whitelistKey = WHITELIST_PREFIX + pubKey;
257
- await batch.put(whitelistKey, true);
258
- }
259
-
260
- async #deleteWhitelistEntry(batch, pubKey) {
261
- const whitelistKey = WHITELIST_PREFIX + pubKey;
262
- await batch.del(whitelistKey);
263
- }
264
-
265
- async #handleApplyAddWriterOperation(op, view, base, node, batch) {
266
- const adminEntry = await batch.get(EntryType.ADMIN);
267
- if (null === adminEntry || !this.check.sanitizeAdminAndWritersOperations(op) || !this.#isAdmin(adminEntry.value, node)) return;
268
-
269
- const isWhitelisted = await this.#isWhitelisted2(op.key, batch);
270
- if (!isWhitelisted || op.key !== op.value.pub) return;
271
- // TODO: if the below is not a message signed by admin BUT this handler is supposed to be executed by the admin, then use admin signatures in apply!
272
- const isMessageVerifed = await this.#verifyMessage(op.value.sig, op.key, MsgUtils.createMessage(op.key, op.value.wk, op.value.nonce, op.type));
273
- if (isMessageVerifed) {
274
- await this.#addWriter(op, batch, base);
275
- }
276
- }
277
-
278
- async #addWriter(op, batch, base) {
279
- const nodeEntry = await batch.get(op.key);
280
- if (nodeEntry === null || !nodeEntry.value.isWriter) {
281
- await base.addWriter(b4a.from(op.value.wk, 'hex'), { isIndexer: false })
282
- await batch.put(op.key, {
283
- pub: op.value.pub,
284
- wk: op.value.wk,
285
- isWriter: true,
286
- isIndexer: false
287
- });
288
- let length = await batch.get('wrl');
289
- if (null === length) {
290
- length = 0;
291
- } else {
292
- length = length.value;
293
- }
294
- await batch.put('wri/' + length, op.value.pub);
295
- await batch.put('wrl', length + 1);
296
- console.log(`Writer added: ${op.key}:${op.value.wk}`);
297
- }
298
- }
299
-
300
- async #handleApplyRemoveWriterOperation(op, view, base, node, batch) {
301
- const adminEntry = await batch.get(EntryType.ADMIN);
302
- if (null === adminEntry || !this.check.sanitizeAdminAndWritersOperations(op) || !this.#isAdmin(adminEntry.value, node)) return;
303
- // TODO: if the below is not a message signed by admin BUT this handler is supposed to be executed by the admin, then use admin signatures in apply!
304
- const isMessageVerifed = await this.#verifyMessage(op.value.sig, op.key, MsgUtils.createMessage(op.key, op.value.wk, op.value.nonce, op.type));
305
- if (isMessageVerifed) {
306
- await this.#removeWriter(op, batch, base);
307
- }
308
- }
309
-
310
- async #removeWriter(op, batch, base) {
311
- let nodeEntry = await batch.get(op.key)
312
- if (nodeEntry !== null) {
313
- nodeEntry = nodeEntry.value;
314
- await base.removeWriter(b4a.from(nodeEntry.wk, 'hex'));
315
- nodeEntry.isWriter = false;
316
- if (nodeEntry.isIndexer) {
317
- nodeEntry.isIndexer = false;
318
- const indexersEntry = await batch.get(EntryType.INDEXERS);
319
- if (null !== indexersEntry && indexersEntry.value.includes(op.key)) {
320
- const idx = indexersEntry.value.indexOf(op.key);
321
- if (idx !== -1) {
322
- indexersEntry.value.splice(idx, 1);
323
- await batch.put(EntryType.INDEXERS, indexersEntry.value);
324
- }
325
- }
326
- }
327
-
328
- await batch.put(op.key, nodeEntry);
329
- console.log(`Writer removed: ${op.key}${op.value.wk ? `:${op.value.wk}` : ''}`);
330
-
331
- }
332
- }
333
-
334
- async #handleApplyAddIndexerOperation(op, view, base, node, batch) {
335
- if (!this.check.sanitizeIndexerOrWhitelistOperations(op)) {
336
- return;
337
- }
338
-
339
- const adminEntry = await batch.get(EntryType.ADMIN);
340
- if (null === adminEntry || !this.#isAdmin(adminEntry.value, node)) return;
341
-
342
- if (!this.#isWhitelisted2(op.key, batch)) return;
343
-
344
- const indexersEntry = await batch.get(EntryType.INDEXERS);
345
- if (null === indexersEntry || Array.from(indexersEntry.value).includes(op.key) ||
346
- Array.from(indexersEntry.value).length >= MAX_INDEXERS) {
347
- return;
348
- }
349
- // TODO: is the below an admin signature? -yes
350
- const isMessageVerifed = await this.#verifyMessage(op.value.sig, adminEntry.value.tracPublicKey, MsgUtils.createMessage(op.key, op.value.nonce, op.type))
351
- if (isMessageVerifed) {
352
- await this.#addIndexer(indexersEntry.value, op, batch, base);
353
- }
354
- }
355
-
356
- async #addIndexer(indexersEntry, op, batch, base) {
357
- let nodeEntry = await batch.get(op.key);
358
-
359
- if (nodeEntry !== null && nodeEntry.value.isWriter && !nodeEntry.value.isIndexer) {
360
- nodeEntry = nodeEntry.value;
361
- await base.removeWriter(b4a.from(nodeEntry.wk, 'hex'));
362
- await base.addWriter(b4a.from(nodeEntry.wk, 'hex'), { isIndexer: true })
363
- nodeEntry.isIndexer = true;
364
- await batch.put(op.key, nodeEntry);
365
- indexersEntry.push(op.key);
366
- await batch.put(EntryType.INDEXERS, indexersEntry);
367
- console.log(`Indexer added: ${op.key}:${nodeEntry.wk}`);
368
- }
369
- }
370
-
371
- async #handleApplyRemoveIndexerOperation(op, view, base, node, batch) {
372
- if (!this.check.sanitizeIndexerOrWhitelistOperations(op)) return;
373
- const adminEntry = await batch.get(EntryType.ADMIN);
374
- let indexersEntry = await batch.get(EntryType.INDEXERS);
375
- if (null === adminEntry || !this.#isAdmin(adminEntry.value, node) || null === indexersEntry || !Array.from(indexersEntry.value).includes(op.key) || Array.from(indexersEntry.value).length <= 1) return;
376
- const isMessageVerifed = await this.#verifyMessage(op.value.sig, adminEntry.value.tracPublicKey, MsgUtils.createMessage(op.key, op.value.nonce, op.type))
377
- if (isMessageVerifed) {
378
- let nodeEntry = await batch.get(op.key);
379
- if (nodeEntry !== null && nodeEntry.value.isWriter && nodeEntry.value.isIndexer) {
380
- indexersEntry = indexersEntry.value;
381
- nodeEntry = nodeEntry.value;
382
- await base.removeWriter(b4a.from(nodeEntry.wk, 'hex'));
383
-
384
- nodeEntry.isWriter = false;
385
- nodeEntry.isIndexer = false;
386
- await batch.put(op.key, nodeEntry);
387
-
388
- const idx = indexersEntry.indexOf(op.key);
389
- if (idx !== -1) {
390
- indexersEntry.splice(idx, 1);
391
- await batch.put(EntryType.INDEXERS, indexersEntry);
392
- }
393
-
394
- console.log(`Indexer removed: ${op.key}:${nodeEntry.wk}`);
395
- }
396
- }
397
- }
398
-
399
- async #handleApplyBanValidatorOperation(op, view, base, node, batch) {
400
- const adminEntry = await batch.get(EntryType.ADMIN);
401
- if (null === adminEntry || !this.#isAdmin(adminEntry.value, node)) return;
402
- if (!this.check.sanitizeIndexerOrWhitelistOperations(op)) return;
403
- const isWhitelisted = await this.#isWhitelisted2(op.key, batch);
404
- if (!isWhitelisted) return;
405
-
406
- const nodeEntry = await batch.get(op.key)
407
- if (null === nodeEntry || nodeEntry.value.isIndexer === true) return; // even if node is not writable atm it should be possible to ban it.
408
- const isMessageVerifed = await this.#verifyMessage(op.value.sig, adminEntry.value.tracPublicKey, MsgUtils.createMessage(op.key, op.value.nonce, op.type))
409
- if (!isMessageVerifed) return;
410
- await this.#deleteWhitelistEntry(batch, op.key);
411
- await this.#removeWriter(op, batch, base);
412
-
413
- }
414
-
415
- async _open() {
416
- await this.#base.ready();
417
- if (this.#enable_wallet) {
418
- await this.#wallet.initKeyPair(this.KEY_PAIR_PATH, this.#readline_instance);
419
- }
420
-
421
- if (this.#enable_wallet) {
422
- console.log('');
423
- console.log('#####################################################################################');
424
- console.log('# MSB Address: ', this.#wallet.publicKey, '#');
425
- this.#writingKey = b4a.toString(this.#base.local.key, 'hex');
426
- console.log('# MSB Writer: ', this.#writingKey, '#');
427
- console.log('#####################################################################################');
428
- }
429
-
430
- console.log('');
431
- if (this.#replicate) {
432
- this.#swarm = await Network.replicate(this.#disable_rate_limit, this, this.#network, this.#enable_txchannel, this.#base, this.#writingKey, this.#dht_bootstrap, this.#swarm, this.#enable_wallet, this.#store, this.#wallet, this.#channel, this.#isStreaming, this.#handleIncomingEvent.bind(this), this.emit.bind(this));
433
- this.#dht_node = this.#swarm.dht;
434
- }
435
-
436
- const adminEntry = await this.get(EntryType.ADMIN);
437
-
438
- if (this.#isAdmin(adminEntry)) {
439
- this.#shouldListenToAdminEvents = true;
440
- this.#adminEventListener(); // only for admin
441
- } else if (!this.#isAdmin(adminEntry) && this.#base.writable && !this.#base.isIndexer) {
442
- this.#shouldListenToWriterEvents = true;
443
- this.#writerEventListener(); // only for writers
444
- }
445
-
446
- await this.#setUpRoleAutomatically(adminEntry);
447
-
448
- console.log(`isIndexer: ${this.#base.isIndexer}`);
449
- console.log(`isWriter: ${this.#base.writable}`);
450
- console.log('MSB Unsigned Length:', this.#base.view.core.length);
451
- console.log('MSB Signed Length:', this.#base.view.core.signedLength);
452
- console.log('');
453
-
454
- this.validatorObserver();
455
- }
456
-
457
- async close() {
458
- console.log('Closing everything...');
459
- if (this.#swarm) {
460
- await this.#swarm.destroy();
461
- }
462
- await this.#base.close();
463
- }
464
-
465
- async #setUpRoleAutomatically() {
466
- if (!this.#base.writable) {
467
- await this.#requestWriterRole(false)
468
- setTimeout(async () => {
469
- await this.#requestWriterRole(true)
470
- }, 5_000);
471
- }
472
- }
473
-
474
- async #sendMessageToAdmin(adminEntry, message) {
475
- try {
476
- if (!adminEntry || !message) {
477
- return;
478
- }
479
- await this.tryConnection(adminEntry.tracPublicKey, 'admin');
480
- await this.spinLock(() => this.#network.admin_stream === null);
481
- if (this.#network.admin_stream !== null) {
482
- await this.#network.admin_stream.messenger.send(message);
483
- }
484
- } catch (e) {
485
- console.log(e)
486
- }
487
- }
488
- async #sendMessageToNode(address, message) {
489
- try {
490
- if (!address || !message) {
491
- return;
492
- }
493
- await this.tryConnection(address, 'node');
494
-
495
- await this.spinLock(() =>
496
- this.#network.custom_stream === null || this.#network.custom_node !== address
497
- );
498
-
499
- if (this.#network.custom_stream !== null) {
500
- await this.#network.custom_stream.messenger.send(message);
501
- }
502
-
503
- } catch (e) {
504
- console.log(e)
505
- }
506
-
507
- }
508
-
509
- async #verifyMessage(signature, publicKey, bufferMessage) {
510
- const bufferPublicKey = b4a.from(publicKey, 'hex');
511
- const hash = await createHash('sha256', bufferMessage);
512
- return this.#wallet.verify(signature, hash, bufferPublicKey);
513
- }
514
-
515
- #isAdmin(adminEntry, node = null) {
516
- //on-chain
517
- if (!adminEntry) return false;
518
- if (node) return adminEntry.wk === b4a.from(node.from.key).toString('hex');
519
- //off-chain
520
- if (this.#enable_wallet === false) return false;
521
- return !!(this.#wallet.publicKey === adminEntry.tracPublicKey && adminEntry.wk === this.#writingKey);
522
- }
523
-
524
- async #isAllowedToRequestRole(key, adminEntry) {
525
- const isWhitelisted = await this.#isWhitelisted(key);
526
- return !!(isWhitelisted && !this.#isAdmin(adminEntry));
527
- }
528
-
529
- async _isAllowedToRequestRole(key, adminEntry) {
530
- const isWhitelisted = await this.#isWhitelisted(key);
531
- return isWhitelisted && this.#isAdmin(adminEntry);
532
- }
533
-
534
- async #isWhitelisted(key) {
535
- const whitelistEntry = await this.getWhitelistEntry(key)
536
- return !!whitelistEntry;
537
- }
538
-
539
- async #isWhitelisted2(key, batch) {
540
- const whitelistEntry = await this.getWhitelistEntry2(key, batch)
541
- return !!whitelistEntry;
542
- }
543
-
544
- async get(key) {
545
- const result = await this.#base.view.get(key);
546
- if (result === null) return null;
547
- return result.value;
548
- }
549
-
550
- async getSigned(key) {
551
- const view_session = this.#base.view.checkout(this.#base.view.core.signedLength);
552
- const result = await view_session.get(key);
553
- if (result === null) return null;
554
- return result.value;
555
- }
556
-
557
- async getWhitelistEntry(key) {
558
- const entry = await this.get(WHITELIST_PREFIX + key);
559
- return entry
560
- }
561
-
562
- async getWhitelistEntry2(key, batch) {
563
- const entry = await batch.get(WHITELIST_PREFIX + key);
564
- return entry !== null ? entry.value : null
565
- }
566
-
567
- async #handleIncomingEvent(parsedRequest) {
568
- try {
569
- if (parsedRequest && parsedRequest.type && parsedRequest.key && parsedRequest.value) {
570
- if (parsedRequest.type === OperationType.ADD_WRITER || parsedRequest.type === OperationType.REMOVE_WRITER) {
571
- //This request must be hanlded by ADMIN
572
- this.emit(EventType.ADMIN_EVENT, parsedRequest);
573
- } else if (parsedRequest.type === OperationType.ADD_ADMIN) {
574
- //This request must be handled by WRITER
575
- this.emit(EventType.WRITER_EVENT, parsedRequest);
576
- }
577
- else if (parsedRequest.type === OperationType.WHITELISTED) {
578
- const adminEntry = await this.get(EntryType.ADMIN);
579
- const reconstructedMessage = MsgUtils.createMessage(parsedRequest.key, parsedRequest.value.nonce, OperationType.WHITELISTED);
580
- const hash = await createHash('sha256', reconstructedMessage);
581
- if (this.#wallet.verify(b4a.from(parsedRequest.value.sig, 'hex'), b4a.from(hash), b4a.from(adminEntry.tracPublicKey, 'hex')) && !this.#base.writable &&parsedRequest.key === this.#wallet.publicKey) {
582
- await this.#handleAddWriterOperation(true)
583
- }
584
- }
585
- }
586
- } catch (error) {
587
- // for now ignore the error
588
- }
589
- }
590
-
591
- #setupInternalListeners() {
592
- this.#base.on(EventType.IS_INDEXER, () => {
593
- if (this.listenerCount(EventType.WRITER_EVENT) > 0) {
594
- this.removeAllListeners(EventType.WRITER_EVENT);
595
- this.#shouldListenToWriterEvents = false;
596
- }
597
- this.#is_indexer = true;
598
- console.log('Current node is an indexer');
599
- });
600
-
601
- this.#base.on(EventType.IS_NON_INDEXER, () => {
602
- this.#is_indexer = false;
603
- console.log('Current node is not an indexer anymore');
604
- });
605
-
606
- this.#base.on(EventType.WRITABLE, async () => {
607
- const updatedNodeEntry = await this.get(this.#wallet.publicKey);
608
- const canEnableWriterEvents = updatedNodeEntry &&
609
- updatedNodeEntry.wk === this.#writingKey &&
610
- !this.#shouldListenToWriterEvents;
611
-
612
- if (canEnableWriterEvents) {
613
- this.#shouldListenToWriterEvents = true;
614
- this.#writerEventListener();
615
- console.log('Current node is writable');
616
- }
617
- });
618
-
619
- this.#base.on(EventType.UNWRITABLE, async () => {
620
- if (this.#enable_wallet === false) {
621
- console.log('Current node is unwritable');
622
- return;
623
- }
624
- const updatedNodeEntry = await this.get(this.#wallet.publicKey);
625
- const canDisableWriterEvents = updatedNodeEntry &&
626
- !updatedNodeEntry.isWriter &&
627
- this.#shouldListenToWriterEvents;
628
-
629
- if (canDisableWriterEvents) {
630
- this.removeAllListeners(EventType.WRITER_EVENT);
631
- this.#shouldListenToWriterEvents = false;
632
- console.log('Current node is unwritable');
633
- }
634
- });
635
- }
636
-
637
- async #adminEventListener() {
638
- this.on(EventType.ADMIN_EVENT, async (parsedRequest) => {
639
- if (this.#enable_wallet === false) return;
640
- const isWhitelisted = await this.#isWhitelisted(parsedRequest.key);
641
- const isEventMessageVerifed = await MsgUtils.verifyEventMessage(parsedRequest, this.#wallet, this.check)
642
- if (isWhitelisted && isEventMessageVerifed) {
643
- await this.#base.append(parsedRequest);
644
- }
645
- });
646
- }
647
-
648
- async #writerEventListener() {
649
- this.on(EventType.WRITER_EVENT, async (parsedRequest) => {
650
- if (this.#enable_wallet === false) return;
651
- const adminEntry = await this.get(EntryType.ADMIN);
652
- const isEventMessageVerifed = await MsgUtils.verifyEventMessage(parsedRequest, this.#wallet, this.check)
653
- if (adminEntry && adminEntry.tracPublicKey === parsedRequest.key && isEventMessageVerifed) {
654
- await this.#base.append(parsedRequest);
655
- }
656
- });
657
- }
658
-
659
- msbListener() {
660
- this.on(EventType.READY_MSB, async () => {
661
- if (!this.#isStreaming) {
662
- this.#isStreaming = true;
663
- }
664
- });
665
- }
666
-
667
- async #handleAdminOperations() {
668
- try {
669
- const adminEntry = await this.get(EntryType.ADMIN);
670
- const addAdminMessage = await MsgUtils.assembleAdminMessage(adminEntry, this.#writingKey, this.#wallet, this.#bootstrap);
671
- if (!adminEntry && this.#wallet && this.#writingKey && this.#writingKey === this.#bootstrap) {
672
- await this.#base.append(addAdminMessage);
673
- } else if (adminEntry && this.#wallet && adminEntry.tracPublicKey === this.#wallet.publicKey && this.#writingKey && this.#writingKey !== adminEntry.wk) {
674
- if (null === this.#network.validator_stream) return;
675
- await this.#network.validator_stream.messenger.send(addAdminMessage);
676
- }
677
-
678
- setTimeout(async () => {
679
- const updatedAdminEntry = await this.get(EntryType.ADMIN);
680
- if (this.#isAdmin(updatedAdminEntry) && !this.#shouldListenToAdminEvents) {
681
- this.#shouldListenToAdminEvents = true;
682
- this.#adminEventListener();
683
- }
684
- }, LISTENER_TIMEOUT);
685
-
686
- } catch (e) {
687
- console.log(e);
688
- }
689
- }
690
-
691
- async #handleWhitelistOperations() {
692
- if (this.#enable_wallet === false) return;
693
- const adminEntry = await this.get(EntryType.ADMIN);
694
- if (!this.#isAdmin(adminEntry)) return;
695
-
696
- const assembledWhitelistMessages = await MsgUtils.assembleWhitelistMessages(adminEntry, this.#wallet);
697
-
698
- if (!assembledWhitelistMessages) {
699
- console.log('Whitelist message not sent.');
700
- return;
701
- }
702
-
703
- const totelElements = assembledWhitelistMessages.length;
704
-
705
- for (let i = 0; i < totelElements; i++) {
706
- const isWhitelisted = await this.#isWhitelisted(assembledWhitelistMessages[i].key);
707
- if (!isWhitelisted) {
708
- await this.#base.append(assembledWhitelistMessages[i]);
709
- const whitelistedMessage = await MsgUtils.assembleWhitelistedMessage(this.#wallet, assembledWhitelistMessages[i].key);
710
- this.#sendMessageToNode(assembledWhitelistMessages[i].key, whitelistedMessage);
711
- await sleep(WHITELIST_SLEEP_INTERVAL);
712
- console.log(`Whitelist message sent (public key ${(i + 1)}/${totelElements})`);
713
- }
714
- }
715
- }
716
-
717
- async generateTx(bootstrap, msb_bootstrap, validator_writer_key, local_writer_key, local_public_key, content_hash, nonce) {
718
- let tx = bootstrap + '-' +
719
- msb_bootstrap + '-' +
720
- validator_writer_key + '-' +
721
- local_writer_key + '-' +
722
- local_public_key + '-' +
723
- content_hash + '-' +
724
- nonce;
725
- return await createHash('sha256', await createHash('sha256', tx));
726
- }
727
-
728
- async #requestWriterRole(toAdd) {
729
- if (this.#enable_wallet === false) return;
730
- const adminEntry = await this.get(EntryType.ADMIN);
731
- const nodeEntry = await this.get(this.#wallet.publicKey);
732
- const isAlreadyWriter = !!(nodeEntry && nodeEntry.isWriter)
733
- let assembledMessage = null;
734
- if (toAdd) {
735
- const isAllowedToRequestRole = await this.#isAllowedToRequestRole(this.#wallet.publicKey, adminEntry);
736
- const canAddWriter = !!(!this.#base.writable && !isAlreadyWriter && isAllowedToRequestRole);
737
- if (canAddWriter) {
738
- assembledMessage = await MsgUtils.assembleAddWriterMessage(this.#wallet, this.#writingKey);
739
- }
740
- }
741
- else {
742
- if (isAlreadyWriter) {
743
- assembledMessage = await MsgUtils.assembleRemoveWriterMessage(this.#wallet, this.#writingKey);
744
- }
745
- }
746
-
747
- if (assembledMessage) {
748
- await this.#sendMessageToAdmin(adminEntry, assembledMessage);
749
- }
750
- }
751
-
752
- async #updateIndexerRole(tracPublicKey, toAdd) {
753
- if (this.#enable_wallet === false) return;
754
- const adminEntry = await this.get(EntryType.ADMIN);
755
- if (!this.#isAdmin(adminEntry) && !this.#base.writable) return;
756
-
757
- const isWhitelisted = await this.#isWhitelisted(tracPublicKey);
758
- if (!isWhitelisted) return;
759
-
760
- const nodeEntry = await this.get(tracPublicKey);
761
- if (!nodeEntry || !nodeEntry.isWriter) return;
762
-
763
- const indexersEntry = await this.get(EntryType.INDEXERS);
764
-
765
- if (toAdd) {
766
- const canAddIndexer = !nodeEntry.isIndexer && indexersEntry.length <= MAX_INDEXERS;
767
- if (canAddIndexer) {
768
- const assembledAddIndexerMessage = await MsgUtils.assembleAddIndexerMessage(this.#wallet, tracPublicKey);
769
- await this.#base.append(assembledAddIndexerMessage);
770
- }
771
- } else {
772
- const canRemoveIndexer = !toAdd && nodeEntry.isIndexer && indexersEntry.length > MIN_INDEXERS;
773
- if (canRemoveIndexer) {
774
- const assembledRemoveIndexer = await MsgUtils.assembleRemoveIndexerMessage(this.#wallet, tracPublicKey);
775
- await this.#base.append(assembledRemoveIndexer);
776
- }
777
-
778
- }
779
- }
780
-
781
- async tryConnection(address, type = null) {
782
- if (null === this.#swarm) return null;
783
- if (this.#network.validator_stream !== null && address !== b4a.toString(this.#network.validator_stream.remotePublicKey, 'hex')) {
784
- this.#swarm.leavePeer(this.#network.validator_stream.remotePublicKey);
785
- this.#network.validator_stream = null;
786
- this.#network.validator = null;
787
- }
788
- // trying to join a peer from the global swarm
789
- if (false === this.#swarm.peers.has(address)) {
790
- this.#swarm.joinPeer(b4a.from(address, 'hex'));
791
- let cnt = 0;
792
- while (false === this.#swarm.peers.has(address)) {
793
- if (cnt >= 1500) break;
794
- await sleep(10);
795
- cnt += 1;
796
- }
797
- }
798
-
799
- if (this.#swarm.peers.has(address)) {
800
- let stream;
801
- // split it into 2 cases as before 1. existing connection and
802
- const peerInfo = this.#swarm.peers.get(address)
803
- stream = this.#swarm._allConnections.get(peerInfo.publicKey)
804
-
805
- if (stream !== undefined && stream.messenger !== undefined) {
806
- await this.#sendRequestByType(stream, type);
807
- }
808
- }
809
- }
810
-
811
- async #sendRequestByType(stream, type) {
812
- const waitFor = {
813
- validator: () => this.#network.validator_stream,
814
- admin: () => this.#network.admin_stream,
815
- node: () => this.#network.custom_stream
816
- }[type];
817
-
818
- if (type === 'validator') {
819
- await stream.messenger.send('get_validator');
820
- } else if (type === 'admin') {
821
- await stream.messenger.send('get_admin');
822
- } else if (type === 'node') {
823
- await stream.messenger.send('get_node');
824
- } else {
825
- return;
826
- }
827
- await this.spinLock( () => !waitFor)
828
- };
829
-
830
- async spinLock(conditionFn, maxIterations = 1500, intervalMs = 10) {
831
- let counter = 0;
832
- while (conditionFn() && counter < maxIterations) {
833
- await sleep(intervalMs);
834
- counter++;
835
- }
836
- }
837
-
838
- async validatorObserver() {
839
- // Finding writers for admin recovery case
840
- while (this.#enable_wallet) {
841
-
842
- if (this.#dht_node === null || this.#network.validator_stream !== null) {
843
- await sleep(1000);
844
- continue;
845
- }
846
- const lengthEntry = await this.#base.view.get('wrl');
847
- const length = lengthEntry?.value ?? 0;
848
-
849
- async function findValidator(_this) {
850
- if (_this.#network.validator_stream !== null) return;
851
-
852
- const rndIndex = Math.floor(Math.random() * length);
853
- const wriEntry = await _this.#base.view.get('wri/' + rndIndex);
854
- if (_this.#network.validator_stream !== null || wriEntry === null) return;
855
-
856
- const validatorEntry = await _this.#base.view.get(wriEntry.value);
857
- if (
858
- _this.#network.validator_stream !== null ||
859
- _this.#network.validator !== null ||
860
- validatorEntry === null ||
861
- !validatorEntry.value.isWriter ||
862
- validatorEntry.value.isIndexer
863
- ) return;
864
-
865
- const pubKey = validatorEntry.value.pub;
866
- if (pubKey === _this.#wallet.publicKey) return;
867
- await _this.tryConnection(pubKey, 'validator');
868
- }
869
-
870
- const promises = [];
871
- for (let i = 0; i < 10; i++) {
872
- promises.push(findValidator(this));
873
- await sleep(250);
874
- }
875
- await Promise.all(promises);
876
-
877
- await sleep(1000);
878
- }
879
- }
880
-
881
-
882
- async #banValidator(tracPublicKey) {
883
- const adminEntry = await this.get(EntryType.ADMIN);
884
- if (!this.#isAdmin(adminEntry)) return;
885
- const isWhitelisted = await this.#isWhitelisted(tracPublicKey);
886
- const nodeEntry = await this.get(tracPublicKey);
887
- if (!isWhitelisted || null === nodeEntry || nodeEntry.isIndexer === true) return;
888
-
889
- const assembledBanValidatorMessage = await MsgUtils.assembleBanValidatorMessage(this.#wallet, tracPublicKey);
890
- this.#base.append(assembledBanValidatorMessage);
891
-
892
- }
893
-
894
- async #handleAddIndexerOperation(tracPublicKey) {
895
- this.#updateIndexerRole(tracPublicKey, true);
896
- }
897
-
898
- async #handleRemoveIndexerOperation(tracPublicKey) {
899
- this.#updateIndexerRole(tracPublicKey, false);
900
- }
901
-
902
- async #handleAddWriterOperation() {
903
- await this.#requestWriterRole(true);
904
- }
905
-
906
- async #handleRemoveWriterOperation() {
907
- await this.#requestWriterRole(false);
908
- }
909
-
910
- async #handleBanValidatorOperation(tracPublicKey) {
911
- await this.#banValidator(tracPublicKey);
912
- }
913
-
914
- printHelp() {
915
- console.log('Available commands:');
916
- console.log('- /add_writer: add yourself as validator to this MSB once whitelisted.');
917
- console.log('- /remove_writer: remove yourself from this MSB.');
918
- console.log('- /add_admin: register admin entry with bootstrap key. (initial setup)');
919
- console.log('- /add_whitelist: add all specified whitelist addresses. (admin only)');
920
- console.log('- /add_indexer <address>: change a role of the selected writer node to indexer role. (admin only)');
921
- console.log('- /remove_indexer <address>: change a role of the selected indexer node to default role. (admin only)');
922
- console.log('- /ban_writer <address>: demote a whitelisted writer to default role and remove it from the whitelist. (admin only)');
923
- console.log('- /get_node_info <address>: get information about a node with the given address.');
924
- console.log('- /stats: check system stats such as writing key, DAG, etc.');
925
- console.log('- /exit: Exit the program.');
926
- console.log('- /help: display this help.');
927
- }
928
-
929
- async interactiveMode() {
930
- if (this.#readline_instance === null) return;
931
- const rl = this.#readline_instance;
932
-
933
- this.printHelp();
934
-
935
- rl.on('line', async (input) => {
936
- switch (input) {
937
- case '/help':
938
- this.printHelp();
939
- break;
940
- case '/exit':
941
- console.log('Exiting...');
942
- rl.close();
943
- await this.close();
944
- typeof process !== "undefined" ? process.exit(0) : Pear.exit(0);
945
- break;
946
- case '/push_writer_add':
947
- await this.#requestWriterRole(true)
948
- break;
949
- case '/add_admin':
950
- await this.#handleAdminOperations();
951
- break;
952
- case '/add_whitelist':
953
- await this.#handleWhitelistOperations();
954
- break;
955
- case '/add_writer':
956
- await this.#handleAddWriterOperation(true);
957
- break;
958
- case '/remove_writer':
959
- await this.#handleRemoveWriterOperation();
960
- break
961
- case '/flags':
962
- console.log("shouldListenToAdminEvents: ", this.#shouldListenToAdminEvents);
963
- console.log("shouldListenToWriterEvents: ", this.#shouldListenToWriterEvents);
964
- console.log("isWritable: ", this.#base.writable);
965
- console.log("isIndexer: ", this.#base.isIndexer);
966
- break
967
- case '/show':
968
- const admin = await this.get(EntryType.ADMIN);
969
- console.log('Admin:', admin);
970
- const indexers = await this.get(EntryType.INDEXERS);
971
- console.log('Indexers:', indexers);
972
- break;
973
- case '/stats':
974
- await verifyDag(this.#base, this.#swarm, this.#wallet, this.#writingKey);
975
- break;
976
- default:
977
- if (input.startsWith('/get_node_info')) {
978
- const splitted = input.split(' ');
979
- console.log("whitelist entry:", await this.#isWhitelisted(splitted[1]))
980
- } else if (input.startsWith('/add_indexer')) {
981
- const splitted = input.split(' ');
982
- const tracPublicKey = splitted[1]
983
- await this.#handleAddIndexerOperation(tracPublicKey);
984
- }
985
- else if (input.startsWith('/remove_indexer')) {
986
- const splitted = input.split(' ');
987
- const tracPublicKey = splitted[1]
988
- await this.#handleRemoveIndexerOperation(tracPublicKey);
989
- }
990
- else if (input.startsWith('/ban_writer')) {
991
- const splitted = input.split(' ');
992
- const tracPublicKey = splitted[1]
993
- await this.#handleBanValidatorOperation(tracPublicKey);
994
- }
995
- }
996
- rl.prompt();
997
- });
998
-
999
- rl.prompt();
1000
- }
1001
-
1002
- }
1003
-
1004
- function noop() { }
1005
- export default MainSettlementBus;