trac-msb 0.0.10 → 0.0.12
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/package.json +5 -1
- package/src/functions.js +64 -8
- package/src/index.js +200 -157
- package/src/writerManager.js +316 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trac-msb",
|
|
3
3
|
"main": "src/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.12",
|
|
5
5
|
"pear": {
|
|
6
6
|
"name": "trac-msb",
|
|
7
7
|
"type": "terminal"
|
|
@@ -30,5 +30,9 @@
|
|
|
30
30
|
"ready-resource": "^1.0.0",
|
|
31
31
|
"safety-catch": "1.0.2",
|
|
32
32
|
"xache": "1.2.0"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"registry": "https://registry.npmjs.org",
|
|
36
|
+
"access": "public"
|
|
33
37
|
}
|
|
34
38
|
}
|
package/src/functions.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
3
2
|
//TODO: if something is missing, add additonal sanitization
|
|
4
3
|
// parsed.op === 'pre-tx' -> moved out of the scope this check because we can re-use this function in the apply
|
|
5
4
|
// TODO: Split sanitization on pre and post TX
|
|
@@ -16,11 +15,68 @@ export function sanitizeTransaction(parsedTx) {
|
|
|
16
15
|
);
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
if(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
export function restoreManifest(parsedManifest) {
|
|
19
|
+
|
|
20
|
+
if (Array.isArray(parsedManifest.signers)) {
|
|
21
|
+
parsedManifest.signers = parsedManifest.signers.map(signer => {
|
|
22
|
+
if(signer.namespace && signer.namespace.data &&signer.publicKey && signer.publicKey.data){
|
|
23
|
+
return {
|
|
24
|
+
...signer,
|
|
25
|
+
namespace: Buffer.from(signer.namespace.data),
|
|
26
|
+
publicKey: Buffer.from(signer.publicKey.data),
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
return signer;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return parsedManifest;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// To improve - could be done in better approach (simplify)
|
|
38
|
+
export function restoreHash(parsedPreTx) {
|
|
39
|
+
const reconstructedContentHash = createHash('sha256')
|
|
40
|
+
.update(JSON.stringify(parsedPreTx.ch))
|
|
41
|
+
.digest('hex');
|
|
42
|
+
|
|
43
|
+
const reconstructedTxHash = createHash('sha256')
|
|
44
|
+
.update(
|
|
45
|
+
parsedPreTx.w + '-' +
|
|
46
|
+
parsedPreTx.i + '-' +
|
|
47
|
+
parsedPreTx.ipk + '-' +
|
|
48
|
+
reconstructedContentHash + '-' +
|
|
49
|
+
parsedPreTx.in
|
|
50
|
+
)
|
|
51
|
+
.digest('hex');
|
|
52
|
+
|
|
53
|
+
const finalReconstructedTxHash = createHash('sha256')
|
|
54
|
+
.update(reconstructedTxHash)
|
|
55
|
+
.digest('hex');
|
|
56
|
+
return finalReconstructedTxHash;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function verifyDag(autoBaseInsance) {
|
|
60
|
+
try {
|
|
61
|
+
console.log('--- DAG Monitoring ---');
|
|
62
|
+
const dagView = await autoBaseInsance.view.core.treeHash();
|
|
63
|
+
const lengthdagView = autoBaseInsance.view.core.length;
|
|
64
|
+
const dagSystem = await autoBaseInsance.system.core.treeHash();
|
|
65
|
+
const lengthdagSystem = autoBaseInsance.system.core.length;
|
|
66
|
+
console.log('this.base.view.core.signedLength:', autoBaseInsance.view.core.signedLength);
|
|
67
|
+
console.log("this.base.signedLength", autoBaseInsance.signedLength);
|
|
68
|
+
console.log("this.base.linearizer.indexers.length", autoBaseInsance.linearizer.indexers.length);
|
|
69
|
+
console.log("this.base.indexedLength", autoBaseInsance.indexedLength);
|
|
70
|
+
console.log(`base.key/writingKey: ${autoBaseInsance.key.toString('hex')}`);
|
|
71
|
+
|
|
72
|
+
console.log(`VIEW Dag: ${dagView.toString('hex')} (length: ${lengthdagView})`);
|
|
73
|
+
console.log(`SYSTEM Dag: ${dagSystem.toString('hex')} (length: ${lengthdagSystem})`);
|
|
74
|
+
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error('Error during DAG monitoring:', error.message);
|
|
25
77
|
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function sleep(ms) {
|
|
81
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
26
82
|
}
|
package/src/index.js
CHANGED
|
@@ -5,14 +5,20 @@ import ReadyResource from 'ready-resource';
|
|
|
5
5
|
import b4a from 'b4a';
|
|
6
6
|
import Hyperbee from 'hyperbee';
|
|
7
7
|
import readline from 'readline';
|
|
8
|
-
import
|
|
9
|
-
import { sanitizeTransaction, addWriter } from './functions.js';
|
|
8
|
+
import { sanitizeTransaction, restoreManifest, sleep, restoreHash ,verifyDag } from './functions.js';
|
|
10
9
|
import w from 'protomux-wakeup';
|
|
11
|
-
import * as edKeyGen from "trac-wallet"
|
|
12
|
-
import fs from 'node:fs';
|
|
13
10
|
import Corestore from 'corestore';
|
|
11
|
+
import verifier from 'hypercore/lib/verifier.js';
|
|
12
|
+
import WriterManager from './writerManager.js';
|
|
13
|
+
import PeerWallet from "trac-wallet"; // TODO: Decide if this should be used here directly or inputed as an option
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const { manifestHash, createManifest } = verifier;
|
|
14
17
|
|
|
15
18
|
const wakeup = new w();
|
|
19
|
+
//TODO: change isValid to isIndexer
|
|
20
|
+
//TODO: How about nonce if edDSA is deterministic?
|
|
21
|
+
//TODO: CHECK IF TX HASH IS ALREDY IN BASE BEFORE VALIDATING IT TO DON'T OVERWRITE tx/writerPubKey. Also we need to validate this case where the 2 nodes send the same hash.
|
|
16
22
|
|
|
17
23
|
export class MainSettlementBus extends ReadyResource {
|
|
18
24
|
|
|
@@ -20,31 +26,33 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
20
26
|
super();
|
|
21
27
|
this.STORES_DIRECTORY = options.stores_directory;
|
|
22
28
|
this.KEY_PAIR_PATH = `${this.STORES_DIRECTORY}${options.store_name}/db/keypair.json`
|
|
23
|
-
this.signingKeyPair = null;
|
|
24
29
|
this.store = new Corestore(this.STORES_DIRECTORY + options.store_name);
|
|
25
30
|
this.swarm = null;
|
|
26
31
|
this.tx = options.tx || null;
|
|
27
32
|
this.tx_pool = [];
|
|
28
33
|
this.enable_txchannel = typeof options.enable_txchannel !== "undefined" && options.enable_txchannel === false ? false : true;
|
|
29
34
|
this.enable_wallet = typeof options.enable_wallet !== "undefined" && options.enable_wallet === false ? false : true;
|
|
35
|
+
this.isVerifyOnly = typeof options.isVerifyOnly !== "undefined" && options.isVerifyOnly === true ? true : false;
|
|
30
36
|
this.base = null;
|
|
31
|
-
this.key = null;
|
|
32
37
|
this.channel = options.channel || null;
|
|
33
38
|
this.connectedNodes = 1;
|
|
34
39
|
this.replicate = options.replicate !== false;
|
|
35
|
-
this.
|
|
40
|
+
this.writingKey = null;
|
|
36
41
|
this.isStreaming = false;
|
|
37
42
|
this.bootstrap = options.bootstrap || null;
|
|
38
43
|
this.opts = options;
|
|
39
|
-
this.connectedPeers = new Set();
|
|
40
44
|
this.bee = null;
|
|
41
45
|
|
|
46
|
+
// TODO: Decide if this is better placed in the _open method instead of here
|
|
47
|
+
this.wallet = new PeerWallet({ isVerifyOnly: this.isVerifyOnly });
|
|
48
|
+
|
|
42
49
|
this.pool();
|
|
43
50
|
this.msbListener();
|
|
44
51
|
this._boot();
|
|
45
52
|
this.ready().catch(noop);
|
|
46
|
-
}
|
|
47
53
|
|
|
54
|
+
}
|
|
55
|
+
//TODO: Move apply to the separate file
|
|
48
56
|
_boot() {
|
|
49
57
|
const _this = this;
|
|
50
58
|
this.base = new Autobase(this.store, this.bootstrap, {
|
|
@@ -56,6 +64,8 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
56
64
|
keyEncoding: 'utf-8',
|
|
57
65
|
valueEncoding: 'json'
|
|
58
66
|
})
|
|
67
|
+
_this.keysView = _this.bee.sub('pubKeys');
|
|
68
|
+
|
|
59
69
|
return _this.bee;
|
|
60
70
|
},
|
|
61
71
|
|
|
@@ -64,25 +74,142 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
64
74
|
for (const node of nodes) {
|
|
65
75
|
const op = node.value;
|
|
66
76
|
const postTx = op.value;
|
|
77
|
+
|
|
78
|
+
// UNDER NO CIRCUMSTANCE DO THINGS LIKE THIS IN APPLY!
|
|
79
|
+
/*
|
|
80
|
+
if (!op || !op.type || !op.key || !op.value) {
|
|
81
|
+
continue;
|
|
82
|
+
}*/
|
|
83
|
+
|
|
84
|
+
// WRITING & INDEXING
|
|
67
85
|
if (op.type === 'tx') {
|
|
68
86
|
if (null === await view.get(op.key) &&
|
|
87
|
+
restoreHash(postTx) !== postTx.tx &&
|
|
69
88
|
sanitizeTransaction(postTx) &&
|
|
70
89
|
postTx.op === 'post-tx' &&
|
|
71
|
-
|
|
72
|
-
|
|
90
|
+
this.wallet.verify(Buffer.from(postTx.is, 'hex'), Buffer.from(postTx.tx, 'utf-8'), Buffer.from(postTx.ipk, 'hex')) &&// sender verification
|
|
91
|
+
this.wallet.verify(Buffer.from(postTx.ws, 'hex'), Buffer.from(postTx.tx, 'utf-8'), Buffer.from(postTx.wp, 'hex')) &&// writer verification
|
|
73
92
|
Buffer.byteLength(JSON.stringify(postTx)) <= 4096
|
|
74
93
|
) {
|
|
75
94
|
await view.put(op.key, op.value);
|
|
76
|
-
console.log(`TX: ${op.key} appended. Signed length: `,
|
|
95
|
+
console.log(`TX: ${op.key} appended. Signed length: `, _this.base.view.core.signedLength);
|
|
77
96
|
}
|
|
78
97
|
} else if (op.type === 'addWriter') {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
98
|
+
//TODO: it can be optimalized by adding variables to don't call Buffer.from multiple times.
|
|
99
|
+
//TODO: SANITIZE INCOMPING PROPOSAL
|
|
100
|
+
if (node.from.key.toString('hex') === this.bootstrap) {
|
|
101
|
+
const message = Buffer.concat([
|
|
102
|
+
Buffer.from(JSON.stringify(op.value.hpm)),
|
|
103
|
+
Buffer.from(op.value.wk, 'hex'),
|
|
104
|
+
Buffer.from(op.key, 'hex')
|
|
105
|
+
//TODO: ADD THE NONCE?
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
const pop1Valid = this.wallet.verify(op.value.pop1, message, Buffer.from(op.value.hpm.signers[0].publicKey));
|
|
109
|
+
const pop2Valid = this.wallet.verify(op.value.pop2, message, op.key);
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
const restoredManifest = restoreManifest(op.value.hpm); //temporary workaround
|
|
113
|
+
if (pop1Valid && pop2Valid && manifestHash(createManifest(restoredManifest)).toString('hex') === op.value.wk) {
|
|
114
|
+
|
|
115
|
+
const writerEntry = this.base.view.get(op.key);
|
|
116
|
+
if (writerEntry === null || !writerEntry.isValid) {
|
|
117
|
+
await base.addWriter(b4a.from(op.value.wk, 'hex'), { isIndexer: false })
|
|
118
|
+
|
|
119
|
+
await view.put(op.key, {
|
|
120
|
+
wk: op.value.wk,
|
|
121
|
+
hpm: op.value.hpm,
|
|
122
|
+
pop1: op.value.pop1, // TODO: observation this is really necessary to store pops? IF NOT DELETE IT!
|
|
123
|
+
pop2: op.value.pop2,
|
|
124
|
+
isValid: true,
|
|
125
|
+
isIndexer: false
|
|
126
|
+
//TODO: ADD NONCE?
|
|
127
|
+
});
|
|
128
|
+
console.log(`Writer added: ${op.value.wk}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} else if (op.type === 'removeWriter') {
|
|
133
|
+
//TODO: it can be optimalized by adding variables to don't call Buffer.from multiple times. And other operations
|
|
134
|
+
//TODO: SANITIZE INCOMPING PROPOSAL
|
|
135
|
+
if (node.from.key.toString('hex') === this.bootstrap) {
|
|
136
|
+
const publicKey = Buffer.from(op.key, 'hex');
|
|
137
|
+
const message = Buffer.concat([
|
|
138
|
+
publicKey
|
|
139
|
+
//TODO: ADD NONCE ?
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
const popIsValid = this.wallet.verify(op.value.pop, message, publicKey);
|
|
143
|
+
if (popIsValid) {
|
|
144
|
+
const writerEntry = await _this.base.view.get(op.key)
|
|
145
|
+
if (writerEntry !== null && writerEntry.value.isValid ) {
|
|
146
|
+
await base.removeWriter(Buffer.from(writerEntry.value.wk, 'hex'));
|
|
147
|
+
writerEntry.value.isValid = false;
|
|
148
|
+
if (writerEntry.value.isIndexer === true) {
|
|
149
|
+
writerEntry.value.isIndexer = false;
|
|
150
|
+
}
|
|
151
|
+
await view.put(op.key, writerEntry.value);
|
|
152
|
+
console.log(`Writer removed: ${writerEntry.value.wk}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} else if (op.type === 'initBootstrap') {
|
|
158
|
+
// this operation initializes the bootstrap skp to grant this public key the owner status.
|
|
159
|
+
//TODO: ADD MORE SANITIZATION. THIS IS STILL JS.
|
|
160
|
+
//TODO: HANDLE ERRORS?
|
|
161
|
+
//TODO: it can be optimalized by adding variables to don't call Buffer.from multiple times.
|
|
162
|
+
if (node.from.key.toString('hex') === this.bootstrap) {
|
|
163
|
+
const message = Buffer.concat([
|
|
164
|
+
Buffer.from(JSON.stringify(op.value.hpm)),
|
|
165
|
+
Buffer.from(op.value.wk, 'hex'),
|
|
166
|
+
Buffer.from(op.value.skp, 'hex')
|
|
167
|
+
//here should be nonce anyway in the future - generated randomly from the huge space.
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
const pop1Valid = this.wallet.verify(op.value.pop1, message, Buffer.from(op.value.hpm.signers[0].publicKey));
|
|
171
|
+
const pop2Valid = this.wallet.verify(op.value.pop2, message, Buffer.from(op.value.skp, 'hex'));
|
|
172
|
+
const restoredManifest = restoreManifest(op.value.hpm); //temporary workaround
|
|
173
|
+
|
|
174
|
+
if (pop1Valid && pop2Valid && manifestHash(createManifest(restoredManifest)).toString('hex') === this.bootstrap) {
|
|
175
|
+
await view.put(op.key, op.value);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else if (op.type === 'addIndexer') {
|
|
179
|
+
if (node.from.key.toString('hex') === _this.bootstrap) {
|
|
180
|
+
//Simplify signature
|
|
181
|
+
const message = Buffer.concat([
|
|
182
|
+
Buffer.from(op.value.ptpk, 'hex'),
|
|
183
|
+
Buffer.from(op.value.pwk, 'hex')]
|
|
184
|
+
)
|
|
185
|
+
const popValid = _this.wallet.verify(op.value.pop, message, op.key);
|
|
186
|
+
const writerEntry = await _this.base.view.get(op.value.ptpk);
|
|
187
|
+
if (popValid && writerEntry !== null && writerEntry.value.isValid === true && writerEntry.value.wk === op.value.pwk && writerEntry.value.isIndexer === false) {
|
|
188
|
+
await base.removeWriter(Buffer.from(writerEntry.value.wk, 'hex'));
|
|
189
|
+
await base.addWriter(b4a.from(writerEntry.value.wk, 'hex'), { indexer: true })
|
|
190
|
+
writerEntry.value.isIndexer = true;
|
|
191
|
+
await view.put(op.value.ptpk, writerEntry.value);
|
|
192
|
+
console.log(`${op.value.ptpk}:${op.value.pwk} writer became indexer`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} else if (op.type === 'removeIndexer') {
|
|
196
|
+
if (node.from.key.toString('hex') === _this.bootstrap) {
|
|
197
|
+
//Simplify signature
|
|
198
|
+
const message = Buffer.concat([
|
|
199
|
+
Buffer.from(op.value.ptpk, 'hex'),
|
|
200
|
+
Buffer.from(op.value.pwk, 'hex')]
|
|
201
|
+
)
|
|
202
|
+
const popValid = _this.wallet.verify(op.value.pop, message, op.key);
|
|
203
|
+
const writerEntry = await _this.base.view.get(op.value.ptpk);
|
|
204
|
+
|
|
205
|
+
if (popValid && writerEntry !== null && writerEntry.value.isValid === true && writerEntry.value.wk === op.value.pwk && writerEntry.value.isIndexer === true) {
|
|
206
|
+
await base.removeWriter(Buffer.from(writerEntry.value.wk, 'hex'));
|
|
207
|
+
await base.addWriter(b4a.from(writerEntry.value.wk, 'hex'), { indexer: false })
|
|
208
|
+
writerEntry.value.isIndexer = false;
|
|
209
|
+
await view.put(op.value.ptpk, writerEntry.value);
|
|
210
|
+
console.log(`Writer ${op.value.ptpk}:${op.value.pwk} is not longer indexer`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
86
213
|
}
|
|
87
214
|
}
|
|
88
215
|
}
|
|
@@ -92,17 +219,23 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
92
219
|
|
|
93
220
|
async _open() {
|
|
94
221
|
await this.base.ready();
|
|
95
|
-
if(this.enable_wallet){
|
|
96
|
-
await this
|
|
222
|
+
if (this.enable_wallet && !this.isVerifyOnly) {
|
|
223
|
+
await this.wallet.initKeyPair(this.KEY_PAIR_PATH);
|
|
97
224
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.writerLocalKey = b4a.toString(this.base.local.key, 'hex');
|
|
225
|
+
|
|
226
|
+
this.writingKey = b4a.toString(this.base.local.key, 'hex');
|
|
227
|
+
|
|
102
228
|
if (this.replicate) await this._replicate();
|
|
103
229
|
if (this.enable_txchannel) {
|
|
104
230
|
await this.txChannel();
|
|
105
231
|
}
|
|
232
|
+
this.writerManager = new WriterManager(this);
|
|
233
|
+
|
|
234
|
+
console.log('View Length:', this.base.view.core.length);
|
|
235
|
+
console.log('View Signed Length:', this.base.view.core.signedLength);
|
|
236
|
+
console.log('MSB Key:', Buffer(this.base.view.core.key).toString('hex'));
|
|
237
|
+
console.log(`isWritable? ${this.base.writable}`);
|
|
238
|
+
console.log(`isIndexer: ${this.base.isIndexer}`);
|
|
106
239
|
}
|
|
107
240
|
|
|
108
241
|
async close() {
|
|
@@ -116,23 +249,19 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
116
249
|
this.tx_swarm = new Hyperswarm({ maxPeers: 1024, maxParallel: 512, maxServerConnections: 256 });
|
|
117
250
|
this.tx_swarm.on('connection', async (connection, peerInfo) => {
|
|
118
251
|
const _this = this;
|
|
119
|
-
const peerName = b4a.toString(connection.remotePublicKey, 'hex');
|
|
120
|
-
this.connectedPeers.add(peerName);
|
|
121
|
-
this.connectedNodes++;
|
|
122
252
|
|
|
123
253
|
connection.on('close', () => {
|
|
124
|
-
this.connectedNodes--;
|
|
125
|
-
this.connectedPeers.delete(peerName);
|
|
126
254
|
});
|
|
127
255
|
|
|
128
256
|
connection.on('error', (error) => { });
|
|
129
257
|
|
|
130
258
|
connection.on('data', async (msg) => {
|
|
131
259
|
|
|
132
|
-
|
|
260
|
+
// indexers are NOT supposed to validate transactions!
|
|
261
|
+
if (_this.base.isIndexer) return;
|
|
133
262
|
|
|
134
263
|
// TODO: decide if a tx rejection should be responded with
|
|
135
|
-
if(_this.tx_pool.length >= 1000) {
|
|
264
|
+
if (_this.tx_pool.length >= 1000) {
|
|
136
265
|
console.log('pool full');
|
|
137
266
|
return
|
|
138
267
|
}
|
|
@@ -140,16 +269,17 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
140
269
|
if(Buffer.byteLength(msg) > 3072) return;
|
|
141
270
|
|
|
142
271
|
try {
|
|
143
|
-
|
|
144
272
|
const parsedPreTx = JSON.parse(msg);
|
|
145
273
|
|
|
146
274
|
if (sanitizeTransaction(parsedPreTx) &&
|
|
147
275
|
parsedPreTx.op === 'pre-tx' &&
|
|
148
|
-
|
|
149
|
-
parsedPreTx.
|
|
276
|
+
restoreHash(parsedPreTx) !== parsedPreTx.tx &&
|
|
277
|
+
this.wallet.verify(Buffer.from(parsedPreTx.is, 'hex'), Buffer.from(parsedPreTx.tx, 'utf-8'), Buffer.from(parsedPreTx.ipk, 'hex')) &&
|
|
278
|
+
parsedPreTx.w === _this.writingKey &&
|
|
150
279
|
null === await _this.base.view.get(parsedPreTx.tx)
|
|
151
280
|
) {
|
|
152
|
-
const signature =
|
|
281
|
+
const signature = this.wallet.sign(Buffer.from(parsedPreTx.tx, 'utf-8'));
|
|
282
|
+
|
|
153
283
|
const append_tx = {
|
|
154
284
|
op: 'post-tx',
|
|
155
285
|
tx: parsedPreTx.tx,
|
|
@@ -159,10 +289,10 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
159
289
|
ipk: parsedPreTx.ipk,
|
|
160
290
|
ch: parsedPreTx.ch,
|
|
161
291
|
in: parsedPreTx.in,
|
|
162
|
-
ws:
|
|
163
|
-
wp: this.
|
|
292
|
+
ws: JSON.parse(JSON.stringify(signature)),
|
|
293
|
+
wp: JSON.parse(JSON.stringify(this.wallet.publicKey)),
|
|
164
294
|
};
|
|
165
|
-
_this.tx_pool.push({ tx: parsedPreTx.tx, append_tx
|
|
295
|
+
_this.tx_pool.push({ tx: parsedPreTx.tx, append_tx: append_tx });
|
|
166
296
|
}
|
|
167
297
|
} catch (e) {
|
|
168
298
|
console.log(e)
|
|
@@ -176,44 +306,40 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
176
306
|
console.log('Joined MSB TX channel');
|
|
177
307
|
}
|
|
178
308
|
|
|
179
|
-
async pool(){
|
|
180
|
-
while(true){
|
|
181
|
-
if(this.tx_pool.length > 0){
|
|
309
|
+
async pool() {
|
|
310
|
+
while (true) {
|
|
311
|
+
if (this.tx_pool.length > 0) {
|
|
182
312
|
const length = this.tx_pool.length;
|
|
183
|
-
for(let i = 0; i < length; i++){
|
|
313
|
+
for (let i = 0; i < length; i++) {
|
|
184
314
|
await this.base.append({ type: 'tx', key: this.tx_pool[i].tx, value: this.tx_pool[i].append_tx });
|
|
185
|
-
await
|
|
315
|
+
await sleep(5);
|
|
186
316
|
}
|
|
187
317
|
this.tx_pool.splice(0, length);
|
|
188
318
|
}
|
|
189
|
-
await
|
|
319
|
+
await sleep(10);
|
|
190
320
|
}
|
|
191
321
|
}
|
|
192
322
|
|
|
193
|
-
async sleep(ms) {
|
|
194
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
323
|
async _replicate() {
|
|
198
324
|
if (!this.swarm) {
|
|
199
|
-
const keyPair = await this.store.createKeyPair('hyperswarm');
|
|
325
|
+
const keyPair = this.base.local.keyPair //await this.store.createKeyPair('hyperswarm');
|
|
200
326
|
this.swarm = new Hyperswarm({ keyPair, maxPeers: 1024, maxParallel: 512, maxServerConnections: 256 });
|
|
201
327
|
|
|
202
328
|
console.log(`Channel: ${this.channel}`);
|
|
203
|
-
console.log(`Writer key: ${this.
|
|
204
|
-
|
|
329
|
+
console.log(`Writer key: ${this.writingKey}`)
|
|
330
|
+
|
|
205
331
|
this.swarm.on('connection', async (connection, peerInfo) => {
|
|
206
|
-
const peerName = b4a.toString(connection.remotePublicKey, 'hex');
|
|
207
|
-
this.connectedPeers.add(peerName);
|
|
208
332
|
wakeup.addStream(connection);
|
|
209
333
|
this.store.replicate(connection);
|
|
210
|
-
|
|
334
|
+
|
|
211
335
|
|
|
212
336
|
connection.on('close', () => {
|
|
213
|
-
this.connectedNodes--;
|
|
214
|
-
this.connectedPeers.delete(peerName);
|
|
215
337
|
});
|
|
216
338
|
|
|
339
|
+
connection.on('data', async data => {
|
|
340
|
+
await WriterManager.handleIncomingWriterEvent(this, data);
|
|
341
|
+
})
|
|
342
|
+
|
|
217
343
|
connection.on('error', (error) => { });
|
|
218
344
|
|
|
219
345
|
if (!this.isStreaming) {
|
|
@@ -236,30 +362,6 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
236
362
|
});
|
|
237
363
|
}
|
|
238
364
|
|
|
239
|
-
async verifyDag() {
|
|
240
|
-
try {
|
|
241
|
-
console.log('--- DAG Monitoring ---');
|
|
242
|
-
const dagView = await this.base.view.core.treeHash();
|
|
243
|
-
const lengthdagView = this.base.view.core.length;
|
|
244
|
-
const dagSystem = await this.base.system.core.treeHash();
|
|
245
|
-
const lengthdagSystem = this.base.system.core.length;
|
|
246
|
-
console.log('this.base.view.core.signedLength:', this.base.view.core.signedLength);
|
|
247
|
-
console.log("this.base.signedLength", this.base.signedLength);
|
|
248
|
-
console.log("this.base.linearizer.indexers.length", this.base.linearizer.indexers.length);
|
|
249
|
-
console.log("this.base.indexedLength", this.base.indexedLength);
|
|
250
|
-
//console.log("this.base.system.core", this.base.system.core);
|
|
251
|
-
console.log(`writerLocalKey: ${this.writerLocalKey}`);
|
|
252
|
-
console.log(`base.key: ${this.base.key.toString('hex')}`);
|
|
253
|
-
console.log('discoveryKey:', b4a.toString(this.base.discoveryKey, 'hex'));
|
|
254
|
-
|
|
255
|
-
console.log(`VIEW Dag: ${dagView.toString('hex')} (length: ${lengthdagView})`);
|
|
256
|
-
console.log(`SYSTEM Dag: ${dagSystem.toString('hex')} (length: ${lengthdagSystem})`);
|
|
257
|
-
|
|
258
|
-
} catch (error) {
|
|
259
|
-
console.error('Error during DAG monitoring:', error.message);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
365
|
async interactiveMode() {
|
|
264
366
|
const rl = readline.createInterface({
|
|
265
367
|
input: process.stdin,
|
|
@@ -267,102 +369,43 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
267
369
|
});
|
|
268
370
|
|
|
269
371
|
console.log('MSB started. Available commands:');
|
|
270
|
-
console.log('- /
|
|
271
|
-
console.log('- /
|
|
372
|
+
console.log('- /addMe: send request to admin to become a writer node in the TRAC Network');
|
|
373
|
+
console.log('- /removeMe: send request to admin to remove writer node from the TRAC Network');
|
|
374
|
+
console.log('- /addIndexer <TracPublicKey> <WritingKey> (Admin only): enter a public key and writing key to make node as Indexer. Node have to be a writer already. ');
|
|
375
|
+
console.log('- /removeIndexer <TracPublicKey> <WritingKey> (Admin only): enter a public key and writing key to take off Indexer role. Node have to be a writer already');
|
|
272
376
|
console.log('- /dag: check system properties such as writer key, DAG, etc.');
|
|
273
377
|
console.log('- /exit: Exit the program');
|
|
274
378
|
|
|
275
379
|
rl.on('line', async (input) => {
|
|
276
380
|
switch (input) {
|
|
277
381
|
case '/dag':
|
|
278
|
-
await this.
|
|
382
|
+
await verifyDag(this.base);
|
|
279
383
|
break;
|
|
280
384
|
case '/exit':
|
|
281
385
|
console.log('Exiting...');
|
|
282
386
|
rl.close();
|
|
283
387
|
await this.close();
|
|
284
388
|
process.exit(0);
|
|
389
|
+
case '/addMe':
|
|
390
|
+
await this.writerManager.addMe();
|
|
391
|
+
break;
|
|
392
|
+
case '/removeMe':
|
|
393
|
+
await this.writerManager.removeMe();
|
|
285
394
|
break;
|
|
286
395
|
default:
|
|
287
|
-
if (input.startsWith('/
|
|
288
|
-
|
|
396
|
+
if (input.startsWith('/addIndexer')) {
|
|
397
|
+
const splitted = input.split(' ');
|
|
398
|
+
this.writerManager.addIndexer(splitted[1], splitted[2]);
|
|
399
|
+
|
|
400
|
+
} else if (input.startsWith('/removeIndexer')) {
|
|
401
|
+
const splitted = input.split(' ');
|
|
402
|
+
this.writerManager.removeIndexer(splitted[1], splitted[2]);
|
|
289
403
|
}
|
|
290
404
|
}
|
|
291
405
|
rl.prompt();
|
|
292
406
|
});
|
|
293
|
-
|
|
294
407
|
rl.prompt();
|
|
295
408
|
}
|
|
296
|
-
|
|
297
|
-
async #getMnemonicInteractiveMode() {
|
|
298
|
-
const rl = readline.createInterface({
|
|
299
|
-
input: process.stdin,
|
|
300
|
-
output: process.stdout
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
const question = (query) => {
|
|
304
|
-
return new Promise(resolve => {
|
|
305
|
-
rl.question(query, resolve);
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
let mnemonic;
|
|
310
|
-
let choice = '';
|
|
311
|
-
while (!choice.trim()) {
|
|
312
|
-
choice = await question("[1]. Generate new mnemonic phrase\n[2]. Restore keypair from backed up mnemonic phrase\nYour choice (1/2): ");
|
|
313
|
-
switch (choice) {
|
|
314
|
-
case '1':
|
|
315
|
-
mnemonic = undefined
|
|
316
|
-
break;
|
|
317
|
-
case '2':
|
|
318
|
-
const mnemonicInput = await question("Enter your mnemonic phrase: ");
|
|
319
|
-
mnemonic = edKeyGen.sanitizeMnemonic(mnemonicInput);
|
|
320
|
-
break;
|
|
321
|
-
default:
|
|
322
|
-
console.log("Invalid choice. Please select again");
|
|
323
|
-
choice = '';
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
rl.close();
|
|
328
|
-
return mnemonic;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
async #initKeyPair() {
|
|
332
|
-
// TODO: User shouldn't be allowed to store it in unencrypted form. ASK for a password to encrypt it. ENCRYPT(HASH(PASSWORD,SALT),FILE)/DECRYPT(HASH(PASSWORD,SALT),ENCRYPTED_FILE)?
|
|
333
|
-
try {
|
|
334
|
-
// Check if the key file exists
|
|
335
|
-
if (fs.existsSync(this.KEY_PAIR_PATH)) {
|
|
336
|
-
const keyPair = JSON.parse(fs.readFileSync(this.KEY_PAIR_PATH));
|
|
337
|
-
this.signingKeyPair = {
|
|
338
|
-
publicKey: Buffer.from(keyPair.publicKey, 'hex'),
|
|
339
|
-
secretKey: Buffer.from(keyPair.secretKey, 'hex')
|
|
340
|
-
}
|
|
341
|
-
} else {
|
|
342
|
-
console.log("Key file was not found. How do you wish to proceed?");
|
|
343
|
-
const mnemonic = await this.#getMnemonicInteractiveMode();
|
|
344
|
-
|
|
345
|
-
const generatedSecrets = edKeyGen.generateKeyPair(mnemonic);
|
|
346
|
-
const keyPair = {
|
|
347
|
-
publicKey: Buffer.from(generatedSecrets.publicKey).toString('hex'),
|
|
348
|
-
secretKey: Buffer.from(generatedSecrets.secretKey).toString('hex')
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
//TODO: ASK USER TO WRITE FIRST SECOND AND LAST WORD OR SOMETHING SIMILAR TO CONFIRM THEY HAVE WRITTEN IT DOWN
|
|
352
|
-
if (!mnemonic) console.log("This is your mnemonic:\n", generatedSecrets.mnemonic, "\nPlease back it up in a safe location")
|
|
353
|
-
|
|
354
|
-
fs.writeFileSync(this.KEY_PAIR_PATH, JSON.stringify(keyPair));
|
|
355
|
-
this.signingKeyPair = {
|
|
356
|
-
publicKey: generatedSecrets.publicKey,
|
|
357
|
-
secretKey: generatedSecrets.secretKey,
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
console.log("DEBUG: Key pair generated and stored in", this.KEY_PAIR_PATH);
|
|
361
|
-
}
|
|
362
|
-
} catch (err) {
|
|
363
|
-
console.error(err);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
409
|
}
|
|
367
410
|
|
|
368
411
|
function noop() { }
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import ReadyResource from 'ready-resource';
|
|
2
|
+
|
|
3
|
+
//TODO ADD THROWS
|
|
4
|
+
//TODO FOR NOW IT'S FINE. IT MUST BE INTEGRATED WITH TRAC WALLET
|
|
5
|
+
//TODO: How about nonce if edDSA is deterministic?
|
|
6
|
+
//TODO: if enable_wallet is false then user shouldn't be allowd to add or remove writer
|
|
7
|
+
/**
|
|
8
|
+
* WriterManager manages writer nodes in the TRAC NETWORK, handling events, adding/removing writers, and managing the bootstrap process.
|
|
9
|
+
* It interacts with the MainSettlementBus instance to perform key operations.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const MS_TO_WAIT = 5000;
|
|
13
|
+
|
|
14
|
+
export class WriterManager extends ReadyResource {
|
|
15
|
+
/**
|
|
16
|
+
* Initializes the WriterManager with the given MainSettlementBus instance.
|
|
17
|
+
* @param {MainSettlementBus} msbInstance - An instance of the MainSettlementBus class.
|
|
18
|
+
*/
|
|
19
|
+
constructor(msbInstance) {
|
|
20
|
+
super();
|
|
21
|
+
this.base = msbInstance.base;
|
|
22
|
+
this.wallet = msbInstance.wallet;
|
|
23
|
+
this.swarm = msbInstance.swarm;
|
|
24
|
+
this.bootstrap = msbInstance.bootstrap;
|
|
25
|
+
this.writingKey = msbInstance.writingKey;
|
|
26
|
+
|
|
27
|
+
this.#initBootstrap(msbInstance);
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Initializes the bootstrap initialization process if the node is a bootstrap node and can be only executed by the bootstrap node.
|
|
33
|
+
* @returns {Promise<void>} Resolves once the bootstrap initialization is complete.
|
|
34
|
+
*/
|
|
35
|
+
async #initBootstrap(msbInstance) {
|
|
36
|
+
try {
|
|
37
|
+
|
|
38
|
+
if (this.writingKey && this.writingKey === this.bootstrap) {
|
|
39
|
+
|
|
40
|
+
const bootStrapEntry = await this.getBootstrapEntry();
|
|
41
|
+
if (bootStrapEntry === null) {
|
|
42
|
+
|
|
43
|
+
const message = Buffer.concat([
|
|
44
|
+
Buffer.from(JSON.stringify(this.base.localWriter.core.manifest)),
|
|
45
|
+
Buffer.from(this.writingKey, 'hex'),
|
|
46
|
+
Buffer.from(this.wallet.publicKey, 'hex')
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
//SEND BOOTSTRAP TRAC MANIFEST
|
|
50
|
+
await this.base.append({
|
|
51
|
+
type: 'initBootstrap',
|
|
52
|
+
key: 'bootstrap',
|
|
53
|
+
value: {
|
|
54
|
+
wk: this.writingKey,
|
|
55
|
+
hpm: this.base.localWriter.core.manifest,
|
|
56
|
+
skp: this.wallet.publicKey,
|
|
57
|
+
pop1: this.wallet.sign(message, this.base.localWriter.core.keyPair.secretKey),
|
|
58
|
+
pop2: this.wallet.sign(message)
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
console.log(`Bootstrap node ${this.writingKey} is ready to accept writers`);
|
|
63
|
+
await this.#WriterEventListener(msbInstance);
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`err in `, error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Listens for writer events and processes them by appending the parsed request to the base.
|
|
72
|
+
* At this moment only Bootstrap is listening for writer events.
|
|
73
|
+
* @private
|
|
74
|
+
*/
|
|
75
|
+
async #WriterEventListener(msbInstance) {
|
|
76
|
+
msbInstance.on('writerEvent', async (parsedRequest) => {
|
|
77
|
+
await this.base.append(parsedRequest);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Adds the current node as a writer if it is not already a writer.
|
|
83
|
+
* The node must connect to the bootstrap node to complete the operation.
|
|
84
|
+
* @returns {Promise<void>} Resolves once the writer has been added or if the operation fails.
|
|
85
|
+
*/
|
|
86
|
+
async addMe() {
|
|
87
|
+
try {
|
|
88
|
+
|
|
89
|
+
if (this.writingKey === this.bootstrap) {
|
|
90
|
+
console.log('Bootstrap node cannot add itself');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const writerEntry = await this.base.view.get(this.wallet.publicKey);
|
|
95
|
+
if (writerEntry !== null && writerEntry.value.isValid) {
|
|
96
|
+
console.log(`Cannot perform operation because node is already writer`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const bootstrapEntry = await this.getBootstrapEntry();
|
|
101
|
+
if (!bootstrapEntry?.value?.hpm?.signers?.[0]?.publicKey?.data) {
|
|
102
|
+
console.log(`Bootstrap key not found`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const bootstrapPubKey = Buffer.from((bootstrapEntry.value.hpm.signers[0].publicKey.data)).toString('hex');
|
|
107
|
+
|
|
108
|
+
this.swarm.connections.forEach(async conn => {
|
|
109
|
+
if (conn.connected && conn.remotePublicKey.toString('hex') === bootstrapPubKey) {
|
|
110
|
+
|
|
111
|
+
const message = Buffer.concat([
|
|
112
|
+
Buffer.from(JSON.stringify(this.base.local.core.manifest)),
|
|
113
|
+
Buffer.from(this.writingKey, 'hex'),
|
|
114
|
+
Buffer.from(this.wallet.publicKey, 'hex')
|
|
115
|
+
//TODO: ADD NONCE?
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
//SEND TRAC MANIFEST
|
|
119
|
+
conn.write(JSON.stringify({
|
|
120
|
+
type: 'addWriter',
|
|
121
|
+
key: this.wallet.publicKey,
|
|
122
|
+
value: {
|
|
123
|
+
wk: this.writingKey,
|
|
124
|
+
hpm: this.base.local.core.manifest,
|
|
125
|
+
pop1: this.wallet.sign(message, this.base.local.core.header.keyPair.secretKey),
|
|
126
|
+
pop2: this.wallet.sign(message),
|
|
127
|
+
//TODO: ADD NONCE?
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
}));
|
|
131
|
+
|
|
132
|
+
setTimeout(async () => {
|
|
133
|
+
const updatedWriterEntry = await this.base.view.get(this.wallet.publicKey);
|
|
134
|
+
if (updatedWriterEntry !== null && updatedWriterEntry.value.isValid) {
|
|
135
|
+
console.log(`Writer ${this.writingKey} was successfully added.`);
|
|
136
|
+
} else {
|
|
137
|
+
console.warn(`Writer ${this.writingKey} was NOT added.`);
|
|
138
|
+
}
|
|
139
|
+
}, MS_TO_WAIT);
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error(`err in `, error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Removes the current node as a writer.
|
|
148
|
+
* The node must connect to the bootstrap node to complete the removal operation.
|
|
149
|
+
* @returns {Promise<void>} Resolves once the writer has been removed or if the operation fails.
|
|
150
|
+
*/
|
|
151
|
+
async removeMe() {
|
|
152
|
+
try {
|
|
153
|
+
|
|
154
|
+
if (this.writingKey === this.bootstrap) {
|
|
155
|
+
console.log('Bootstrap node cannot remove itself');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const writerEntry = await this.base.view.get(this.wallet.publicKey);
|
|
160
|
+
if (writerEntry === null || !writerEntry.value.isValid) {
|
|
161
|
+
console.log(`Your key does not exist in the database or you can't remove it`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const bootstrapEntry = await this.getBootstrapEntry();
|
|
166
|
+
|
|
167
|
+
if (bootstrapEntry === null || !bootstrapEntry.value?.hpm?.signers?.[0]?.publicKey?.data) {
|
|
168
|
+
console.log(`Bootstrap key not found`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const bootstrapPubKey = Buffer.from(bootstrapEntry.value.hpm.signers[0].publicKey.data).toString('hex');
|
|
173
|
+
|
|
174
|
+
this.swarm.connections.forEach(async conn => {
|
|
175
|
+
if (conn.connected && conn.remotePublicKey.toString('hex') === bootstrapPubKey) {
|
|
176
|
+
|
|
177
|
+
const message = Buffer.concat([
|
|
178
|
+
Buffer.from(this.wallet.publicKey, 'hex') // TODO: TO REDUCE THE SIZE OF THE MESSAGE WE CAN SEND SIMPLE STRING SUCHAS "REMOVE". You can just HASH the message and sign the hash.
|
|
179
|
+
//TODO: ADD NONCE?
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
//SEND TRAC MANIFEST
|
|
183
|
+
conn.write(JSON.stringify({
|
|
184
|
+
type: 'removeWriter',
|
|
185
|
+
key: this.wallet.publicKey,
|
|
186
|
+
value: {
|
|
187
|
+
pop: this.wallet.sign(message),
|
|
188
|
+
//TODO: ADD NONCE?
|
|
189
|
+
}
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
setTimeout(async () => {
|
|
195
|
+
const updatedWriterEntry = await this.base.view.get(this.wallet.publicKey);
|
|
196
|
+
if (updatedWriterEntry !== null && !updatedWriterEntry.value.isValid) {
|
|
197
|
+
console.log(`Key successfully removed`);
|
|
198
|
+
} else {
|
|
199
|
+
console.log(`Failed to remove key`);
|
|
200
|
+
}
|
|
201
|
+
}, MS_TO_WAIT);
|
|
202
|
+
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error(`err in `, error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Adds a writer node as an indexer.
|
|
211
|
+
* The node must already be a valid writer to become an indexer.
|
|
212
|
+
* This operation can only be performed by the bootstrap/admin node.
|
|
213
|
+
*
|
|
214
|
+
* @param {string} peerTracPublicKey - The TRAC public key of the writer node to be promoted to indexer.
|
|
215
|
+
* @param {string} peerWritingKey - The writing key of the writer node to be promoted to indexer.
|
|
216
|
+
* @returns {Promise<void>} Resolves once the writer has been promoted to indexer or if the operation fails.
|
|
217
|
+
*/
|
|
218
|
+
async addIndexer(peerTracPublicKey, peerWritingKey) {
|
|
219
|
+
//TODO: ADD TIMEOUT WITH MESSAGE OF SUCCESS OR FAILURE.
|
|
220
|
+
//TODO IMPROVE CHECKS
|
|
221
|
+
if (this.writingKey !== this.bootstrap) {
|
|
222
|
+
console.log('Only bootstrap node can add indexer');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const writerEntry = await this.base.view.get(peerTracPublicKey);
|
|
227
|
+
if (writerEntry === null || writerEntry.value.isValid === false || writerEntry.value.wk !== peerWritingKey || (writerEntry.value.isValid === true && writerEntry.value.isIndexer === true)) {
|
|
228
|
+
console.log(`Writer ${peerTracPublicKey}:${this.writingKey} cannot become an indexer`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const message = Buffer.concat([
|
|
233
|
+
Buffer.from(peerTracPublicKey, 'hex'),
|
|
234
|
+
Buffer.from(peerWritingKey, 'hex')
|
|
235
|
+
]);
|
|
236
|
+
|
|
237
|
+
const indexerRequest = {
|
|
238
|
+
type: 'addIndexer',
|
|
239
|
+
key: this.wallet.publicKey,
|
|
240
|
+
value: {
|
|
241
|
+
ptpk: peerTracPublicKey,
|
|
242
|
+
pwk: peerWritingKey,
|
|
243
|
+
pop: this.wallet.sign(message)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
await this.base.append(indexerRequest);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Removes the indexer status from a writer node.
|
|
250
|
+
* The node must already be a valid writer and an indexer to lose its indexer status.
|
|
251
|
+
* This operation can only be performed by the bootstrap node.
|
|
252
|
+
*
|
|
253
|
+
* @param {string} peerTracPublicKey - The TRAC public key of the writer node to lose indexer status.
|
|
254
|
+
* @param {string} peerWritingKey - The writing key of the writer node to lose indexer status.
|
|
255
|
+
* @returns {Promise<void>} Resolves once the writer has lost indexer status or if the operation fails.
|
|
256
|
+
*/
|
|
257
|
+
async removeIndexer(peerTracPublicKey, peerWritingKey) {
|
|
258
|
+
//TODO: ADD TIMEOUT WITH MESSAGE OF SUCCESS OR FAILURE.
|
|
259
|
+
//TODO IMPROVE CHECKS
|
|
260
|
+
if (this.writingKey !== this.bootstrap) {
|
|
261
|
+
console.log('Only bootstrap node can remove indexer');
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const writerEntry = await this.base.view.get(peerTracPublicKey);
|
|
265
|
+
|
|
266
|
+
if (writerEntry === null || writerEntry.value.isValid === false || writerEntry.value.wk !== peerWritingKey || (writerEntry.value.isValid === true && writerEntry.value.isIndexer === false)) {
|
|
267
|
+
console.log(`Writer ${peerTracPublicKey}:${this.writingKey} cannot lose indexer status`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const message = Buffer.concat([
|
|
272
|
+
Buffer.from(peerTracPublicKey, 'hex'),
|
|
273
|
+
Buffer.from(peerWritingKey, 'hex')
|
|
274
|
+
]);
|
|
275
|
+
|
|
276
|
+
const indexerRequest = {
|
|
277
|
+
type: 'removeIndexer',
|
|
278
|
+
key: this.wallet.publicKey,
|
|
279
|
+
value: {
|
|
280
|
+
ptpk: peerTracPublicKey,
|
|
281
|
+
pwk: peerWritingKey,
|
|
282
|
+
pop: this.wallet.sign(message)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
await this.base.append(indexerRequest);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Retrieves the bootstrap entry from the base view.
|
|
290
|
+
*
|
|
291
|
+
* @returns {Promise<Object|null>} The bootstrap entry if it exists, otherwise null.
|
|
292
|
+
*/
|
|
293
|
+
async getBootstrapEntry() {
|
|
294
|
+
return await this.base.view.get('bootstrap');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Handles incoming writer events by parsing the data and emitting a writerEvent.
|
|
299
|
+
*
|
|
300
|
+
* @param {MainSettlementBus} msbInstance - An instance of the MainSettlementBus class.
|
|
301
|
+
* @param {Buffer} data - The data received from the connection.
|
|
302
|
+
*/
|
|
303
|
+
static async handleIncomingWriterEvent(msbInstance, data) {
|
|
304
|
+
try {
|
|
305
|
+
const bufferData = data.toString();
|
|
306
|
+
const parsedRequest = JSON.parse(bufferData);
|
|
307
|
+
if (parsedRequest.type === 'addWriter' || parsedRequest.type === 'removeWriter') {
|
|
308
|
+
msbInstance.emit('writerEvent', parsedRequest);
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
// for now ignore the error
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export default WriterManager;
|