@ruvector/edge-net 0.2.0 → 0.2.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruvector/edge-net",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Distributed compute intelligence network with AI agents and workers - contribute browser compute, spawn distributed AI agents, earn credits. Features Time Crystal coordination, Neural DAG attention, P2P swarm intelligence, ONNX inference, WebRTC signaling, CRDT ledger, and multi-agent workflows.",
6
6
  "main": "ruvector_edge_net.js",
package/qdag.js CHANGED
@@ -193,26 +193,37 @@ export class QDAG extends EventEmitter {
193
193
  * Select tips for new transaction (weighted random walk)
194
194
  */
195
195
  selectTips(count = 2) {
196
+ // Ensure genesis exists
197
+ if (!this.transactions.has('genesis')) {
198
+ this.createGenesis();
199
+ }
200
+
196
201
  const tips = Array.from(this.tips);
197
202
 
203
+ // Fallback to genesis if no tips available
198
204
  if (tips.length === 0) {
199
205
  return ['genesis'];
200
206
  }
201
207
 
208
+ // Return all tips if we have fewer than requested
202
209
  if (tips.length <= count) {
203
- return tips;
210
+ return [...tips]; // Return copy to prevent mutation issues
204
211
  }
205
212
 
206
213
  // Weighted random selection based on cumulative weight
207
214
  const selected = new Set();
208
215
  const weights = tips.map(tipId => {
209
216
  const tx = this.transactions.get(tipId);
210
- return tx ? tx.cumulativeWeight : 1;
217
+ return tx ? Math.max(tx.cumulativeWeight, 1) : 1;
211
218
  });
212
219
 
213
220
  const totalWeight = weights.reduce((a, b) => a + b, 0);
214
221
 
215
- while (selected.size < count && selected.size < tips.length) {
222
+ // Safety: prevent infinite loop
223
+ let attempts = 0;
224
+ const maxAttempts = count * 10;
225
+
226
+ while (selected.size < count && selected.size < tips.length && attempts < maxAttempts) {
216
227
  let random = Math.random() * totalWeight;
217
228
 
218
229
  for (let i = 0; i < tips.length; i++) {
@@ -222,18 +233,26 @@ export class QDAG extends EventEmitter {
222
233
  break;
223
234
  }
224
235
  }
236
+ attempts++;
225
237
  }
226
238
 
227
- return Array.from(selected);
239
+ // Ensure we have at least one valid parent
240
+ const result = Array.from(selected);
241
+ if (result.length === 0) {
242
+ result.push(tips[0] || 'genesis');
243
+ }
244
+
245
+ return result;
228
246
  }
229
247
 
230
248
  /**
231
249
  * Add transaction to QDAG
232
250
  */
233
251
  addTransaction(tx) {
234
- // Validate transaction
235
- if (!this.validateTransaction(tx)) {
236
- throw new Error('Invalid transaction');
252
+ // Validate transaction with detailed error
253
+ const validation = this.validateTransaction(tx, { returnError: true });
254
+ if (!validation.valid) {
255
+ throw new Error(`Invalid transaction: ${validation.error}`);
237
256
  }
238
257
 
239
258
  // Check for duplicates
@@ -304,35 +323,55 @@ export class QDAG extends EventEmitter {
304
323
 
305
324
  /**
306
325
  * Validate transaction
326
+ * @returns {boolean|{valid: boolean, error: string}}
307
327
  */
308
- validateTransaction(tx) {
328
+ validateTransaction(tx, options = {}) {
329
+ const returnError = options.returnError || false;
330
+ const fail = (msg) => returnError ? { valid: false, error: msg } : false;
331
+ const pass = () => returnError ? { valid: true, error: null } : true;
332
+
309
333
  // Check required fields
310
- if (!tx.id || !tx.timestamp || !tx.type) {
311
- return false;
334
+ if (!tx.id) {
335
+ return fail('Missing transaction id');
336
+ }
337
+ if (!tx.timestamp) {
338
+ return fail('Missing transaction timestamp');
339
+ }
340
+ if (!tx.type) {
341
+ return fail('Missing transaction type');
312
342
  }
313
343
 
314
- // Check parents exist (except genesis)
315
- if (tx.type !== 'genesis') {
316
- if (tx.parents.length === 0) {
317
- return false;
318
- }
344
+ // Genesis transactions don't need parents
345
+ if (tx.type === 'genesis') {
346
+ return pass();
347
+ }
319
348
 
320
- for (const parentId of tx.parents) {
321
- if (!this.transactions.has(parentId)) {
322
- return false;
323
- }
349
+ // Ensure genesis exists before validating non-genesis transactions
350
+ if (!this.transactions.has('genesis')) {
351
+ this.createGenesis();
352
+ }
353
+
354
+ // Check parents exist
355
+ if (!tx.parents || tx.parents.length === 0) {
356
+ return fail('Non-genesis transaction must have at least one parent');
357
+ }
358
+
359
+ for (const parentId of tx.parents) {
360
+ if (!this.transactions.has(parentId)) {
361
+ return fail(`Parent transaction not found: ${parentId}`);
324
362
  }
325
363
  }
326
364
 
327
- // Check no cycles (parents must be older)
365
+ // Check no cycles (parents must be older or equal for simultaneous txs)
328
366
  for (const parentId of tx.parents) {
329
367
  const parent = this.transactions.get(parentId);
330
- if (parent && parent.timestamp >= tx.timestamp) {
331
- return false;
368
+ // Allow equal timestamps (transactions created at same time)
369
+ if (parent && parent.timestamp > tx.timestamp) {
370
+ return fail(`Parent ${parentId} has future timestamp`);
332
371
  }
333
372
  }
334
373
 
335
- return true;
374
+ return pass();
336
375
  }
337
376
 
338
377
  /**
Binary file