trac-peer 0.1.56 → 0.1.58
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 +3 -1
- package/src/api.js +244 -28
- package/src/feature.js +16 -12
- package/src/functions.js +22 -16
- package/src/index.js +132 -63
- package/src/protocol.js +26 -26
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trac-peer",
|
|
3
3
|
"main": "src/index.js",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.58",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"assert": "npm:bare-node-assert",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"brittle": "3.0.0",
|
|
38
38
|
"buffer": "npm:bare-node-buffer",
|
|
39
39
|
"child_process": "npm:bare-node-child-process",
|
|
40
|
+
"compact-encoding": "^2.16.1",
|
|
40
41
|
"console": "npm:bare-node-console",
|
|
41
42
|
"corestore": "7.4.1",
|
|
42
43
|
"crypto": "npm:bare-node-crypto",
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"path": "npm:bare-node-path",
|
|
61
62
|
"pear-interface": "1.0.0",
|
|
62
63
|
"process": "npm:bare-node-process",
|
|
64
|
+
"protomux": "^3.10.1",
|
|
63
65
|
"protomux-wakeup": "^2.2.1",
|
|
64
66
|
"readline": "npm:bare-node-readline",
|
|
65
67
|
"ready-resource": "^1.0.0",
|
package/src/api.js
CHANGED
|
@@ -2,6 +2,14 @@ import b4a from "b4a";
|
|
|
2
2
|
import {jsonStringify} from "./functions.js";
|
|
3
3
|
|
|
4
4
|
export class ProtocolApi{
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Exposes read and write functions.
|
|
8
|
+
* May be extended by contract protocol instances.
|
|
9
|
+
*
|
|
10
|
+
* @param peer
|
|
11
|
+
* @param options
|
|
12
|
+
*/
|
|
5
13
|
constructor(peer, options = {}) {
|
|
6
14
|
this.peer = peer;
|
|
7
15
|
this.api_tx_exposed = options.api_tx_exposed === true;
|
|
@@ -9,30 +17,57 @@ export class ProtocolApi{
|
|
|
9
17
|
this.options = options;
|
|
10
18
|
}
|
|
11
19
|
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @returns {null|string}
|
|
23
|
+
*/
|
|
12
24
|
getPeerValidatorAddress(){
|
|
13
25
|
return this.peer.validator;
|
|
14
26
|
}
|
|
15
27
|
|
|
28
|
+
/**
|
|
29
|
+
*
|
|
30
|
+
* @returns {null|string}
|
|
31
|
+
*/
|
|
16
32
|
getPeerBootstrap(){
|
|
17
33
|
return this.peer.bootstrap;
|
|
18
34
|
}
|
|
19
35
|
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @returns {null|string}
|
|
39
|
+
*/
|
|
20
40
|
getPeerMsbBootstrap(){
|
|
21
41
|
return this.peer.msb.bootstrap;
|
|
22
42
|
}
|
|
23
43
|
|
|
44
|
+
/**
|
|
45
|
+
*
|
|
46
|
+
* @returns {null|string}
|
|
47
|
+
*/
|
|
24
48
|
getPeerWriterKey(){
|
|
25
49
|
return this.peer.writerLocalKey;
|
|
26
50
|
}
|
|
27
51
|
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
28
56
|
generateNonce(){
|
|
29
57
|
return this.peer.protocol_instance.generateNonce();
|
|
30
58
|
}
|
|
31
59
|
|
|
32
|
-
|
|
60
|
+
/**
|
|
61
|
+
* @throws Error
|
|
62
|
+
* @param msg
|
|
63
|
+
* @param address
|
|
64
|
+
* @param reply_to
|
|
65
|
+
* @param attachments
|
|
66
|
+
* @returns {{dispatch: {type: string, msg: string, address, attachments: *[], deleted_by: null, reply_to: (number|null), pinned: boolean, pin_id: null}}}
|
|
67
|
+
*/
|
|
68
|
+
prepareMessage(msg, address, reply_to = null, attachments = []){
|
|
33
69
|
if(typeof msg !== 'string') throw new Error('Msg must be a string');
|
|
34
|
-
if(b4a.byteLength(jsonStringify(address))
|
|
35
|
-
if(b4a.byteLength(jsonStringify(reply_to)) > 256) throw new Error('Reply to too large.');
|
|
70
|
+
if(b4a.byteLength(jsonStringify(address)) !== 66) throw new Error('Address too large.');
|
|
36
71
|
if(reply_to !== null && isNaN(parseInt(reply_to))) throw new Error('Reply to not a number.');
|
|
37
72
|
if(false === Array.isArray(attachments)) throw new Error('attachments must be an array.');
|
|
38
73
|
if(attachments.length > 20) throw new Error('Too many attachments');
|
|
@@ -54,8 +89,36 @@ export class ProtocolApi{
|
|
|
54
89
|
return prepared;
|
|
55
90
|
}
|
|
56
91
|
|
|
92
|
+
/**
|
|
93
|
+
*
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*/
|
|
96
|
+
msgExposed(){
|
|
97
|
+
return true === this.api_msg_exposed;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* To post a message, an ed25519 signature has to be provided over the given message + nonce.
|
|
102
|
+
*
|
|
103
|
+
* Signing steps:
|
|
104
|
+
*
|
|
105
|
+
* let address = your_ed25519_wallet.getAccountAddress()
|
|
106
|
+
* let nonce = api.generateNonce()
|
|
107
|
+
* let prepared_message = api.prepareMessage("my message", address)
|
|
108
|
+
* let signature = your_ed25519_wallet.sign(JSON.stringify(prepared_message) + nonce)
|
|
109
|
+
* let result = api.post(prepared_message, signature, nonce)
|
|
110
|
+
*
|
|
111
|
+
* Note: new messages can be read from this api's msg functions.
|
|
112
|
+
*
|
|
113
|
+
* @throws Error
|
|
114
|
+
* @param prepared_message
|
|
115
|
+
* @param signature
|
|
116
|
+
* @param nonce
|
|
117
|
+
* @returns {Promise<void>}
|
|
118
|
+
*/
|
|
57
119
|
async post(prepared_message, signature, nonce){
|
|
58
|
-
if(
|
|
120
|
+
if(true !== this.api_msg_exposed) throw new Error('Posting messages not exposed in API.');
|
|
121
|
+
if(this.peer.base.writable === false) throw new Error('Peer is not writable.');
|
|
59
122
|
if(b4a.byteLength(jsonStringify(prepared_message)) > this.peer.protocol_instance.msgMaxBytes()) throw new Error('Prepared message too large.');
|
|
60
123
|
if(typeof prepared_message !== 'object') throw new Error('Prepared message must be an object generated with api.prepareMessage().');
|
|
61
124
|
if(prepared_message.dispatch === undefined || prepared_message.dispatch.type === undefined ||
|
|
@@ -66,6 +129,8 @@ export class ProtocolApi{
|
|
|
66
129
|
if(prepared_message.dispatch.type !== 'msg') throw new Error('Invalid type.');
|
|
67
130
|
if(typeof prepared_message.dispatch.msg !== 'string') throw new Error('Msg must be a string');
|
|
68
131
|
if(b4a.toString(b4a.from(prepared_message.dispatch.address, 'hex'), 'hex') !== prepared_message.dispatch.address) throw new Error('Invalid address.');
|
|
132
|
+
if(b4a.toString(b4a.from(signature, 'hex'), 'hex') !== signature) throw new Error('Invalid signature.');
|
|
133
|
+
if(b4a.toString(b4a.from(nonce, 'hex'), 'hex') !== nonce) throw new Error('Invalid nonce.');
|
|
69
134
|
if(false === Array.isArray(prepared_message.dispatch.attachments)) throw new Error('attachments must be an array.');
|
|
70
135
|
if(prepared_message.dispatch.attachments.length > 20) throw new Error('Too many attachments');
|
|
71
136
|
for(let i = 0; i < prepared_message.dispatch.attachments.length; i++){
|
|
@@ -80,69 +145,99 @@ export class ProtocolApi{
|
|
|
80
145
|
await this.peer.base.append({type: 'msg', value: prepared_message, hash : signature, nonce: nonce });
|
|
81
146
|
}
|
|
82
147
|
|
|
148
|
+
/**
|
|
149
|
+
* @throws Error
|
|
150
|
+
* @param address
|
|
151
|
+
* @param command_hash
|
|
152
|
+
* @param nonce
|
|
153
|
+
* @returns {Promise<*>}
|
|
154
|
+
*/
|
|
83
155
|
async generateTx(address, command_hash, nonce) {
|
|
156
|
+
if(this.getPeerValidatorAddress() === null) throw new Error('Peer not connected to a validator.');
|
|
84
157
|
return await this.peer.protocol_instance.generateTx(this.getPeerBootstrap(),
|
|
85
158
|
this.getPeerMsbBootstrap(), this.getPeerValidatorAddress(), this.getPeerWriterKey(),
|
|
86
159
|
address, command_hash, nonce);
|
|
87
160
|
}
|
|
88
161
|
|
|
162
|
+
/**
|
|
163
|
+
*
|
|
164
|
+
* @param command
|
|
165
|
+
* @returns {*}
|
|
166
|
+
*/
|
|
89
167
|
prepareTxCommand(command){
|
|
90
168
|
return this.peer.protocol_instance.mapTxCommand(command);
|
|
91
169
|
}
|
|
92
170
|
|
|
93
171
|
/**
|
|
94
|
-
*
|
|
172
|
+
*
|
|
173
|
+
* @returns {boolean}
|
|
174
|
+
*/
|
|
175
|
+
txExposed(){
|
|
176
|
+
return true === this.api_tx_exposed;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* To broadcast a TX, an ed25519 signature has to be provided over the given tx + nonce.
|
|
95
181
|
*
|
|
96
182
|
* Signing steps:
|
|
183
|
+
*
|
|
184
|
+
* let address = your_ed25519_wallet.getAccountAddress()
|
|
97
185
|
* let nonce = api.generateNonce()
|
|
98
186
|
* let tx_hash = api.generateTx(address, a_sha256_function(JSON.stringify(api.prepareTxCommand(command))), nonce)
|
|
99
|
-
* let signature =
|
|
100
|
-
*
|
|
187
|
+
* let signature = your_ed25519_wallet.sign(tx + nonce)
|
|
188
|
+
* // simulating
|
|
189
|
+
* let sim_result = api.tx(tx_hash, api.prepareTxCommand(command), address, signature, nonce, true)
|
|
190
|
+
* // broadcasting
|
|
191
|
+
* let result = api.tx(tx_hash, api.prepareTxCommand(command), address, signature, nonce)
|
|
101
192
|
*
|
|
193
|
+
* @throws Error
|
|
102
194
|
* @param tx
|
|
103
195
|
* @param prepared_command
|
|
104
196
|
* @param address
|
|
105
197
|
* @param signature
|
|
106
198
|
* @param nonce
|
|
107
199
|
* @param sim
|
|
108
|
-
* @returns {Promise<boolean>}
|
|
200
|
+
* @returns {Promise<boolean|object>}
|
|
109
201
|
*/
|
|
110
202
|
async tx(tx, prepared_command, address, signature, nonce, sim = false ){
|
|
111
|
-
if(
|
|
203
|
+
if(true !== this.api_tx_exposed) throw new Error('Transactions not exposed in API.');
|
|
204
|
+
if(this.peer.base.writable === false) throw new Error('Peer is not writable.');
|
|
205
|
+
if(this.getPeerValidatorAddress() === null) throw new Error('Peer not connected to a validator.');
|
|
112
206
|
if(typeof prepared_command !== 'object') throw new Error('prepared_command must be an object.');
|
|
113
|
-
if(typeof prepared_command.type !== 'string')
|
|
114
|
-
if(prepared_command.value === undefined)
|
|
207
|
+
if(typeof prepared_command.type !== 'string') throw new Error('prepared_command.type must exist and be a string.');
|
|
208
|
+
if(prepared_command.value === undefined) throw new Error('prepared_command.value is missing.');
|
|
115
209
|
if(b4a.byteLength(jsonStringify(prepared_command)) > this.peer.protocol_instance.txMaxBytes()) throw new Error('prepared_command too large.');
|
|
116
|
-
if(b4a.byteLength(jsonStringify(address))
|
|
117
|
-
if(b4a.byteLength(jsonStringify(signature))
|
|
118
|
-
if(b4a.byteLength(jsonStringify(nonce))
|
|
119
|
-
if(b4a.toString(b4a.from(signature, 'hex'), 'hex') !== signature) throw new Error('Invalid signature.');
|
|
210
|
+
if(b4a.byteLength(jsonStringify(address)) !== 66) throw new Error('Address length invalid.');
|
|
211
|
+
if(b4a.byteLength(jsonStringify(signature)) !== 130) throw new Error('Signature length invalid.');
|
|
212
|
+
if(b4a.byteLength(jsonStringify(nonce)) !== 66) throw new Error('Nonce length invalid.');
|
|
120
213
|
if(b4a.toString(b4a.from(address, 'hex'), 'hex') !== address) throw new Error('Invalid address.');
|
|
214
|
+
if(b4a.toString(b4a.from(signature, 'hex'), 'hex') !== signature) throw new Error('Invalid signature.');
|
|
215
|
+
if(b4a.toString(b4a.from(nonce, 'hex'), 'hex') !== nonce) throw new Error('Invalid nonce.');
|
|
121
216
|
const verified = this.peer.wallet.verify(signature, tx + nonce, address);
|
|
122
217
|
if(false === verified) throw new Error('Invalid signature.');
|
|
123
218
|
const content_hash = await this.peer.createHash('sha256', this.peer.protocol_instance.safeJsonStringify(prepared_command));
|
|
124
219
|
let _tx = await this.generateTx(address, content_hash, nonce);
|
|
125
220
|
if(tx !== _tx) throw new Error('Invalid TX.');
|
|
126
|
-
|
|
127
|
-
this.peer.protocol_instance.surrogate_tx = { tx : ''+tx, nonce : ''+nonce, signature : ''+signature, address : ''+address };
|
|
221
|
+
const surrogate = { tx : _tx, nonce : ''+nonce, signature : ''+signature, address : ''+address };
|
|
128
222
|
let res = false;
|
|
129
223
|
try{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
this.peer.protocol_instance.sim = true;
|
|
133
|
-
}
|
|
134
|
-
const subject = { command : prepared_command.value, validator : ''+this.getPeerValidatorAddress() };
|
|
135
|
-
res = await this.peer.protocol_instance.tx(subject);
|
|
224
|
+
const subject = { command : prepared_command.value, validator : this.getPeerValidatorAddress() };
|
|
225
|
+
res = await this.peer.protocol_instance.tx(subject, sim === true, surrogate);
|
|
136
226
|
} catch(e){ console.log(e) }
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
227
|
+
if(res !== false) {
|
|
228
|
+
const err = this.peer.protocol_instance.getError(res);
|
|
229
|
+
if (null !== err) {
|
|
230
|
+
console.log(err.message);
|
|
231
|
+
}
|
|
140
232
|
}
|
|
141
|
-
this.peer.protocol_instance.sim = false;
|
|
142
|
-
this.peer.protocol_instance.surrogate_tx = null;
|
|
143
233
|
return res;
|
|
144
234
|
}
|
|
145
235
|
|
|
236
|
+
/**
|
|
237
|
+
*
|
|
238
|
+
* @param signed
|
|
239
|
+
* @returns {Promise<null>}
|
|
240
|
+
*/
|
|
146
241
|
async getAdmin(signed = true){
|
|
147
242
|
let res = null;
|
|
148
243
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('admin');
|
|
@@ -151,6 +246,11 @@ export class ProtocolApi{
|
|
|
151
246
|
return null;
|
|
152
247
|
}
|
|
153
248
|
|
|
249
|
+
/**
|
|
250
|
+
*
|
|
251
|
+
* @param signed
|
|
252
|
+
* @returns {Promise<boolean|null>}
|
|
253
|
+
*/
|
|
154
254
|
async getWhitelistEnabled(signed = true){
|
|
155
255
|
let res = null;
|
|
156
256
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('wlst');
|
|
@@ -159,6 +259,12 @@ export class ProtocolApi{
|
|
|
159
259
|
return false;
|
|
160
260
|
}
|
|
161
261
|
|
|
262
|
+
/**
|
|
263
|
+
*
|
|
264
|
+
* @param address
|
|
265
|
+
* @param signed
|
|
266
|
+
* @returns {Promise<boolean|null>}
|
|
267
|
+
*/
|
|
162
268
|
async getWhitelistStatus(address, signed = true){
|
|
163
269
|
let res = null;
|
|
164
270
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('wl/'+address);
|
|
@@ -167,6 +273,12 @@ export class ProtocolApi{
|
|
|
167
273
|
return false;
|
|
168
274
|
}
|
|
169
275
|
|
|
276
|
+
/**
|
|
277
|
+
*
|
|
278
|
+
* @param address
|
|
279
|
+
* @param signed
|
|
280
|
+
* @returns {Promise<boolean|null>}
|
|
281
|
+
*/
|
|
170
282
|
async getModStatus(address, signed = true){
|
|
171
283
|
let res = null;
|
|
172
284
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('mod/'+address);
|
|
@@ -175,6 +287,12 @@ export class ProtocolApi{
|
|
|
175
287
|
return false;
|
|
176
288
|
}
|
|
177
289
|
|
|
290
|
+
/**
|
|
291
|
+
*
|
|
292
|
+
* @param address
|
|
293
|
+
* @param signed
|
|
294
|
+
* @returns {Promise<boolean|null>}
|
|
295
|
+
*/
|
|
178
296
|
async getMuteStatus(address, signed = true){
|
|
179
297
|
let res = null;
|
|
180
298
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('mtd/'+address);
|
|
@@ -183,6 +301,11 @@ export class ProtocolApi{
|
|
|
183
301
|
return false;
|
|
184
302
|
}
|
|
185
303
|
|
|
304
|
+
/**
|
|
305
|
+
*
|
|
306
|
+
* @param signed
|
|
307
|
+
* @returns {Promise<boolean>}
|
|
308
|
+
*/
|
|
186
309
|
async getAutoAddWritersStatus(signed = true){
|
|
187
310
|
let res = null;
|
|
188
311
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('auto_add_writers');
|
|
@@ -193,6 +316,11 @@ export class ProtocolApi{
|
|
|
193
316
|
return false;
|
|
194
317
|
}
|
|
195
318
|
|
|
319
|
+
/**
|
|
320
|
+
*
|
|
321
|
+
* @param signed
|
|
322
|
+
* @returns {Promise<boolean>}
|
|
323
|
+
*/
|
|
196
324
|
async getChatStatus(signed = true){
|
|
197
325
|
let res = null;
|
|
198
326
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('chat_status');
|
|
@@ -203,6 +331,12 @@ export class ProtocolApi{
|
|
|
203
331
|
return false;
|
|
204
332
|
}
|
|
205
333
|
|
|
334
|
+
/**
|
|
335
|
+
*
|
|
336
|
+
* @param nick
|
|
337
|
+
* @param signed
|
|
338
|
+
* @returns {Promise<null>}
|
|
339
|
+
*/
|
|
206
340
|
async getAddressFromNick(nick, signed = true){
|
|
207
341
|
let res = null;
|
|
208
342
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('kcin/'+nick);
|
|
@@ -210,6 +344,12 @@ export class ProtocolApi{
|
|
|
210
344
|
return res;
|
|
211
345
|
}
|
|
212
346
|
|
|
347
|
+
/**
|
|
348
|
+
*
|
|
349
|
+
* @param address
|
|
350
|
+
* @param signed
|
|
351
|
+
* @returns {Promise<null>}
|
|
352
|
+
*/
|
|
213
353
|
async getNick(address, signed = true){
|
|
214
354
|
let res = null;
|
|
215
355
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('nick/'+address);
|
|
@@ -217,6 +357,11 @@ export class ProtocolApi{
|
|
|
217
357
|
return res;
|
|
218
358
|
}
|
|
219
359
|
|
|
360
|
+
/**
|
|
361
|
+
*
|
|
362
|
+
* @param signed
|
|
363
|
+
* @returns {Promise<number>}
|
|
364
|
+
*/
|
|
220
365
|
async getPinnedMessageLength(signed = true){
|
|
221
366
|
let res = null;
|
|
222
367
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('pnl');
|
|
@@ -225,6 +370,12 @@ export class ProtocolApi{
|
|
|
225
370
|
return res;
|
|
226
371
|
}
|
|
227
372
|
|
|
373
|
+
/**
|
|
374
|
+
*
|
|
375
|
+
* @param index
|
|
376
|
+
* @param signed
|
|
377
|
+
* @returns {Promise<null>}
|
|
378
|
+
*/
|
|
228
379
|
async getPinnedMessage(index, signed = true){
|
|
229
380
|
let res = null;
|
|
230
381
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('pni/'+parseInt(index));
|
|
@@ -235,6 +386,11 @@ export class ProtocolApi{
|
|
|
235
386
|
return await this.getMessage(res.msg, signed);
|
|
236
387
|
}
|
|
237
388
|
|
|
389
|
+
/**
|
|
390
|
+
*
|
|
391
|
+
* @param signed
|
|
392
|
+
* @returns {Promise<number>}
|
|
393
|
+
*/
|
|
238
394
|
async getDeletedMessageLength(signed = true){
|
|
239
395
|
let res = null;
|
|
240
396
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('delml');
|
|
@@ -243,6 +399,12 @@ export class ProtocolApi{
|
|
|
243
399
|
return res;
|
|
244
400
|
}
|
|
245
401
|
|
|
402
|
+
/**
|
|
403
|
+
*
|
|
404
|
+
* @param index
|
|
405
|
+
* @param signed
|
|
406
|
+
* @returns {Promise<null>}
|
|
407
|
+
*/
|
|
246
408
|
async getDeletedMessage(index, signed = true){
|
|
247
409
|
let res = null;
|
|
248
410
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('delm/'+parseInt(index));
|
|
@@ -253,6 +415,11 @@ export class ProtocolApi{
|
|
|
253
415
|
return await this.getMessage(res, signed);
|
|
254
416
|
}
|
|
255
417
|
|
|
418
|
+
/**
|
|
419
|
+
*
|
|
420
|
+
* @param signed
|
|
421
|
+
* @returns {Promise<number>}
|
|
422
|
+
*/
|
|
256
423
|
async getMessageLength(signed = true){
|
|
257
424
|
let res = null;
|
|
258
425
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('msgl');
|
|
@@ -261,6 +428,12 @@ export class ProtocolApi{
|
|
|
261
428
|
return res;
|
|
262
429
|
}
|
|
263
430
|
|
|
431
|
+
/**
|
|
432
|
+
*
|
|
433
|
+
* @param index
|
|
434
|
+
* @param signed
|
|
435
|
+
* @returns {Promise<null>}
|
|
436
|
+
*/
|
|
264
437
|
async getMessage(index, signed = true){
|
|
265
438
|
let res = null;
|
|
266
439
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('msg/'+parseInt(index));
|
|
@@ -268,6 +441,12 @@ export class ProtocolApi{
|
|
|
268
441
|
return res;
|
|
269
442
|
}
|
|
270
443
|
|
|
444
|
+
/**
|
|
445
|
+
*
|
|
446
|
+
* @param address
|
|
447
|
+
* @param signed
|
|
448
|
+
* @returns {Promise<number>}
|
|
449
|
+
*/
|
|
271
450
|
async getUserMessageLength(address, signed = true){
|
|
272
451
|
let res = null;
|
|
273
452
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('umsgl/'+address);
|
|
@@ -276,6 +455,13 @@ export class ProtocolApi{
|
|
|
276
455
|
return res;
|
|
277
456
|
}
|
|
278
457
|
|
|
458
|
+
/**
|
|
459
|
+
*
|
|
460
|
+
* @param address
|
|
461
|
+
* @param index
|
|
462
|
+
* @param signed
|
|
463
|
+
* @returns {Promise<null>}
|
|
464
|
+
*/
|
|
279
465
|
async getUserMessage(address, index, signed = true){
|
|
280
466
|
let res = null;
|
|
281
467
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('umsg/'+address+'/'+parseInt(index));
|
|
@@ -286,6 +472,11 @@ export class ProtocolApi{
|
|
|
286
472
|
return await this.getMessage(res, signed);
|
|
287
473
|
}
|
|
288
474
|
|
|
475
|
+
/**
|
|
476
|
+
*
|
|
477
|
+
* @param signed
|
|
478
|
+
* @returns {Promise<number>}
|
|
479
|
+
*/
|
|
289
480
|
async getTxLength(signed = true){
|
|
290
481
|
let res = null;
|
|
291
482
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('txl');
|
|
@@ -294,6 +485,12 @@ export class ProtocolApi{
|
|
|
294
485
|
return res;
|
|
295
486
|
}
|
|
296
487
|
|
|
488
|
+
/**
|
|
489
|
+
*
|
|
490
|
+
* @param index
|
|
491
|
+
* @param signed
|
|
492
|
+
* @returns {Promise<null>}
|
|
493
|
+
*/
|
|
297
494
|
async getTx(index, signed = true){
|
|
298
495
|
let res = null;
|
|
299
496
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('txi/'+parseInt(index));
|
|
@@ -301,6 +498,12 @@ export class ProtocolApi{
|
|
|
301
498
|
return res;
|
|
302
499
|
}
|
|
303
500
|
|
|
501
|
+
/**
|
|
502
|
+
*
|
|
503
|
+
* @param tx
|
|
504
|
+
* @param signed
|
|
505
|
+
* @returns {Promise<null>}
|
|
506
|
+
*/
|
|
304
507
|
async getTxData(tx, signed = true){
|
|
305
508
|
let index = null;
|
|
306
509
|
if(true === signed) index = await this.peer.protocol_instance.getSigned('tx/'+tx);
|
|
@@ -311,6 +514,12 @@ export class ProtocolApi{
|
|
|
311
514
|
return null;
|
|
312
515
|
}
|
|
313
516
|
|
|
517
|
+
/**
|
|
518
|
+
*
|
|
519
|
+
* @param address
|
|
520
|
+
* @param signed
|
|
521
|
+
* @returns {Promise<number>}
|
|
522
|
+
*/
|
|
314
523
|
async getUserTxLength(address, signed = true){
|
|
315
524
|
let res = null;
|
|
316
525
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('utxl/'+address);
|
|
@@ -319,6 +528,13 @@ export class ProtocolApi{
|
|
|
319
528
|
return res;
|
|
320
529
|
}
|
|
321
530
|
|
|
531
|
+
/**
|
|
532
|
+
*
|
|
533
|
+
* @param address
|
|
534
|
+
* @param index
|
|
535
|
+
* @param signed
|
|
536
|
+
* @returns {Promise<null>}
|
|
537
|
+
*/
|
|
322
538
|
async getUserTx(address, index, signed = true){
|
|
323
539
|
let res = null;
|
|
324
540
|
if(true === signed) res = await this.peer.protocol_instance.getSigned('utxi/'+address+'/'+parseInt(index));
|
package/src/feature.js
CHANGED
|
@@ -20,18 +20,22 @@ class Feature {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
async append(key, value){
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
if(this.peer.base.writable){
|
|
24
|
+
const nonce = this.peer.protocol_instance.generateNonce();
|
|
25
|
+
const hash = this.peer.wallet.sign(JSON.stringify(value) + nonce);
|
|
26
|
+
await this.peer.base.append({ type: 'feature', key: this.key + '_' + key, value : {
|
|
27
|
+
dispatch : {
|
|
28
|
+
type : this.key + '_feature',
|
|
29
|
+
key : key,
|
|
30
|
+
hash : hash,
|
|
31
|
+
value : value,
|
|
32
|
+
nonce : nonce,
|
|
33
|
+
address : this.peer.wallet.publicKey
|
|
34
|
+
}
|
|
35
|
+
}});
|
|
36
|
+
} else {
|
|
37
|
+
console.log('Peer running features not writable.');
|
|
38
|
+
}
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
async start(options = {}) {
|
package/src/functions.js
CHANGED
|
@@ -334,30 +334,36 @@ export async function removeWriter(input, peer){
|
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
export async function joinValidator(input, peer){
|
|
338
|
+
const splitted = peer.protocol_instance.parseArgs(input)
|
|
339
|
+
const address = ''+splitted.address;
|
|
340
|
+
const validator = await peer.msb.base.view.get(address);
|
|
341
|
+
if(validator === null || false === validator.value.isWriter || true === validator.value.isIndexer){
|
|
342
|
+
throw new Error('Invalid validator address.');
|
|
343
|
+
}
|
|
344
|
+
await peer.tryConnection(address);
|
|
345
|
+
}
|
|
346
|
+
|
|
337
347
|
export async function tx(input, peer){
|
|
338
348
|
const splitted = peer.protocol_instance.parseArgs(input);
|
|
349
|
+
let res = false;
|
|
339
350
|
if(splitted.command === undefined){
|
|
340
|
-
|
|
341
|
-
} else if(splitted.sim === undefined &&
|
|
342
|
-
|
|
343
|
-
throw new Error('No validator available: Please wait for your peer to find an available one before transacting or pass the public key of a known one that is online.');
|
|
344
|
-
}
|
|
345
|
-
splitted['validator'] = peer.validator;
|
|
346
|
-
} else if(splitted.sim !== undefined && splitted.validator !== undefined){
|
|
347
|
-
throw new Error('Validator flag not allowed when simulating a transaction.');
|
|
351
|
+
res = new Error('Missing option. Please use the --command flag.');
|
|
352
|
+
} else if(splitted.sim === undefined && peer.validator === null){
|
|
353
|
+
res = new Error('No validator available: Please wait for your peer to find an available one or use joinValidator to connect to a specific one.');
|
|
348
354
|
}
|
|
349
|
-
let
|
|
355
|
+
let sim = false;
|
|
350
356
|
try{
|
|
351
357
|
if(splitted.sim !== undefined && parseInt(splitted.sim) === 1){
|
|
352
|
-
|
|
353
|
-
peer.protocol_instance.sim = true;
|
|
358
|
+
sim = true;
|
|
354
359
|
}
|
|
355
|
-
res = await peer.protocol_instance.tx(splitted);
|
|
360
|
+
res = await peer.protocol_instance.tx(splitted, sim);
|
|
356
361
|
} catch(e){ console.log(e) }
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
362
|
+
if(res !== false){
|
|
363
|
+
const err = peer.protocol_instance.getError(res);
|
|
364
|
+
if(null !== err){
|
|
365
|
+
console.log(err.message);
|
|
366
|
+
}
|
|
360
367
|
}
|
|
361
|
-
peer.protocol_instance.sim = false;
|
|
362
368
|
return res;
|
|
363
369
|
}
|
package/src/index.js
CHANGED
|
@@ -11,11 +11,13 @@ import Corestore from 'corestore';
|
|
|
11
11
|
import w from 'protomux-wakeup';
|
|
12
12
|
import BlindPairing from 'blind-pairing'
|
|
13
13
|
const wakeup = new w();
|
|
14
|
+
import Protomux from 'protomux'
|
|
15
|
+
import c from 'compact-encoding'
|
|
14
16
|
import {
|
|
15
17
|
addWriter, addAdmin, setAutoAddWriters, setChatStatus, setMod, deleteMessage,
|
|
16
18
|
enableWhitelist, postMessage, jsonStringify, visibleLength, setNick,
|
|
17
19
|
muteStatus, setWhitelistStatus, updateAdmin, tx, safeClone, jsonParse,
|
|
18
|
-
pinMessage
|
|
20
|
+
pinMessage, joinValidator, removeWriter
|
|
19
21
|
} from "./functions.js";
|
|
20
22
|
import Check from "./check.js";
|
|
21
23
|
export {default as Protocol} from "./protocol.js";
|
|
@@ -44,6 +46,7 @@ export class Peer extends ReadyResource {
|
|
|
44
46
|
this.contract = options.contract || null;
|
|
45
47
|
this.wallet = options.wallet || null;
|
|
46
48
|
this.features = options.features || [];
|
|
49
|
+
this.custom_validators = options.custom_validators || [];
|
|
47
50
|
this.protocol_instance = null;
|
|
48
51
|
this.contract_instance = null;
|
|
49
52
|
this.channel = b4a.alloc(32).fill(options.channel) || null;
|
|
@@ -124,7 +127,8 @@ export class Peer extends ReadyResource {
|
|
|
124
127
|
let _err = null;
|
|
125
128
|
if(null !== err) {
|
|
126
129
|
if(err.constructor.name === 'UnknownContractOperationType') continue;
|
|
127
|
-
|
|
130
|
+
const _err_msg = parseInt(err.message);
|
|
131
|
+
_err = isNaN(_err_msg) ? ''+err.message : _err_msg;
|
|
128
132
|
}
|
|
129
133
|
let len = await batch.get('txl');
|
|
130
134
|
if(null === len) {
|
|
@@ -132,12 +136,13 @@ export class Peer extends ReadyResource {
|
|
|
132
136
|
} else {
|
|
133
137
|
len = len.value;
|
|
134
138
|
}
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
const dta = {};
|
|
140
|
+
dta['val'] = safeClone(op.value.dispatch);
|
|
141
|
+
dta['err'] = _err;
|
|
142
|
+
dta['tx'] = post_tx.value.tx;
|
|
143
|
+
dta['ipk'] = post_tx.value.ipk;
|
|
144
|
+
dta['wp'] = post_tx.value.wp;
|
|
145
|
+
await batch.put('txi/'+len, dta);
|
|
141
146
|
await batch.put('txl', len + 1);
|
|
142
147
|
await batch.put('tx/'+post_tx.value.tx, len);
|
|
143
148
|
let ulen = await batch.get('utxl/'+post_tx.value.ipk);
|
|
@@ -229,7 +234,7 @@ export class Peer extends ReadyResource {
|
|
|
229
234
|
const verified = _this.wallet.verify(op.hash, str_msg + op.nonce, admin.value);
|
|
230
235
|
if(true === verified){
|
|
231
236
|
const writerKey = b4a.from(op.key, 'hex');
|
|
232
|
-
base.addWriter(writerKey, { isIndexer : true });
|
|
237
|
+
await base.addWriter(writerKey, { isIndexer : true });
|
|
233
238
|
await batch.put('sh/'+op.hash, '');
|
|
234
239
|
console.log(`Indexer added: ${op.key}`);
|
|
235
240
|
}
|
|
@@ -245,9 +250,9 @@ export class Peer extends ReadyResource {
|
|
|
245
250
|
const verified = _this.wallet.verify(op.hash, str_msg + op.nonce, admin.value);
|
|
246
251
|
if(true === verified){
|
|
247
252
|
const writerKey = b4a.from(op.key, 'hex');
|
|
248
|
-
base.addWriter(writerKey, { isIndexer : false });
|
|
253
|
+
await base.addWriter(writerKey, { isIndexer : false });
|
|
249
254
|
await batch.put('sh/'+op.hash, '');
|
|
250
|
-
console.log(`Writer added: ${
|
|
255
|
+
console.log(`Writer added: ${writerKey}`);
|
|
251
256
|
}
|
|
252
257
|
}
|
|
253
258
|
} else if (op.type === 'removeWriter') {
|
|
@@ -261,7 +266,7 @@ export class Peer extends ReadyResource {
|
|
|
261
266
|
const verified = _this.wallet.verify(op.hash, str_msg + op.nonce, admin.value);
|
|
262
267
|
if(true === verified){
|
|
263
268
|
const writerKey = b4a.from(op.key, 'hex');
|
|
264
|
-
base.removeWriter(writerKey);
|
|
269
|
+
await base.removeWriter(writerKey);
|
|
265
270
|
await batch.put('sh/'+op.hash, '');
|
|
266
271
|
console.log(`Writer removed: ${op.key}`);
|
|
267
272
|
}
|
|
@@ -302,7 +307,7 @@ export class Peer extends ReadyResource {
|
|
|
302
307
|
const banned = await batch.get('bnd/'+op.key);
|
|
303
308
|
if(null === banned && null !== auto_add_writers && auto_add_writers.value === 'on'){
|
|
304
309
|
const writerKey = b4a.from(op.key, 'hex');
|
|
305
|
-
base.addWriter(writerKey, { isIndexer : false });
|
|
310
|
+
await base.addWriter(writerKey, { isIndexer : false });
|
|
306
311
|
}
|
|
307
312
|
console.log(`Writer auto added: ${op.key}`);
|
|
308
313
|
} else if (op.type === 'addAdmin') {
|
|
@@ -510,6 +515,36 @@ export class Peer extends ReadyResource {
|
|
|
510
515
|
try{ await this.validator_stream.send(b4a.from(jsonStringify(_msg))); } catch(e){ }
|
|
511
516
|
}
|
|
512
517
|
|
|
518
|
+
async tryConnection(address){
|
|
519
|
+
if(null === this.swarm) return null;
|
|
520
|
+
|
|
521
|
+
// trying to join a peer from the global swarm
|
|
522
|
+
if(false === this.swarm.peers.has(address)){
|
|
523
|
+
this.swarm.joinPeer(b4a.from(address, 'hex'));
|
|
524
|
+
let cnt = 0;
|
|
525
|
+
while(false === this.swarm.peers.has(address)){
|
|
526
|
+
if(cnt >= 15) break;
|
|
527
|
+
await sleep(1_000);
|
|
528
|
+
cnt += 1;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if(this.swarm.peers.has(address)){
|
|
533
|
+
let stream;
|
|
534
|
+
const peerInfo = this.swarm.peers.get(address)
|
|
535
|
+
stream = this.swarm._allConnections.get(peerInfo.publicKey)
|
|
536
|
+
if(stream !== undefined && stream.messenger !== undefined){
|
|
537
|
+
stream.messenger.send('get_validator');
|
|
538
|
+
let cnt = 0;
|
|
539
|
+
while(this.validator_stream === null){
|
|
540
|
+
if(cnt >= 1500) break;
|
|
541
|
+
await this.sleep(10);
|
|
542
|
+
cnt += 1;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
513
548
|
async isValidatorAvailable(address){
|
|
514
549
|
if(null === this.msb.getSwarm()) return null;
|
|
515
550
|
let writer_key = null;
|
|
@@ -527,7 +562,9 @@ export class Peer extends ReadyResource {
|
|
|
527
562
|
existing_stream.on('message', (msg) => {
|
|
528
563
|
try{
|
|
529
564
|
const response = jsonParse(b4a.toString(msg, 'utf-8'));
|
|
530
|
-
if(response.op === 'writer_key' && response.key !== undefined
|
|
565
|
+
if(response.op === 'writer_key' && response.key !== undefined &&
|
|
566
|
+
response.channel !== undefined &&
|
|
567
|
+
response.channel === b4a.toString(this.msb.getChannel(), 'utf8')){
|
|
531
568
|
writer_key = response.key;
|
|
532
569
|
}
|
|
533
570
|
}catch(e){}
|
|
@@ -551,7 +588,9 @@ export class Peer extends ReadyResource {
|
|
|
551
588
|
stream.on('message', (msg) => {
|
|
552
589
|
try{
|
|
553
590
|
const response = jsonParse(b4a.toString(msg, 'utf-8'));
|
|
554
|
-
if(response.op === 'writer_key' && response.key !== undefined
|
|
591
|
+
if(response.op === 'writer_key' && response.key !== undefined &&
|
|
592
|
+
response.channel !== undefined &&
|
|
593
|
+
response.channel === b4a.toString(this.msb.getChannel(), 'utf8')){
|
|
555
594
|
writer_key = response.key;
|
|
556
595
|
}
|
|
557
596
|
}catch(e){}
|
|
@@ -621,50 +660,23 @@ export class Peer extends ReadyResource {
|
|
|
621
660
|
} else {
|
|
622
661
|
length = length.value;
|
|
623
662
|
}
|
|
663
|
+
if(this.custom_validators.length !== 0){
|
|
664
|
+
length = this.custom_validators.length;
|
|
665
|
+
}
|
|
624
666
|
async function findSome(){
|
|
625
667
|
if(_this.validator_stream !== null) return;
|
|
626
668
|
const rnd_index = Math.floor(Math.random() * length);
|
|
627
669
|
let validator = await _this.msb.base.view.get('wri/' + rnd_index);
|
|
670
|
+
if(_this.custom_validators.length !== 0){
|
|
671
|
+
validator = { value : _this.custom_validators[rnd_index] };
|
|
672
|
+
console.log('Trying custom validator', validator.value);
|
|
673
|
+
}
|
|
628
674
|
if(_this.validator_stream !== null) return;
|
|
629
675
|
if (null !== validator) {
|
|
630
676
|
validator = await _this.msb.base.view.get(validator.value);
|
|
631
677
|
if(_this.validator_stream !== null) return;
|
|
632
678
|
if(null !== validator && false !== validator.value.isWriter && false === validator.value.isIndexer) {
|
|
633
|
-
|
|
634
|
-
if(_this.validator_stream !== null) return;
|
|
635
|
-
if (null !== result) {
|
|
636
|
-
await _this.sleep(100);
|
|
637
|
-
if(_this.validator_stream !== null) return;
|
|
638
|
-
let existing_stream = undefined;
|
|
639
|
-
if(_this.msb.getSwarm().peers.has(validator.value.pub)){
|
|
640
|
-
const peerInfo = _this.msb.getSwarm().peers.get(validator.value.pub)
|
|
641
|
-
existing_stream = _this.msb.getSwarm()._allConnections.get(peerInfo.publicKey)
|
|
642
|
-
}
|
|
643
|
-
if(existing_stream !== undefined){
|
|
644
|
-
_this.validator_stream = existing_stream;
|
|
645
|
-
_this.validator = validator.value.pub;
|
|
646
|
-
_this.validator_stream.on('close', () => {
|
|
647
|
-
_this.validator_stream = null;
|
|
648
|
-
_this.validator = null;
|
|
649
|
-
console.log('Validator stream closed', validator.value.pub);
|
|
650
|
-
});
|
|
651
|
-
console.log('Validator stream established', validator.value.pub);
|
|
652
|
-
} else {
|
|
653
|
-
_this.validator_stream = _this.msb.getSwarm().dht.connect(b4a.from(validator.value.pub, 'hex'));
|
|
654
|
-
_this.validator = validator.value.pub;
|
|
655
|
-
_this.validator_stream.on('open', () => {
|
|
656
|
-
console.log('Validator stream established', validator.value.pub);
|
|
657
|
-
});
|
|
658
|
-
_this.validator_stream.on('close', () => {
|
|
659
|
-
_this.validator_stream = null;
|
|
660
|
-
_this.validator = null;
|
|
661
|
-
});
|
|
662
|
-
_this.validator_stream.on('error', (error) => {
|
|
663
|
-
_this.validator_stream = null;
|
|
664
|
-
_this.validator = null;
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
}
|
|
679
|
+
await _this.tryConnection(validator.value.pub);
|
|
668
680
|
}
|
|
669
681
|
}
|
|
670
682
|
}
|
|
@@ -694,10 +706,10 @@ export class Peer extends ReadyResource {
|
|
|
694
706
|
const msb_tx = await view_session.get(tx);
|
|
695
707
|
await view_session.close();
|
|
696
708
|
if(null !== msb_tx){
|
|
697
|
-
msb_tx['dispatch'] = this.protocol_instance.prepared_transactions_content[tx].dispatch;
|
|
698
709
|
msb_tx['msbsl'] = msbsl;
|
|
710
|
+
msb_tx['dispatch'] = this.protocol_instance.prepared_transactions_content[tx].dispatch;
|
|
699
711
|
msb_tx['ipk'] = this.protocol_instance.prepared_transactions_content[tx].ipk;
|
|
700
|
-
msb_tx['wp'] = this.validator;
|
|
712
|
+
msb_tx['wp'] = this.protocol_instance.prepared_transactions_content[tx].validator;
|
|
701
713
|
delete this.tx_pool[tx];
|
|
702
714
|
delete this.protocol_instance.prepared_transactions_content[tx];
|
|
703
715
|
await this.base.append({ type: 'tx', key: tx, value: msb_tx });
|
|
@@ -754,16 +766,51 @@ export class Peer extends ReadyResource {
|
|
|
754
766
|
poll: 5000
|
|
755
767
|
});
|
|
756
768
|
console.log('');
|
|
757
|
-
console.log('
|
|
758
|
-
console.log('#
|
|
769
|
+
console.log('######################################################################################');
|
|
770
|
+
console.log('# Peer Address: ', this.wallet.publicKey, '#');
|
|
759
771
|
this.writerLocalKey = b4a.toString(this.base.local.key, 'hex');
|
|
760
|
-
console.log('#
|
|
761
|
-
console.log('
|
|
772
|
+
console.log('# Peer Writer: ', this.writerLocalKey, '#');
|
|
773
|
+
console.log('######################################################################################');
|
|
774
|
+
console.log('');
|
|
762
775
|
console.log(`isIndexer: ${this.base.isIndexer}`);
|
|
763
776
|
console.log(`isWriter: ${this.base.writable}`);
|
|
764
777
|
console.log('');
|
|
765
778
|
|
|
766
779
|
this.swarm.on('connection', async (connection, peerInfo) => {
|
|
780
|
+
|
|
781
|
+
const mux = Protomux.from(connection)
|
|
782
|
+
connection.userData = mux
|
|
783
|
+
|
|
784
|
+
const message_channel = mux.createChannel({
|
|
785
|
+
protocol: b4a.toString(channel, 'utf8'),
|
|
786
|
+
onopen() { },
|
|
787
|
+
onclose() { }
|
|
788
|
+
})
|
|
789
|
+
|
|
790
|
+
message_channel.open()
|
|
791
|
+
|
|
792
|
+
const message = message_channel.addMessage({
|
|
793
|
+
encoding: c.json,
|
|
794
|
+
async onmessage(msg) {
|
|
795
|
+
try{
|
|
796
|
+
if(msg.response !== undefined && msg.response.op !== undefined && msg.response.op === 'validator'){
|
|
797
|
+
const res = await _this.msb.get(msg.response.address);
|
|
798
|
+
if(res === null) return;
|
|
799
|
+
const verified = _this.wallet.verify(msg.sig, JSON.stringify(msg.response) + msg.nonce, msg.response.address)
|
|
800
|
+
if(verified && msg.response.channel === b4a.toString(channel, 'utf8')){
|
|
801
|
+
console.log('Validator stream established', msg.response.address)
|
|
802
|
+
_this.validator_stream = connection;
|
|
803
|
+
_this.validator = msg.response.address;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
} catch (e) {
|
|
807
|
+
console.log(e);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
})
|
|
811
|
+
|
|
812
|
+
connection.messenger = message;
|
|
813
|
+
|
|
767
814
|
const remotePublicKey = b4a.toString(connection.remotePublicKey, 'hex');
|
|
768
815
|
|
|
769
816
|
this.connectedPeers.add(remotePublicKey);
|
|
@@ -772,6 +819,11 @@ export class Peer extends ReadyResource {
|
|
|
772
819
|
this.connectedNodes++;
|
|
773
820
|
|
|
774
821
|
connection.on('close', () => {
|
|
822
|
+
if(this.validator_stream === connection){
|
|
823
|
+
this.validator_stream = null;
|
|
824
|
+
this.validator = null;
|
|
825
|
+
}
|
|
826
|
+
message_channel.close()
|
|
775
827
|
this.connectedNodes--;
|
|
776
828
|
this.connectedPeers.delete(remotePublicKey);
|
|
777
829
|
});
|
|
@@ -855,6 +907,10 @@ export class Peer extends ReadyResource {
|
|
|
855
907
|
const lengthdagSystem = this.base.system.core.length;
|
|
856
908
|
console.log('wallet.address:', this.wallet !== null ? this.wallet.publicKey : 'unset');
|
|
857
909
|
console.log('hypermall.writerKey:', this.writerLocalKey);
|
|
910
|
+
const admin = await this.base.view.get('admin')
|
|
911
|
+
console.log(`admin: ${admin !== null ? admin.value : 'unset'}`);
|
|
912
|
+
console.log(`isIndexer: ${this.base.isIndexer}`);
|
|
913
|
+
console.log(`isWriter: ${this.base.writable}`);
|
|
858
914
|
console.log('swarm.connections.size:', this.swarm.connections.size);
|
|
859
915
|
console.log('base.view.core.signedLength:', this.base.view.core.signedLength);
|
|
860
916
|
console.log("base.signedLength", this.base.signedLength);
|
|
@@ -871,16 +927,12 @@ export class Peer extends ReadyResource {
|
|
|
871
927
|
}
|
|
872
928
|
}
|
|
873
929
|
|
|
874
|
-
|
|
875
|
-
if(this.readline_instance === null || (global.Pear !== undefined && global.Pear.config.options.type === 'desktop')) return;
|
|
876
|
-
|
|
877
|
-
const rl = this.readline_instance;
|
|
878
|
-
|
|
930
|
+
printHelp(){
|
|
879
931
|
console.log('Node started. Available commands:');
|
|
880
932
|
console.log(' ');
|
|
881
933
|
console.log('- Setup Commands:');
|
|
882
934
|
console.log('- /add_admin | Works only once and only on bootstrap node! Enter a wallet address to assign admin rights: \'/add_admin --address "<address>"\'.');
|
|
883
|
-
console.log('- /update_admin | Existing admins may transfer admin ownership. Enter "null" as address to waive admin rights for this peer entirely: \'/
|
|
935
|
+
console.log('- /update_admin | Existing admins may transfer admin ownership. Enter "null" as address to waive admin rights for this peer entirely: \'/update_admin --address "<address>"\'.');
|
|
884
936
|
console.log('- /add_indexer | Only admin. Enter a peer writer key to get included as indexer for this network: \'/add_indexer --key "<key>"\'.');
|
|
885
937
|
console.log('- /add_writer | Only admin. Enter a peer writer key to get included as writer for this network: \'/add_writer --key "<key>"\'.');
|
|
886
938
|
console.log('- /remove_writer | Only admin. Enter a peer writer key to get removed as writer or indexer for this network: \'/remove_writer --key "<key>"\'.');
|
|
@@ -898,18 +950,31 @@ export class Peer extends ReadyResource {
|
|
|
898
950
|
console.log('- /set_whitelist_status | Only admin. Add/remove users to/from the chat whitelist: \'/set_whitelist_status --user "<address>" --status 1\'.');
|
|
899
951
|
console.log(' ');
|
|
900
952
|
console.log('- System Commands:');
|
|
901
|
-
console.log('- /tx | Perform a contract transaction. The command flag contains contract commands
|
|
953
|
+
console.log('- /tx | Perform a contract transaction. The command flag contains contract commands (format is protocol dependent): \'/tx --command "<string>"\'. To simulate a tx, additionally use \'--sim 1\'.');
|
|
954
|
+
console.log('- /join_validator | Try to connect to a specific validator with its MSB address: \'/join_validator --address "<address>"\'.');
|
|
902
955
|
console.log('- /stats | check system properties such as writer key, DAG, etc.');
|
|
903
956
|
console.log('- /get_keys | prints your public and private keys. Be careful and never share your private key!');
|
|
904
957
|
console.log('- /exit | Exit the program');
|
|
958
|
+
console.log('- /help | This help text');
|
|
905
959
|
|
|
906
960
|
this.protocol_instance.printOptions();
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
async interactiveMode() {
|
|
964
|
+
if(this.readline_instance === null || (global.Pear !== undefined && global.Pear.config.options.type === 'desktop')) return;
|
|
965
|
+
|
|
966
|
+
const rl = this.readline_instance;
|
|
967
|
+
|
|
968
|
+
this.printHelp();
|
|
907
969
|
|
|
908
970
|
rl.on('line', async (input) => {
|
|
909
971
|
switch (input) {
|
|
910
972
|
case '/stats':
|
|
911
973
|
await this.verifyDag();
|
|
912
974
|
break;
|
|
975
|
+
case '/help':
|
|
976
|
+
await this.printHelp();
|
|
977
|
+
break;
|
|
913
978
|
case '/exit':
|
|
914
979
|
console.log('Exiting...');
|
|
915
980
|
rl.close();
|
|
@@ -925,6 +990,8 @@ export class Peer extends ReadyResource {
|
|
|
925
990
|
await tx(input, this);
|
|
926
991
|
} else if (input.startsWith('/add_indexer') || input.startsWith('/add_writer')) {
|
|
927
992
|
await addWriter(input, this);
|
|
993
|
+
} else if (input.startsWith('/remove_writer')) {
|
|
994
|
+
await removeWriter(input, this);
|
|
928
995
|
} else if (input.startsWith('/add_admin')) {
|
|
929
996
|
await addAdmin(input, this);
|
|
930
997
|
} else if (input.startsWith('/update_admin')) {
|
|
@@ -949,6 +1016,8 @@ export class Peer extends ReadyResource {
|
|
|
949
1016
|
await enableWhitelist(input, this);
|
|
950
1017
|
} else if (input.startsWith('/set_whitelist_status')) {
|
|
951
1018
|
await setWhitelistStatus(input, this);
|
|
1019
|
+
} else if (input.startsWith('/join_validator')) {
|
|
1020
|
+
await joinValidator(input, this);
|
|
952
1021
|
} else {
|
|
953
1022
|
await this.protocol_instance.customCommand(input);
|
|
954
1023
|
}
|
package/src/protocol.js
CHANGED
|
@@ -17,8 +17,6 @@ class Protocol{
|
|
|
17
17
|
this.safeClone = safeClone;
|
|
18
18
|
this.prepared_transactions_content = {};
|
|
19
19
|
this.features = {};
|
|
20
|
-
this.sim = false;
|
|
21
|
-
this.surrogate_tx = null;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
featMaxBytes(){
|
|
@@ -102,31 +100,27 @@ class Protocol{
|
|
|
102
100
|
return await this.peer.createHash('sha256', await this.peer.createHash('sha256', tx));
|
|
103
101
|
}
|
|
104
102
|
|
|
105
|
-
async simulateTransaction(obj){
|
|
103
|
+
async simulateTransaction(validator_pub_key, obj, surrogate = null){
|
|
106
104
|
const storage = new SimStorage(this.peer);
|
|
107
|
-
const null_hex = '0000000000000000000000000000000000000000000000000000000000000000';
|
|
108
105
|
let nonce = this.generateNonce();
|
|
109
106
|
const content_hash = await this.peer.createHash('sha256', this.safeJsonStringify(obj));
|
|
110
|
-
let tx = await this.generateTx(this.peer.bootstrap, this.peer.msb.bootstrap,
|
|
111
|
-
this.peer.writerLocalKey,
|
|
107
|
+
let tx = await this.generateTx(this.peer.bootstrap, this.peer.msb.bootstrap, validator_pub_key,
|
|
108
|
+
this.peer.writerLocalKey, surrogate !== null ? surrogate.address : this.peer.wallet.publicKey, content_hash, nonce);
|
|
112
109
|
const op = {
|
|
113
110
|
type : 'tx',
|
|
114
111
|
key : tx,
|
|
115
112
|
value : {
|
|
116
113
|
dispatch : obj,
|
|
117
114
|
value : {
|
|
118
|
-
ipk :
|
|
119
|
-
wp :
|
|
115
|
+
ipk : surrogate !== null ? surrogate.address : this.peer.wallet.publicKey,
|
|
116
|
+
wp : validator_pub_key
|
|
120
117
|
}
|
|
121
118
|
}
|
|
122
119
|
}
|
|
123
120
|
return await this.peer.contract_instance.execute(op, storage);
|
|
124
121
|
}
|
|
125
122
|
|
|
126
|
-
async broadcastTransaction(validator_pub_key, obj){
|
|
127
|
-
if(true === this.sim) {
|
|
128
|
-
return await this.simulateTransaction(obj);
|
|
129
|
-
}
|
|
123
|
+
async broadcastTransaction(validator_pub_key, obj, sim = false, surrogate = null){
|
|
130
124
|
if(this.peer.validator_stream !== null &&
|
|
131
125
|
this.peer.wallet.publicKey !== null &&
|
|
132
126
|
this.peer.wallet.secretKey !== null &&
|
|
@@ -134,15 +128,18 @@ class Protocol{
|
|
|
134
128
|
obj.type !== undefined &&
|
|
135
129
|
obj.value !== undefined)
|
|
136
130
|
{
|
|
131
|
+
if(true === sim) {
|
|
132
|
+
return await this.simulateTransaction(validator_pub_key, obj, surrogate);
|
|
133
|
+
}
|
|
137
134
|
|
|
138
135
|
let tx, signature, nonce, publicKey;
|
|
139
136
|
const content_hash = await this.peer.createHash('sha256', this.safeJsonStringify(obj));
|
|
140
137
|
|
|
141
|
-
if(
|
|
142
|
-
nonce =
|
|
143
|
-
tx =
|
|
144
|
-
signature =
|
|
145
|
-
publicKey =
|
|
138
|
+
if(surrogate !== null) {
|
|
139
|
+
nonce = surrogate.nonce;
|
|
140
|
+
tx = surrogate.tx;
|
|
141
|
+
signature = surrogate.signature;
|
|
142
|
+
publicKey = surrogate.address;
|
|
146
143
|
} else {
|
|
147
144
|
nonce = this.generateNonce();
|
|
148
145
|
tx = await this.generateTx(this.peer.bootstrap, this.peer.msb.bootstrap, validator_pub_key,
|
|
@@ -151,7 +148,7 @@ class Protocol{
|
|
|
151
148
|
publicKey = this.peer.wallet.publicKey;
|
|
152
149
|
}
|
|
153
150
|
|
|
154
|
-
|
|
151
|
+
const _tx = {
|
|
155
152
|
op: 'pre-tx',
|
|
156
153
|
tx: tx,
|
|
157
154
|
is: signature,
|
|
@@ -162,12 +159,14 @@ class Protocol{
|
|
|
162
159
|
in : nonce,
|
|
163
160
|
bs : this.peer.bootstrap,
|
|
164
161
|
mbs : this.peer.msb.bootstrap
|
|
165
|
-
}
|
|
166
|
-
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
this.peer.emit('tx', _tx);
|
|
165
|
+
this.prepared_transactions_content[tx] = { dispatch : obj, ipk : publicKey, validator : validator_pub_key };
|
|
166
|
+
return _tx;
|
|
167
167
|
} else {
|
|
168
168
|
throw Error('broadcastTransaction(writer, obj): Cannot prepare transaction. Please make sure inputs and local writer are set.');
|
|
169
169
|
}
|
|
170
|
-
return true;
|
|
171
170
|
}
|
|
172
171
|
|
|
173
172
|
async tokenizeInput(input){
|
|
@@ -190,15 +189,16 @@ class Protocol{
|
|
|
190
189
|
return null;
|
|
191
190
|
}
|
|
192
191
|
|
|
193
|
-
async tx(subject){
|
|
192
|
+
async tx(subject, sim = false, surrogate = null){
|
|
193
|
+
if(this.peer.validator_stream === null) throw new Error('HyperMallProtocol::tx(): No validator available.');
|
|
194
194
|
const obj = this.mapTxCommand(subject.command);
|
|
195
|
-
if(null !== obj) {
|
|
196
|
-
return await this.broadcastTransaction(
|
|
195
|
+
if(null !== obj && typeof obj.type === 'string' && obj.value !== undefined) {
|
|
196
|
+
return await this.broadcastTransaction(this.peer.validator,{
|
|
197
197
|
type : obj.type,
|
|
198
198
|
value : obj.value
|
|
199
|
-
});
|
|
199
|
+
}, sim, surrogate);
|
|
200
200
|
}
|
|
201
|
-
|
|
201
|
+
throw new Error('HyperMallProtocol::tx(): command not found.');
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
async customCommand(input){ }
|