@ruvector/edge-net 0.1.7 → 0.2.0
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/ledger.js +663 -0
- package/monitor.js +675 -0
- package/onnx-worker.js +482 -0
- package/package.json +40 -5
- package/qdag.js +582 -0
- package/scheduler.js +764 -0
- package/signaling.js +732 -0
package/qdag.js
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ruvector/edge-net QDAG (Quantum DAG) Implementation
|
|
3
|
+
*
|
|
4
|
+
* Directed Acyclic Graph for distributed consensus and task tracking
|
|
5
|
+
* Inspired by IOTA Tangle and DAG-based blockchains
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Tip selection algorithm
|
|
9
|
+
* - Proof of contribution verification
|
|
10
|
+
* - Transaction validation
|
|
11
|
+
* - Network synchronization
|
|
12
|
+
*
|
|
13
|
+
* @module @ruvector/edge-net/qdag
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { EventEmitter } from 'events';
|
|
17
|
+
import { randomBytes, createHash, createHmac } from 'crypto';
|
|
18
|
+
|
|
19
|
+
// ============================================
|
|
20
|
+
// TRANSACTION
|
|
21
|
+
// ============================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* QDAG Transaction
|
|
25
|
+
*/
|
|
26
|
+
export class Transaction {
|
|
27
|
+
constructor(data = {}) {
|
|
28
|
+
this.id = data.id || `tx-${randomBytes(16).toString('hex')}`;
|
|
29
|
+
this.timestamp = data.timestamp || Date.now();
|
|
30
|
+
this.type = data.type || 'generic'; // 'genesis', 'task', 'reward', 'transfer'
|
|
31
|
+
|
|
32
|
+
// Links to parent transactions (must reference 2 tips)
|
|
33
|
+
this.parents = data.parents || [];
|
|
34
|
+
|
|
35
|
+
// Transaction payload
|
|
36
|
+
this.payload = data.payload || {};
|
|
37
|
+
|
|
38
|
+
// Proof of contribution
|
|
39
|
+
this.proof = data.proof || null;
|
|
40
|
+
|
|
41
|
+
// Issuer
|
|
42
|
+
this.issuer = data.issuer || null;
|
|
43
|
+
this.signature = data.signature || null;
|
|
44
|
+
|
|
45
|
+
// Computed fields
|
|
46
|
+
this.hash = data.hash || this.computeHash();
|
|
47
|
+
this.weight = data.weight || 1;
|
|
48
|
+
this.cumulativeWeight = data.cumulativeWeight || 1;
|
|
49
|
+
this.confirmed = data.confirmed || false;
|
|
50
|
+
this.confirmedAt = data.confirmedAt || null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Compute transaction hash
|
|
55
|
+
*/
|
|
56
|
+
computeHash() {
|
|
57
|
+
const content = JSON.stringify({
|
|
58
|
+
id: this.id,
|
|
59
|
+
timestamp: this.timestamp,
|
|
60
|
+
type: this.type,
|
|
61
|
+
parents: this.parents,
|
|
62
|
+
payload: this.payload,
|
|
63
|
+
proof: this.proof,
|
|
64
|
+
issuer: this.issuer,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return createHash('sha256').update(content).digest('hex');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Sign transaction
|
|
72
|
+
*/
|
|
73
|
+
sign(privateKey) {
|
|
74
|
+
const hmac = createHmac('sha256', privateKey);
|
|
75
|
+
hmac.update(this.hash);
|
|
76
|
+
this.signature = hmac.digest('hex');
|
|
77
|
+
return this.signature;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Verify signature
|
|
82
|
+
*/
|
|
83
|
+
verify(publicKey) {
|
|
84
|
+
if (!this.signature) return false;
|
|
85
|
+
|
|
86
|
+
const hmac = createHmac('sha256', publicKey);
|
|
87
|
+
hmac.update(this.hash);
|
|
88
|
+
const expected = hmac.digest('hex');
|
|
89
|
+
|
|
90
|
+
return this.signature === expected;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Serialize transaction
|
|
95
|
+
*/
|
|
96
|
+
toJSON() {
|
|
97
|
+
return {
|
|
98
|
+
id: this.id,
|
|
99
|
+
timestamp: this.timestamp,
|
|
100
|
+
type: this.type,
|
|
101
|
+
parents: this.parents,
|
|
102
|
+
payload: this.payload,
|
|
103
|
+
proof: this.proof,
|
|
104
|
+
issuer: this.issuer,
|
|
105
|
+
signature: this.signature,
|
|
106
|
+
hash: this.hash,
|
|
107
|
+
weight: this.weight,
|
|
108
|
+
cumulativeWeight: this.cumulativeWeight,
|
|
109
|
+
confirmed: this.confirmed,
|
|
110
|
+
confirmedAt: this.confirmedAt,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Deserialize transaction
|
|
116
|
+
*/
|
|
117
|
+
static fromJSON(json) {
|
|
118
|
+
return new Transaction(json);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================
|
|
123
|
+
// QDAG (Quantum DAG)
|
|
124
|
+
// ============================================
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* QDAG - Directed Acyclic Graph for distributed consensus
|
|
128
|
+
*/
|
|
129
|
+
export class QDAG extends EventEmitter {
|
|
130
|
+
constructor(options = {}) {
|
|
131
|
+
super();
|
|
132
|
+
this.id = options.id || `qdag-${randomBytes(8).toString('hex')}`;
|
|
133
|
+
this.nodeId = options.nodeId;
|
|
134
|
+
|
|
135
|
+
// Transaction storage
|
|
136
|
+
this.transactions = new Map();
|
|
137
|
+
this.tips = new Set(); // Unconfirmed transactions
|
|
138
|
+
this.confirmed = new Set(); // Confirmed transactions
|
|
139
|
+
|
|
140
|
+
// Indices
|
|
141
|
+
this.byIssuer = new Map(); // issuer -> Set<txId>
|
|
142
|
+
this.byType = new Map(); // type -> Set<txId>
|
|
143
|
+
this.children = new Map(); // txId -> Set<childTxId>
|
|
144
|
+
|
|
145
|
+
// Configuration
|
|
146
|
+
this.confirmationThreshold = options.confirmationThreshold || 5;
|
|
147
|
+
this.maxTips = options.maxTips || 100;
|
|
148
|
+
this.pruneAge = options.pruneAge || 24 * 60 * 60 * 1000; // 24 hours
|
|
149
|
+
|
|
150
|
+
// Stats
|
|
151
|
+
this.stats = {
|
|
152
|
+
transactions: 0,
|
|
153
|
+
confirmed: 0,
|
|
154
|
+
tips: 0,
|
|
155
|
+
avgConfirmationTime: 0,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Create genesis if needed
|
|
159
|
+
if (options.createGenesis !== false) {
|
|
160
|
+
this.createGenesis();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create genesis transaction
|
|
166
|
+
*/
|
|
167
|
+
createGenesis() {
|
|
168
|
+
const genesis = new Transaction({
|
|
169
|
+
id: 'genesis',
|
|
170
|
+
type: 'genesis',
|
|
171
|
+
parents: [],
|
|
172
|
+
payload: {
|
|
173
|
+
message: 'QDAG Genesis',
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
},
|
|
176
|
+
issuer: 'system',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
genesis.confirmed = true;
|
|
180
|
+
genesis.confirmedAt = Date.now();
|
|
181
|
+
genesis.cumulativeWeight = this.confirmationThreshold + 1;
|
|
182
|
+
|
|
183
|
+
this.transactions.set(genesis.id, genesis);
|
|
184
|
+
this.tips.add(genesis.id);
|
|
185
|
+
this.confirmed.add(genesis.id);
|
|
186
|
+
|
|
187
|
+
this.emit('genesis', genesis);
|
|
188
|
+
|
|
189
|
+
return genesis;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Select tips for new transaction (weighted random walk)
|
|
194
|
+
*/
|
|
195
|
+
selectTips(count = 2) {
|
|
196
|
+
const tips = Array.from(this.tips);
|
|
197
|
+
|
|
198
|
+
if (tips.length === 0) {
|
|
199
|
+
return ['genesis'];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (tips.length <= count) {
|
|
203
|
+
return tips;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Weighted random selection based on cumulative weight
|
|
207
|
+
const selected = new Set();
|
|
208
|
+
const weights = tips.map(tipId => {
|
|
209
|
+
const tx = this.transactions.get(tipId);
|
|
210
|
+
return tx ? tx.cumulativeWeight : 1;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const totalWeight = weights.reduce((a, b) => a + b, 0);
|
|
214
|
+
|
|
215
|
+
while (selected.size < count && selected.size < tips.length) {
|
|
216
|
+
let random = Math.random() * totalWeight;
|
|
217
|
+
|
|
218
|
+
for (let i = 0; i < tips.length; i++) {
|
|
219
|
+
random -= weights[i];
|
|
220
|
+
if (random <= 0) {
|
|
221
|
+
selected.add(tips[i]);
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return Array.from(selected);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Add transaction to QDAG
|
|
232
|
+
*/
|
|
233
|
+
addTransaction(tx) {
|
|
234
|
+
// Validate transaction
|
|
235
|
+
if (!this.validateTransaction(tx)) {
|
|
236
|
+
throw new Error('Invalid transaction');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check for duplicates
|
|
240
|
+
if (this.transactions.has(tx.id)) {
|
|
241
|
+
return this.transactions.get(tx.id);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Store transaction
|
|
245
|
+
this.transactions.set(tx.id, tx);
|
|
246
|
+
this.tips.add(tx.id);
|
|
247
|
+
this.stats.transactions++;
|
|
248
|
+
|
|
249
|
+
// Update indices
|
|
250
|
+
if (tx.issuer) {
|
|
251
|
+
if (!this.byIssuer.has(tx.issuer)) {
|
|
252
|
+
this.byIssuer.set(tx.issuer, new Set());
|
|
253
|
+
}
|
|
254
|
+
this.byIssuer.get(tx.issuer).add(tx.id);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!this.byType.has(tx.type)) {
|
|
258
|
+
this.byType.set(tx.type, new Set());
|
|
259
|
+
}
|
|
260
|
+
this.byType.get(tx.type).add(tx.id);
|
|
261
|
+
|
|
262
|
+
// Update parent references
|
|
263
|
+
for (const parentId of tx.parents) {
|
|
264
|
+
if (!this.children.has(parentId)) {
|
|
265
|
+
this.children.set(parentId, new Set());
|
|
266
|
+
}
|
|
267
|
+
this.children.get(parentId).add(tx.id);
|
|
268
|
+
|
|
269
|
+
// Remove parent from tips
|
|
270
|
+
this.tips.delete(parentId);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Update weights
|
|
274
|
+
this.updateWeights(tx.id);
|
|
275
|
+
|
|
276
|
+
// Check for confirmations
|
|
277
|
+
this.checkConfirmations();
|
|
278
|
+
|
|
279
|
+
this.emit('transaction', tx);
|
|
280
|
+
|
|
281
|
+
return tx;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Create and add a new transaction
|
|
286
|
+
*/
|
|
287
|
+
createTransaction(type, payload, options = {}) {
|
|
288
|
+
const parents = options.parents || this.selectTips(2);
|
|
289
|
+
|
|
290
|
+
const tx = new Transaction({
|
|
291
|
+
type,
|
|
292
|
+
payload,
|
|
293
|
+
parents,
|
|
294
|
+
issuer: options.issuer || this.nodeId,
|
|
295
|
+
proof: options.proof,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (options.privateKey) {
|
|
299
|
+
tx.sign(options.privateKey);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return this.addTransaction(tx);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Validate transaction
|
|
307
|
+
*/
|
|
308
|
+
validateTransaction(tx) {
|
|
309
|
+
// Check required fields
|
|
310
|
+
if (!tx.id || !tx.timestamp || !tx.type) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Check parents exist (except genesis)
|
|
315
|
+
if (tx.type !== 'genesis') {
|
|
316
|
+
if (tx.parents.length === 0) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
for (const parentId of tx.parents) {
|
|
321
|
+
if (!this.transactions.has(parentId)) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Check no cycles (parents must be older)
|
|
328
|
+
for (const parentId of tx.parents) {
|
|
329
|
+
const parent = this.transactions.get(parentId);
|
|
330
|
+
if (parent && parent.timestamp >= tx.timestamp) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Update cumulative weights
|
|
340
|
+
*/
|
|
341
|
+
updateWeights(txId) {
|
|
342
|
+
const tx = this.transactions.get(txId);
|
|
343
|
+
if (!tx) return;
|
|
344
|
+
|
|
345
|
+
// Update weight of this transaction
|
|
346
|
+
tx.cumulativeWeight = tx.weight;
|
|
347
|
+
|
|
348
|
+
// Add weight of all children
|
|
349
|
+
const children = this.children.get(txId);
|
|
350
|
+
if (children) {
|
|
351
|
+
for (const childId of children) {
|
|
352
|
+
const child = this.transactions.get(childId);
|
|
353
|
+
if (child) {
|
|
354
|
+
tx.cumulativeWeight += child.cumulativeWeight;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Propagate to parents
|
|
360
|
+
for (const parentId of tx.parents) {
|
|
361
|
+
this.updateWeights(parentId);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Check for newly confirmed transactions
|
|
367
|
+
*/
|
|
368
|
+
checkConfirmations() {
|
|
369
|
+
for (const [txId, tx] of this.transactions) {
|
|
370
|
+
if (!tx.confirmed && tx.cumulativeWeight >= this.confirmationThreshold) {
|
|
371
|
+
tx.confirmed = true;
|
|
372
|
+
tx.confirmedAt = Date.now();
|
|
373
|
+
|
|
374
|
+
this.confirmed.add(txId);
|
|
375
|
+
this.stats.confirmed++;
|
|
376
|
+
|
|
377
|
+
// Update average confirmation time
|
|
378
|
+
const confirmTime = tx.confirmedAt - tx.timestamp;
|
|
379
|
+
this.stats.avgConfirmationTime =
|
|
380
|
+
(this.stats.avgConfirmationTime * (this.stats.confirmed - 1) + confirmTime) /
|
|
381
|
+
this.stats.confirmed;
|
|
382
|
+
|
|
383
|
+
this.emit('confirmed', tx);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
this.stats.tips = this.tips.size;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get transaction by ID
|
|
392
|
+
*/
|
|
393
|
+
getTransaction(txId) {
|
|
394
|
+
return this.transactions.get(txId);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Get transactions by issuer
|
|
399
|
+
*/
|
|
400
|
+
getByIssuer(issuer) {
|
|
401
|
+
const txIds = this.byIssuer.get(issuer) || new Set();
|
|
402
|
+
return Array.from(txIds).map(id => this.transactions.get(id));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get transactions by type
|
|
407
|
+
*/
|
|
408
|
+
getByType(type) {
|
|
409
|
+
const txIds = this.byType.get(type) || new Set();
|
|
410
|
+
return Array.from(txIds).map(id => this.transactions.get(id));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Get current tips
|
|
415
|
+
*/
|
|
416
|
+
getTips() {
|
|
417
|
+
return Array.from(this.tips).map(id => this.transactions.get(id));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Get confirmed transactions
|
|
422
|
+
*/
|
|
423
|
+
getConfirmed() {
|
|
424
|
+
return Array.from(this.confirmed).map(id => this.transactions.get(id));
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Prune old transactions
|
|
429
|
+
*/
|
|
430
|
+
prune() {
|
|
431
|
+
const cutoff = Date.now() - this.pruneAge;
|
|
432
|
+
let pruned = 0;
|
|
433
|
+
|
|
434
|
+
for (const [txId, tx] of this.transactions) {
|
|
435
|
+
if (tx.type === 'genesis') continue;
|
|
436
|
+
|
|
437
|
+
if (tx.confirmed && tx.confirmedAt < cutoff) {
|
|
438
|
+
// Remove from storage
|
|
439
|
+
this.transactions.delete(txId);
|
|
440
|
+
this.confirmed.delete(txId);
|
|
441
|
+
this.tips.delete(txId);
|
|
442
|
+
|
|
443
|
+
// Clean up indices
|
|
444
|
+
if (tx.issuer && this.byIssuer.has(tx.issuer)) {
|
|
445
|
+
this.byIssuer.get(tx.issuer).delete(txId);
|
|
446
|
+
}
|
|
447
|
+
if (this.byType.has(tx.type)) {
|
|
448
|
+
this.byType.get(tx.type).delete(txId);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
this.children.delete(txId);
|
|
452
|
+
|
|
453
|
+
pruned++;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (pruned > 0) {
|
|
458
|
+
this.emit('pruned', { count: pruned });
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return pruned;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Get QDAG statistics
|
|
466
|
+
*/
|
|
467
|
+
getStats() {
|
|
468
|
+
return {
|
|
469
|
+
id: this.id,
|
|
470
|
+
...this.stats,
|
|
471
|
+
size: this.transactions.size,
|
|
472
|
+
memoryUsage: process.memoryUsage?.().heapUsed,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Export QDAG for synchronization
|
|
478
|
+
*/
|
|
479
|
+
export(since = 0) {
|
|
480
|
+
const transactions = [];
|
|
481
|
+
|
|
482
|
+
for (const [txId, tx] of this.transactions) {
|
|
483
|
+
if (tx.timestamp >= since) {
|
|
484
|
+
transactions.push(tx.toJSON());
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
id: this.id,
|
|
490
|
+
timestamp: Date.now(),
|
|
491
|
+
transactions,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Import transactions from another node
|
|
497
|
+
*/
|
|
498
|
+
import(data) {
|
|
499
|
+
let imported = 0;
|
|
500
|
+
|
|
501
|
+
// Sort by timestamp to maintain order
|
|
502
|
+
const sorted = data.transactions.sort((a, b) => a.timestamp - b.timestamp);
|
|
503
|
+
|
|
504
|
+
for (const txData of sorted) {
|
|
505
|
+
try {
|
|
506
|
+
const tx = Transaction.fromJSON(txData);
|
|
507
|
+
if (!this.transactions.has(tx.id)) {
|
|
508
|
+
this.addTransaction(tx);
|
|
509
|
+
imported++;
|
|
510
|
+
}
|
|
511
|
+
} catch (error) {
|
|
512
|
+
console.error('[QDAG] Import error:', error.message);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
this.emit('imported', { count: imported, from: data.id });
|
|
517
|
+
|
|
518
|
+
return imported;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Merge with another QDAG
|
|
523
|
+
*/
|
|
524
|
+
merge(other) {
|
|
525
|
+
return this.import(other.export());
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ============================================
|
|
530
|
+
// TASK TRANSACTION HELPERS
|
|
531
|
+
// ============================================
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Create a task submission transaction
|
|
535
|
+
*/
|
|
536
|
+
export function createTaskTransaction(qdag, task, options = {}) {
|
|
537
|
+
return qdag.createTransaction('task', {
|
|
538
|
+
taskId: task.id,
|
|
539
|
+
type: task.type,
|
|
540
|
+
data: task.data,
|
|
541
|
+
priority: task.priority || 'medium',
|
|
542
|
+
reward: task.reward || 0,
|
|
543
|
+
deadline: task.deadline,
|
|
544
|
+
}, options);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Create a task completion/reward transaction
|
|
549
|
+
*/
|
|
550
|
+
export function createRewardTransaction(qdag, taskTxId, result, options = {}) {
|
|
551
|
+
const taskTx = qdag.getTransaction(taskTxId);
|
|
552
|
+
if (!taskTx) throw new Error('Task transaction not found');
|
|
553
|
+
|
|
554
|
+
return qdag.createTransaction('reward', {
|
|
555
|
+
taskTxId,
|
|
556
|
+
result,
|
|
557
|
+
worker: options.worker,
|
|
558
|
+
reward: taskTx.payload.reward || 0,
|
|
559
|
+
completedAt: Date.now(),
|
|
560
|
+
}, {
|
|
561
|
+
...options,
|
|
562
|
+
parents: [taskTxId, ...qdag.selectTips(1)],
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Create a credit transfer transaction
|
|
568
|
+
*/
|
|
569
|
+
export function createTransferTransaction(qdag, from, to, amount, options = {}) {
|
|
570
|
+
return qdag.createTransaction('transfer', {
|
|
571
|
+
from,
|
|
572
|
+
to,
|
|
573
|
+
amount,
|
|
574
|
+
memo: options.memo,
|
|
575
|
+
}, options);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ============================================
|
|
579
|
+
// EXPORTS
|
|
580
|
+
// ============================================
|
|
581
|
+
|
|
582
|
+
export default QDAG;
|