trac-msb 0.0.9 → 0.0.11
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 +7 -3
- package/src/functions.js +64 -8
- package/src/index.js +201 -159
- 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.11",
|
|
5
5
|
"pear": {
|
|
6
6
|
"name": "trac-msb",
|
|
7
7
|
"type": "terminal"
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
"test": "brittle test/*.test.js"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"autobase": "7.
|
|
15
|
+
"autobase": "7.0.45",
|
|
16
16
|
"b4a": "1.6.7",
|
|
17
17
|
"bare-fs": "4.0.1",
|
|
18
18
|
"brittle": "3.0.0",
|
|
19
|
-
"corestore": "7.0.
|
|
19
|
+
"corestore": "7.0.22",
|
|
20
20
|
"debounceify": "1.1.0",
|
|
21
21
|
"trac-wallet": "^0.0.3",
|
|
22
22
|
"hyperbee": "2.23.0",
|
|
@@ -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,35 @@ 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
|
+
if (this.enable_wallet) {
|
|
48
|
+
this.wallet = new PeerWallet({ isVerifyOnly: this.isVerifyOnly });
|
|
49
|
+
}
|
|
50
|
+
|
|
42
51
|
this.pool();
|
|
43
52
|
this.msbListener();
|
|
44
53
|
this._boot();
|
|
45
54
|
this.ready().catch(noop);
|
|
46
|
-
}
|
|
47
55
|
|
|
56
|
+
}
|
|
57
|
+
//TODO: Move apply to the separate file
|
|
48
58
|
_boot() {
|
|
49
59
|
const _this = this;
|
|
50
60
|
this.base = new Autobase(this.store, this.bootstrap, {
|
|
@@ -56,6 +66,8 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
56
66
|
keyEncoding: 'utf-8',
|
|
57
67
|
valueEncoding: 'json'
|
|
58
68
|
})
|
|
69
|
+
_this.keysView = _this.bee.sub('pubKeys');
|
|
70
|
+
|
|
59
71
|
return _this.bee;
|
|
60
72
|
},
|
|
61
73
|
|
|
@@ -64,25 +76,140 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
64
76
|
for (const node of nodes) {
|
|
65
77
|
const op = node.value;
|
|
66
78
|
const postTx = op.value;
|
|
67
|
-
|
|
79
|
+
|
|
80
|
+
if (!op || !op.type || !op.key || !op.value) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// WRITING & INDEXING
|
|
84
|
+
if (op.type === 'addWriter') {
|
|
85
|
+
//TODO: it can be optimalized by adding variables to don't call Buffer.from multiple times.
|
|
86
|
+
//TODO: SANITIZE INCOMPING PROPOSAL
|
|
87
|
+
if (node.from.key.toString('hex') === this.bootstrap) {
|
|
88
|
+
const message = Buffer.concat([
|
|
89
|
+
Buffer.from(JSON.stringify(op.value.hpm)),
|
|
90
|
+
Buffer.from(op.value.wk, 'hex'),
|
|
91
|
+
Buffer.from(op.key, 'hex')
|
|
92
|
+
//TODO: ADD THE NONCE?
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
const pop1Valid = this.wallet.verify(op.value.pop1, message, Buffer.from(op.value.hpm.signers[0].publicKey));
|
|
96
|
+
const pop2Valid = this.wallet.verify(op.value.pop2, message, op.key);
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
const restoredManifest = restoreManifest(op.value.hpm); //temporary workaround
|
|
100
|
+
if (pop1Valid && pop2Valid && manifestHash(createManifest(restoredManifest)).toString('hex') === op.value.wk) {
|
|
101
|
+
|
|
102
|
+
const writerEntry = this.base.view.get(op.key);
|
|
103
|
+
if (writerEntry === null || !writerEntry.isValid) {
|
|
104
|
+
await base.addWriter(b4a.from(op.value.wk, 'hex'), { isIndexer: false })
|
|
105
|
+
|
|
106
|
+
await view.put(op.key, {
|
|
107
|
+
wk: op.value.wk,
|
|
108
|
+
hpm: op.value.hpm,
|
|
109
|
+
pop1: op.value.pop1, // TODO: observation this is really necessary to store pops? IF NOT DELETE IT!
|
|
110
|
+
pop2: op.value.pop2,
|
|
111
|
+
isValid: true,
|
|
112
|
+
isIndexer: false
|
|
113
|
+
//TODO: ADD NONCE?
|
|
114
|
+
});
|
|
115
|
+
console.log(`Writer added: ${op.value.wk}`);
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} else if (op.type === 'removeWriter') {
|
|
121
|
+
//TODO: it can be optimalized by adding variables to don't call Buffer.from multiple times. And other operations
|
|
122
|
+
//TODO: SANITIZE INCOMPING PROPOSAL
|
|
123
|
+
if (node.from.key.toString('hex') === this.bootstrap) {
|
|
124
|
+
const publicKey = Buffer.from(op.key, 'hex');
|
|
125
|
+
const message = Buffer.concat([
|
|
126
|
+
publicKey
|
|
127
|
+
//TODO: ADD NONCE ?
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
const popIsValid = this.wallet.verify(op.value.pop, message, publicKey);
|
|
131
|
+
if (popIsValid) {
|
|
132
|
+
const writerEntry = await _this.base.view.get(op.key)
|
|
133
|
+
if (writerEntry !== null && writerEntry.value.isValid ) {
|
|
134
|
+
await base.removeWriter(Buffer.from(writerEntry.value.wk, 'hex'));
|
|
135
|
+
writerEntry.value.isValid = false;
|
|
136
|
+
if (writerEntry.value.isIndexer === true) {
|
|
137
|
+
writerEntry.value.isIndexer = false;
|
|
138
|
+
}
|
|
139
|
+
await view.put(op.key, writerEntry.value);
|
|
140
|
+
console.log(`Writer removed: ${writerEntry.value.wk}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} else if (op.type === 'initBootstrap') {
|
|
146
|
+
// this operation initializes the bootstrap skp to grant this public key the owner status.
|
|
147
|
+
//TODO: ADD MORE SANITIZATION. THIS IS STILL JS.
|
|
148
|
+
//TODO: HANDLE ERRORS?
|
|
149
|
+
//TODO: it can be optimalized by adding variables to don't call Buffer.from multiple times.
|
|
150
|
+
if (node.from.key.toString('hex') === this.bootstrap) {
|
|
151
|
+
const message = Buffer.concat([
|
|
152
|
+
Buffer.from(JSON.stringify(op.value.hpm)),
|
|
153
|
+
Buffer.from(op.value.wk, 'hex'),
|
|
154
|
+
Buffer.from(op.value.skp, 'hex')
|
|
155
|
+
//here should be nonce anyway in the future - generated randomly from the huge space.
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
const pop1Valid = this.wallet.verify(op.value.pop1, message, Buffer.from(op.value.hpm.signers[0].publicKey));
|
|
159
|
+
const pop2Valid = this.wallet.verify(op.value.pop2, message, Buffer.from(op.value.skp, 'hex'));
|
|
160
|
+
const restoredManifest = restoreManifest(op.value.hpm); //temporary workaround
|
|
161
|
+
|
|
162
|
+
if (pop1Valid && pop2Valid && manifestHash(createManifest(restoredManifest)).toString('hex') === this.bootstrap) {
|
|
163
|
+
await view.put(op.key, op.value);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} else if (op.type === 'addIndexer') {
|
|
167
|
+
if (node.from.key.toString('hex') === _this.bootstrap) {
|
|
168
|
+
//Simplify signature
|
|
169
|
+
const message = Buffer.concat([
|
|
170
|
+
Buffer.from(op.value.ptpk, 'hex'),
|
|
171
|
+
Buffer.from(op.value.pwk, 'hex')]
|
|
172
|
+
)
|
|
173
|
+
const popValid = _this.wallet.verify(op.value.pop, message, op.key);
|
|
174
|
+
const writerEntry = await _this.base.view.get(op.value.ptpk);
|
|
175
|
+
if (popValid && writerEntry !== null && writerEntry.value.isValid === true && writerEntry.value.wk === op.value.pwk && writerEntry.value.isIndexer === false) {
|
|
176
|
+
await base.removeWriter(Buffer.from(writerEntry.value.wk, 'hex'));
|
|
177
|
+
await base.addWriter(b4a.from(writerEntry.value.wk, 'hex'), { indexer: true })
|
|
178
|
+
writerEntry.value.isIndexer = true;
|
|
179
|
+
await view.put(op.value.ptpk, writerEntry.value);
|
|
180
|
+
console.log(`${op.value.ptpk}:${op.value.pwk} writer became indexer`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} else if (op.type === 'removeIndexer') {
|
|
184
|
+
if (node.from.key.toString('hex') === _this.bootstrap) {
|
|
185
|
+
//Simplify signature
|
|
186
|
+
const message = Buffer.concat([
|
|
187
|
+
Buffer.from(op.value.ptpk, 'hex'),
|
|
188
|
+
Buffer.from(op.value.pwk, 'hex')]
|
|
189
|
+
)
|
|
190
|
+
const popValid = _this.wallet.verify(op.value.pop, message, op.key);
|
|
191
|
+
const writerEntry = await _this.base.view.get(op.value.ptpk);
|
|
192
|
+
|
|
193
|
+
if (popValid && writerEntry !== null && writerEntry.value.isValid === true && writerEntry.value.wk === op.value.pwk && writerEntry.value.isIndexer === true) {
|
|
194
|
+
await base.removeWriter(Buffer.from(writerEntry.value.wk, 'hex'));
|
|
195
|
+
await base.addWriter(b4a.from(writerEntry.value.wk, 'hex'), { indexer: false })
|
|
196
|
+
writerEntry.value.isIndexer = false;
|
|
197
|
+
await view.put(op.value.ptpk, writerEntry.value);
|
|
198
|
+
console.log(`Writer ${op.value.ptpk}:${op.value.pwk} is not longer indexer`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} else if (op.type === 'tx') {
|
|
68
202
|
if (null === await view.get(op.key) &&
|
|
203
|
+
restoreHash(postTx) !== postTx.tx &&
|
|
69
204
|
sanitizeTransaction(postTx) &&
|
|
70
205
|
postTx.op === 'post-tx' &&
|
|
71
|
-
|
|
72
|
-
|
|
206
|
+
this.wallet.verify(Buffer.from(postTx.is, 'hex'), Buffer.from(postTx.tx, 'utf-8'), Buffer.from(postTx.ipk, 'hex')) &&// sender verification
|
|
207
|
+
this.wallet.verify(Buffer.from(postTx.ws, 'hex'), Buffer.from(postTx.tx, 'utf-8'), Buffer.from(postTx.wp, 'hex')) &&// writer verification
|
|
73
208
|
Buffer.byteLength(JSON.stringify(postTx)) <= 4096
|
|
74
209
|
) {
|
|
75
210
|
await view.put(op.key, op.value);
|
|
76
|
-
console.log(`TX: ${op.key} appended. Signed length: `,
|
|
211
|
+
console.log(`TX: ${op.key} appended. Signed length: `, _this.base.view.core.signedLength);
|
|
77
212
|
}
|
|
78
|
-
} else if (op.type === 'addWriter') {
|
|
79
|
-
const writerKey = b4a.from(op.key, 'hex');
|
|
80
|
-
await base.addWriter(writerKey);
|
|
81
|
-
console.log(`Writer added: ${op.key}`);
|
|
82
|
-
} else if (op.type === 'addWriter2') {
|
|
83
|
-
const writerKey = b4a.from(op.key, 'hex');
|
|
84
|
-
await base.addWriter(writerKey, { isIndexer : false });
|
|
85
|
-
console.log(`Writer added: ${op.key} non-indexer`);
|
|
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,18 @@ 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
|
-
if(_this.base.isIndexer) return;
|
|
260
|
+
if (!_this.base.isIndexer) return;
|
|
133
261
|
|
|
134
262
|
// TODO: decide if a tx rejection should be responded with
|
|
135
|
-
if(_this.tx_pool.length >= 1000) {
|
|
263
|
+
if (_this.tx_pool.length >= 1000) {
|
|
136
264
|
console.log('pool full');
|
|
137
265
|
return
|
|
138
266
|
}
|
|
@@ -140,16 +268,17 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
140
268
|
if(Buffer.byteLength(msg) > 3072) return;
|
|
141
269
|
|
|
142
270
|
try {
|
|
143
|
-
|
|
144
271
|
const parsedPreTx = JSON.parse(msg);
|
|
145
272
|
|
|
146
273
|
if (sanitizeTransaction(parsedPreTx) &&
|
|
147
274
|
parsedPreTx.op === 'pre-tx' &&
|
|
148
|
-
|
|
149
|
-
parsedPreTx.
|
|
275
|
+
restoreHash(parsedPreTx) !== parsedPreTx.tx &&
|
|
276
|
+
this.wallet.verify(Buffer.from(parsedPreTx.is, 'hex'), Buffer.from(parsedPreTx.tx, 'utf-8'), Buffer.from(parsedPreTx.ipk, 'hex')) &&
|
|
277
|
+
parsedPreTx.w === _this.writingKey &&
|
|
150
278
|
null === await _this.base.view.get(parsedPreTx.tx)
|
|
151
279
|
) {
|
|
152
|
-
const signature =
|
|
280
|
+
const signature = this.wallet.sign(Buffer.from(parsedPreTx.tx, 'utf-8'));
|
|
281
|
+
|
|
153
282
|
const append_tx = {
|
|
154
283
|
op: 'post-tx',
|
|
155
284
|
tx: parsedPreTx.tx,
|
|
@@ -159,10 +288,10 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
159
288
|
ipk: parsedPreTx.ipk,
|
|
160
289
|
ch: parsedPreTx.ch,
|
|
161
290
|
in: parsedPreTx.in,
|
|
162
|
-
ws:
|
|
163
|
-
wp: this.
|
|
291
|
+
ws: JSON.parse(JSON.stringify(signature)),
|
|
292
|
+
wp: JSON.parse(JSON.stringify(this.wallet.publicKey)),
|
|
164
293
|
};
|
|
165
|
-
_this.tx_pool.push({ tx: parsedPreTx.tx, append_tx
|
|
294
|
+
_this.tx_pool.push({ tx: parsedPreTx.tx, append_tx: append_tx });
|
|
166
295
|
}
|
|
167
296
|
} catch (e) {
|
|
168
297
|
console.log(e)
|
|
@@ -176,44 +305,40 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
176
305
|
console.log('Joined MSB TX channel');
|
|
177
306
|
}
|
|
178
307
|
|
|
179
|
-
async pool(){
|
|
180
|
-
while(true){
|
|
181
|
-
if(this.tx_pool.length > 0){
|
|
308
|
+
async pool() {
|
|
309
|
+
while (true) {
|
|
310
|
+
if (this.tx_pool.length > 0) {
|
|
182
311
|
const length = this.tx_pool.length;
|
|
183
|
-
for(let i = 0; i < length; i++){
|
|
312
|
+
for (let i = 0; i < length; i++) {
|
|
184
313
|
await this.base.append({ type: 'tx', key: this.tx_pool[i].tx, value: this.tx_pool[i].append_tx });
|
|
185
|
-
await
|
|
314
|
+
await sleep(5);
|
|
186
315
|
}
|
|
187
316
|
this.tx_pool.splice(0, length);
|
|
188
317
|
}
|
|
189
|
-
await
|
|
318
|
+
await sleep(10);
|
|
190
319
|
}
|
|
191
320
|
}
|
|
192
321
|
|
|
193
|
-
async sleep(ms) {
|
|
194
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
322
|
async _replicate() {
|
|
198
323
|
if (!this.swarm) {
|
|
199
|
-
const keyPair = await this.store.createKeyPair('hyperswarm');
|
|
324
|
+
const keyPair = this.base.local.keyPair //await this.store.createKeyPair('hyperswarm');
|
|
200
325
|
this.swarm = new Hyperswarm({ keyPair, maxPeers: 1024, maxParallel: 512, maxServerConnections: 256 });
|
|
201
326
|
|
|
202
327
|
console.log(`Channel: ${this.channel}`);
|
|
203
|
-
console.log(`Writer key: ${this.
|
|
204
|
-
|
|
328
|
+
console.log(`Writer key: ${this.writingKey}`)
|
|
329
|
+
|
|
205
330
|
this.swarm.on('connection', async (connection, peerInfo) => {
|
|
206
|
-
const peerName = b4a.toString(connection.remotePublicKey, 'hex');
|
|
207
|
-
this.connectedPeers.add(peerName);
|
|
208
331
|
wakeup.addStream(connection);
|
|
209
332
|
this.store.replicate(connection);
|
|
210
|
-
|
|
333
|
+
|
|
211
334
|
|
|
212
335
|
connection.on('close', () => {
|
|
213
|
-
this.connectedNodes--;
|
|
214
|
-
this.connectedPeers.delete(peerName);
|
|
215
336
|
});
|
|
216
337
|
|
|
338
|
+
connection.on('data', async data => {
|
|
339
|
+
await WriterManager.handleIncomingWriterEvent(this, data);
|
|
340
|
+
})
|
|
341
|
+
|
|
217
342
|
connection.on('error', (error) => { });
|
|
218
343
|
|
|
219
344
|
if (!this.isStreaming) {
|
|
@@ -236,30 +361,6 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
236
361
|
});
|
|
237
362
|
}
|
|
238
363
|
|
|
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
364
|
async interactiveMode() {
|
|
264
365
|
const rl = readline.createInterface({
|
|
265
366
|
input: process.stdin,
|
|
@@ -267,102 +368,43 @@ export class MainSettlementBus extends ReadyResource {
|
|
|
267
368
|
});
|
|
268
369
|
|
|
269
370
|
console.log('MSB started. Available commands:');
|
|
270
|
-
console.log('- /
|
|
271
|
-
console.log('- /
|
|
371
|
+
console.log('- /addMe: send request to admin to become a writer node in the TRAC Network');
|
|
372
|
+
console.log('- /removeMe: send request to admin to remove writer node from the TRAC Network');
|
|
373
|
+
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. ');
|
|
374
|
+
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
375
|
console.log('- /dag: check system properties such as writer key, DAG, etc.');
|
|
273
376
|
console.log('- /exit: Exit the program');
|
|
274
377
|
|
|
275
378
|
rl.on('line', async (input) => {
|
|
276
379
|
switch (input) {
|
|
277
380
|
case '/dag':
|
|
278
|
-
await this.
|
|
381
|
+
await verifyDag(this.base);
|
|
279
382
|
break;
|
|
280
383
|
case '/exit':
|
|
281
384
|
console.log('Exiting...');
|
|
282
385
|
rl.close();
|
|
283
386
|
await this.close();
|
|
284
387
|
process.exit(0);
|
|
388
|
+
case '/addMe':
|
|
389
|
+
await this.writerManager.addMe();
|
|
390
|
+
break;
|
|
391
|
+
case '/removeMe':
|
|
392
|
+
await this.writerManager.removeMe();
|
|
285
393
|
break;
|
|
286
394
|
default:
|
|
287
|
-
if (input.startsWith('/
|
|
288
|
-
|
|
395
|
+
if (input.startsWith('/addIndexer')) {
|
|
396
|
+
const splitted = input.split(' ');
|
|
397
|
+
this.writerManager.addIndexer(splitted[1], splitted[2]);
|
|
398
|
+
|
|
399
|
+
} else if (input.startsWith('/removeIndexer')) {
|
|
400
|
+
const splitted = input.split(' ');
|
|
401
|
+
this.writerManager.removeIndexer(splitted[1], splitted[2]);
|
|
289
402
|
}
|
|
290
403
|
}
|
|
291
404
|
rl.prompt();
|
|
292
405
|
});
|
|
293
|
-
|
|
294
406
|
rl.prompt();
|
|
295
407
|
}
|
|
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
408
|
}
|
|
367
409
|
|
|
368
410
|
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;
|