trac-peer 0.1.43 → 0.1.44
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 +1 -1
- package/src/api.js +142 -2
- package/src/functions.js +1 -0
- package/src/index.js +5 -11
- package/src/protocol.js +44 -19
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -1,6 +1,146 @@
|
|
|
1
|
+
import b4a from "b4a";
|
|
2
|
+
import {jsonStringify} from "./functions.js";
|
|
3
|
+
|
|
1
4
|
export class ProtocolApi{
|
|
2
|
-
constructor(options = {}) {
|
|
3
|
-
this.peer =
|
|
5
|
+
constructor(peer, options = {}) {
|
|
6
|
+
this.peer = peer;
|
|
7
|
+
this.api_tx_exposed = options.api_tx_exposed === true;
|
|
8
|
+
this.api_msg_exposed = options.api_msg_exposed === true;
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getPeerValidatorAddress(){
|
|
13
|
+
return this.peer.validator;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getPeerBootstrap(){
|
|
17
|
+
return this.peer.bootstrap;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getPeerMsbBootstrap(){
|
|
21
|
+
return this.peer.msb.bootstrap;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getPeerWriterKey(){
|
|
25
|
+
return this.peer.writerLocalKey;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
generateNonce(){
|
|
29
|
+
return this.peer.protocol_instance.generateNonce();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async prepareMessage(msg, address, reply_to = null, attachments = []){
|
|
33
|
+
if(typeof msg !== 'string') throw new Error('Msg must be a string');
|
|
34
|
+
if(b4a.byteLength(jsonStringify(address)) > 64) throw new Error('Address too large.');
|
|
35
|
+
if(b4a.byteLength(jsonStringify(reply_to)) > 256) throw new Error('Reply to too large.');
|
|
36
|
+
if(reply_to !== null && isNaN(parseInt(reply_to))) throw new Error('Reply to not a number.');
|
|
37
|
+
if(false === Array.isArray(attachments)) throw new Error('attachments must be an array.');
|
|
38
|
+
if(attachments.length > 20) throw new Error('Too many attachments');
|
|
39
|
+
for(let i = 0; i < attachments.length; i++){
|
|
40
|
+
if(typeof attachments[i] !== 'string') throw new Error('Attachment at index ' + i + ' is not a string.');
|
|
41
|
+
}
|
|
42
|
+
const prepared = {
|
|
43
|
+
dispatch : {
|
|
44
|
+
type : 'msg',
|
|
45
|
+
msg: msg,
|
|
46
|
+
address : address,
|
|
47
|
+
attachments : attachments,
|
|
48
|
+
deleted_by : null,
|
|
49
|
+
reply_to : reply_to !== null ? parseInt(reply_to) : null,
|
|
50
|
+
pinned : false,
|
|
51
|
+
pin_id : null
|
|
52
|
+
}};
|
|
53
|
+
if(b4a.byteLength(jsonStringify(prepared)) > this.peer.protocol_instance.msgMaxBytes()) throw new Error('Message too large.');
|
|
54
|
+
return prepared;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async post(prepared_message, signature, nonce){
|
|
58
|
+
if(false === this.api_msg_exposed) throw new Error('Posting messages not exposed in API.');
|
|
59
|
+
if(b4a.byteLength(jsonStringify(prepared_message)) > this.peer.protocol_instance.msgMaxBytes()) throw new Error('Prepared message too large.');
|
|
60
|
+
if(typeof prepared_message !== 'object') throw new Error('Prepared message must be an object generated with api.prepareMessage().');
|
|
61
|
+
if(prepared_message.dispatch === undefined || prepared_message.dispatch.type === undefined ||
|
|
62
|
+
prepared_message.dispatch.msg === undefined || prepared_message.dispatch.address === undefined ||
|
|
63
|
+
prepared_message.dispatch.attachments === undefined || prepared_message.dispatch.deleted_by === undefined ||
|
|
64
|
+
prepared_message.dispatch.reply_to === undefined || prepared_message.dispatch.pinned === undefined ||
|
|
65
|
+
prepared_message.dispatch.pin_id === undefined) throw new Error('Invalid prepared message.');
|
|
66
|
+
if(prepared_message.dispatch.type !== 'msg') throw new Error('Invalid type.');
|
|
67
|
+
if(typeof prepared_message.dispatch.msg !== 'string') throw new Error('Msg must be a string');
|
|
68
|
+
if(b4a.toString(b4a.from(prepared_message.dispatch.address, 'hex'), 'hex') !== prepared_message.dispatch.address) throw new Error('Invalid address.');
|
|
69
|
+
if(false === Array.isArray(prepared_message.dispatch.attachments)) throw new Error('attachments must be an array.');
|
|
70
|
+
if(prepared_message.dispatch.attachments.length > 20) throw new Error('Too many attachments');
|
|
71
|
+
for(let i = 0; i < prepared_message.dispatch.attachments.length; i++){
|
|
72
|
+
if(typeof prepared_message.dispatch.attachments[i] !== 'string') throw new Error('Attachment at index ' + i + ' is not a string.');
|
|
73
|
+
}
|
|
74
|
+
if(prepared_message.dispatch.deleted_by !== null) throw new Error('deleted_by must be null');
|
|
75
|
+
if(prepared_message.dispatch.reply_to !== null && isNaN(parseInt(prepared_message.dispatch.reply_to))) throw new Error('Reply to not a number.');
|
|
76
|
+
if(prepared_message.dispatch.pinned !== false) throw new Error('pinned must be false');
|
|
77
|
+
if(prepared_message.dispatch.pin_id !== null) throw new Error('pin_id must be null');
|
|
78
|
+
const verified = this.peer.wallet.verify(signature, JSON.stringify(prepared_message) + nonce, prepared_message.dispatch.address);
|
|
79
|
+
if(false === verified) throw new Error('Invalid signature. Please sign your prepared message.');
|
|
80
|
+
await this.peer.base.append({type: 'msg', value: prepared_message, hash : signature, nonce: nonce });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async generateTx(address, command_hash, nonce) {
|
|
84
|
+
return await this.peer.protocol_instance.generateTx(this.getPeerBootstrap(),
|
|
85
|
+
this.getPeerMsbBootstrap(), this.getPeerValidatorAddress(), this.getPeerWriterKey(),
|
|
86
|
+
address, command_hash, nonce);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async prepareTxCommand(command){
|
|
90
|
+
return await this.peer.protocol_instance.mapTxCommand(command);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* To pass a TX, an ed25519 signature has to be provided over the given tx + nonce.
|
|
95
|
+
*
|
|
96
|
+
* Signing steps:
|
|
97
|
+
* let nonce = api.generateNonce()
|
|
98
|
+
* let tx = api.generateTx(address, sha256(JSON.stringify(api.prepareTxCommand(command))), nonce)
|
|
99
|
+
* let sig = your_sign_lib.sign(tx + nonce, address)
|
|
100
|
+
* api.tx(tx, api.prepareTxCommand(command), address, sig, nonce)
|
|
101
|
+
*
|
|
102
|
+
* @param tx
|
|
103
|
+
* @param prepared_command
|
|
104
|
+
* @param address
|
|
105
|
+
* @param signature
|
|
106
|
+
* @param nonce
|
|
107
|
+
* @param sim
|
|
108
|
+
* @returns {Promise<boolean>}
|
|
109
|
+
*/
|
|
110
|
+
async tx(tx, prepared_command, address, signature, nonce, sim = false ){
|
|
111
|
+
if(false === this.api_tx_exposed) throw new Error('Transactions not exposed in API.');
|
|
112
|
+
if(typeof prepared_command !== 'object') throw new Error('prepared_command must be an object.');
|
|
113
|
+
if(typeof prepared_command.type !== 'string') throw new Error('prepared_command.type must exist and be a string.');
|
|
114
|
+
if(prepared_command.value === undefined) throw new Error('prepared_command.value is missing.');
|
|
115
|
+
if(b4a.byteLength(jsonStringify(prepared_command)) > this.peer.protocol_instance.txMaxBytes()) throw new Error('prepared_command too large.');
|
|
116
|
+
if(b4a.byteLength(jsonStringify(address)) > 64) throw new Error('Address too large.');
|
|
117
|
+
if(b4a.byteLength(jsonStringify(signature)) > 128) throw new Error('Signature too large.');
|
|
118
|
+
if(b4a.byteLength(jsonStringify(nonce)) > 256) throw new Error('Nonce too large.');
|
|
119
|
+
if(b4a.toString(b4a.from(signature, 'hex'), 'hex') !== signature) throw new Error('Invalid signature.');
|
|
120
|
+
if(b4a.toString(b4a.from(address, 'hex'), 'hex') !== address) throw new Error('Invalid address.');
|
|
121
|
+
const verified = this.peer.wallet.verify(signature, tx + nonce, address);
|
|
122
|
+
if(false === verified) throw new Error('Invalid signature.');
|
|
123
|
+
const content_hash = await this.peer.createHash('sha256', this.peer.protocol_instance.safeJsonStringify(prepared_command));
|
|
124
|
+
let _tx = await this.generateTx(address, content_hash, nonce);
|
|
125
|
+
if(tx !== _tx) throw new Error('Invalid TX.');
|
|
126
|
+
while(null !== this.peer.protocol_instance.surrogate_tx) await this.peer.sleep(3);
|
|
127
|
+
this.peer.protocol_instance.surrogate_tx = { tx : ''+tx, nonce : ''+nonce, signature : ''+signature, address : ''+address };
|
|
128
|
+
let res = false;
|
|
129
|
+
try{
|
|
130
|
+
if(true === sim){
|
|
131
|
+
while(true === this.peer.protocol_instance.sim) await this.peer.sleep(3);
|
|
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);
|
|
136
|
+
} catch(e){ console.log(e) }
|
|
137
|
+
const err = this.peer.protocol_instance.getError(res);
|
|
138
|
+
if(null !== err){
|
|
139
|
+
console.log(err.message);
|
|
140
|
+
}
|
|
141
|
+
this.peer.protocol_instance.sim = false;
|
|
142
|
+
this.peer.protocol_instance.surrogate_tx = null;
|
|
143
|
+
return res;
|
|
4
144
|
}
|
|
5
145
|
|
|
6
146
|
async getAdmin(signed = true){
|
package/src/functions.js
CHANGED
|
@@ -332,6 +332,7 @@ export async function tx(input, peer){
|
|
|
332
332
|
let res = false;
|
|
333
333
|
try{
|
|
334
334
|
if(splitted.sim !== undefined && parseInt(splitted.sim) === 1){
|
|
335
|
+
while(true === peer.protocol_instance.sim) await peer.sleep(3);
|
|
335
336
|
peer.protocol_instance.sim = true;
|
|
336
337
|
}
|
|
337
338
|
res = await peer.protocol_instance.tx(splitted);
|
package/src/index.js
CHANGED
|
@@ -114,7 +114,6 @@ export class Peer extends ReadyResource {
|
|
|
114
114
|
post_tx.value.ch === content_hash &&
|
|
115
115
|
_this.wallet.verify(post_tx.value.ws, post_tx.value.tx + post_tx.value.wn, op.value.wp) &&
|
|
116
116
|
_this.wallet.verify(post_tx.value.is, post_tx.value.tx + post_tx.value.in, op.value.ipk) &&
|
|
117
|
-
_this.wallet.verify(op.value.hash, post_tx.value.tx + post_tx.value.ch + op.value.nonce, post_tx.value.ipk) &&
|
|
118
117
|
post_tx.value.tx === await _this.protocol_instance.generateTx(
|
|
119
118
|
_this.bootstrap, _this.msb.bootstrap,
|
|
120
119
|
post_tx.value.wp, post_tx.value.i, post_tx.value.ipk,
|
|
@@ -493,7 +492,7 @@ export class Peer extends ReadyResource {
|
|
|
493
492
|
if(this.validator_stream === null) return;
|
|
494
493
|
let _msg = safeClone(msg);
|
|
495
494
|
if(_msg['ts'] !== undefined) delete _msg['ts'];
|
|
496
|
-
await this.validator_stream.send(b4a.from(jsonStringify(_msg)));
|
|
495
|
+
try{ await this.validator_stream.send(b4a.from(jsonStringify(_msg))); } catch(e){ }
|
|
497
496
|
}
|
|
498
497
|
|
|
499
498
|
async getValidatorWriterKey(address){
|
|
@@ -554,10 +553,7 @@ export class Peer extends ReadyResource {
|
|
|
554
553
|
|
|
555
554
|
async initContract(){
|
|
556
555
|
this.init_contract_starting = true;
|
|
557
|
-
this.protocol_instance = new this.protocol(
|
|
558
|
-
peer : this,
|
|
559
|
-
base : this.base
|
|
560
|
-
});
|
|
556
|
+
this.protocol_instance = new this.protocol(this, this.base, this.options);
|
|
561
557
|
await this.protocol_instance.extendApi();
|
|
562
558
|
this.contract_instance = new this.contract(this.protocol_instance);
|
|
563
559
|
}
|
|
@@ -651,12 +647,10 @@ export class Peer extends ReadyResource {
|
|
|
651
647
|
const msb_tx = await view_session.get(tx);
|
|
652
648
|
await view_session.close();
|
|
653
649
|
if(null !== msb_tx){
|
|
654
|
-
msb_tx['dispatch'] = this.protocol_instance.prepared_transactions_content[tx];
|
|
650
|
+
msb_tx['dispatch'] = this.protocol_instance.prepared_transactions_content[tx].dispatch;
|
|
655
651
|
msb_tx['msbsl'] = msbsl;
|
|
656
|
-
msb_tx['ipk'] = this.
|
|
652
|
+
msb_tx['ipk'] = this.protocol_instance.prepared_transactions_content[tx].ipk;
|
|
657
653
|
msb_tx['wp'] = this.validator;
|
|
658
|
-
msb_tx['nonce'] = this.protocol_instance.generateNonce();
|
|
659
|
-
msb_tx['hash'] = this.wallet.sign(tx + await this.createHash('sha256', jsonStringify(msb_tx['dispatch'])) + msb_tx['nonce']);
|
|
660
654
|
delete this.tx_pool[tx];
|
|
661
655
|
delete this.protocol_instance.prepared_transactions_content[tx];
|
|
662
656
|
await this.base.append({ type: 'tx', key: tx, value: msb_tx });
|
|
@@ -706,7 +700,7 @@ export class Peer extends ReadyResource {
|
|
|
706
700
|
secretKey: b4a.from(this.wallet.secretKey, 'hex')
|
|
707
701
|
};
|
|
708
702
|
|
|
709
|
-
this.swarm = new Hyperswarm({ keyPair, bootstrap: this.dhtBootstrap });
|
|
703
|
+
this.swarm = new Hyperswarm({ keyPair, randomPunchInterval: 500, bootstrap: this.dhtBootstrap });
|
|
710
704
|
this.dhtNode = this.swarm.dht;
|
|
711
705
|
|
|
712
706
|
console.log(`Writer key: ${this.writerLocalKey}`)
|
package/src/protocol.js
CHANGED
|
@@ -3,10 +3,10 @@ import {ProtocolApi} from './api.js';
|
|
|
3
3
|
import Wallet from 'trac-wallet';
|
|
4
4
|
|
|
5
5
|
class Protocol{
|
|
6
|
-
constructor(options = {}) {
|
|
7
|
-
this.api = new ProtocolApi(
|
|
8
|
-
this.base =
|
|
9
|
-
this.peer =
|
|
6
|
+
constructor(peer, base, options = {}) {
|
|
7
|
+
this.api = new ProtocolApi(peer, options);
|
|
8
|
+
this.base = base;
|
|
9
|
+
this.peer = peer;
|
|
10
10
|
this.options = options;
|
|
11
11
|
this.input = null;
|
|
12
12
|
this.tokenized_input = null;
|
|
@@ -15,10 +15,10 @@ class Protocol{
|
|
|
15
15
|
this.safeJsonStringify = jsonStringify;
|
|
16
16
|
this.safeJsonParse = jsonParse;
|
|
17
17
|
this.safeClone = safeClone;
|
|
18
|
-
this.nonce = 0;
|
|
19
18
|
this.prepared_transactions_content = {};
|
|
20
19
|
this.features = {};
|
|
21
20
|
this.sim = false;
|
|
21
|
+
this.surrogate_tx = false;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
featMaxBytes(){
|
|
@@ -91,10 +91,10 @@ class Protocol{
|
|
|
91
91
|
this.features[key] = feature;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
async generateTx(bootstrap, msb_bootstrap,
|
|
94
|
+
async generateTx(bootstrap, msb_bootstrap, validator_public_key, local_writer_key, local_public_key, content_hash, nonce){
|
|
95
95
|
let tx = bootstrap + '-' +
|
|
96
96
|
msb_bootstrap + '-' +
|
|
97
|
-
|
|
97
|
+
validator_public_key + '-' +
|
|
98
98
|
local_writer_key + '-' +
|
|
99
99
|
local_public_key + '-' +
|
|
100
100
|
content_hash + '-' +
|
|
@@ -106,16 +106,16 @@ class Protocol{
|
|
|
106
106
|
const storage = new SimStorage(this.peer);
|
|
107
107
|
const null_hex = '0000000000000000000000000000000000000000000000000000000000000000';
|
|
108
108
|
let nonce = this.generateNonce();
|
|
109
|
-
const content_hash = await this.peer.createHash('sha256',
|
|
109
|
+
const content_hash = await this.peer.createHash('sha256', this.safeJsonStringify(obj));
|
|
110
110
|
let tx = await this.generateTx(this.peer.bootstrap, this.peer.msb.bootstrap, null_hex,
|
|
111
|
-
this.peer.writerLocalKey, this.peer.wallet.publicKey, content_hash, nonce);
|
|
111
|
+
this.peer.writerLocalKey, this.surrogate_tx !== null ? this.surrogate_tx.address : this.peer.wallet.publicKey, content_hash, nonce);
|
|
112
112
|
const op = {
|
|
113
113
|
type : 'tx',
|
|
114
114
|
key : tx,
|
|
115
115
|
value : {
|
|
116
116
|
dispatch : obj,
|
|
117
117
|
value : {
|
|
118
|
-
ipk : this.peer.wallet.publicKey,
|
|
118
|
+
ipk : this.surrogate_tx !== null ? this.surrogate_tx.address : this.peer.wallet.publicKey,
|
|
119
119
|
wp : null_hex
|
|
120
120
|
}
|
|
121
121
|
}
|
|
@@ -134,24 +134,36 @@ class Protocol{
|
|
|
134
134
|
obj.type !== undefined &&
|
|
135
135
|
obj.value !== undefined)
|
|
136
136
|
{
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
137
|
+
|
|
138
|
+
let tx, signature, nonce, publicKey;
|
|
139
|
+
const content_hash = await this.peer.createHash('sha256', this.safeJsonStringify(obj));
|
|
140
|
+
|
|
141
|
+
if(this.surrogate_tx !== null) {
|
|
142
|
+
nonce = this.surrogate_tx.nonce;
|
|
143
|
+
tx = this.surrogate_tx.tx;
|
|
144
|
+
signature = this.surrogate_tx.signature;
|
|
145
|
+
publicKey = this.surrogate_tx.address;
|
|
146
|
+
} else {
|
|
147
|
+
nonce = this.generateNonce();
|
|
148
|
+
tx = await this.generateTx(this.peer.bootstrap, this.peer.msb.bootstrap, validator_pub_key,
|
|
149
|
+
this.peer.writerLocalKey, this.peer.wallet.publicKey, content_hash, nonce);
|
|
150
|
+
signature = this.peer.wallet.sign(tx + nonce);
|
|
151
|
+
publicKey = this.peer.wallet.publicKey;
|
|
152
|
+
}
|
|
153
|
+
|
|
142
154
|
this.peer.emit('tx', {
|
|
143
155
|
op: 'pre-tx',
|
|
144
156
|
tx: tx,
|
|
145
157
|
is: signature,
|
|
146
158
|
wp : validator_pub_key,
|
|
147
159
|
i: this.peer.writerLocalKey,
|
|
148
|
-
ipk:
|
|
160
|
+
ipk: publicKey,
|
|
149
161
|
ch : content_hash,
|
|
150
|
-
in :
|
|
162
|
+
in : nonce,
|
|
151
163
|
bs : this.peer.bootstrap,
|
|
152
164
|
mbs : this.peer.msb.bootstrap
|
|
153
165
|
});
|
|
154
|
-
this.prepared_transactions_content[tx] = obj;
|
|
166
|
+
this.prepared_transactions_content[tx] = { dispatch : obj, ipk : publicKey };
|
|
155
167
|
} else {
|
|
156
168
|
throw Error('broadcastTransaction(writer, obj): Cannot prepare transaction. Please make sure inputs and local writer are set.');
|
|
157
169
|
}
|
|
@@ -174,7 +186,20 @@ class Protocol{
|
|
|
174
186
|
return null;
|
|
175
187
|
}
|
|
176
188
|
|
|
177
|
-
async
|
|
189
|
+
async mapTxCommand(command){
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async tx(subject){
|
|
194
|
+
const obj = this.mapTxCommand(subject.command);
|
|
195
|
+
if(null !== obj) {
|
|
196
|
+
return await this.broadcastTransaction(subject.validator,{
|
|
197
|
+
type : obj.type,
|
|
198
|
+
value : obj.value
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return new Error('HyperMallProtocol::tx(): command not found.');
|
|
202
|
+
}
|
|
178
203
|
|
|
179
204
|
async customCommand(input){ }
|
|
180
205
|
|