@anonympins/fingerprint 0.5.2 → 0.5.5
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/CHANGELOG.md +27 -0
- package/package.json +1 -1
- package/src/js/build-client.js +67 -67
- package/src/js/fingerprint.client.js +280 -38
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +474 -163
- package/src/js/fingerprint.utils.js +213 -213
- package/src/js/library.js +24 -8
- package/src/js/mongodb-store.js +165 -79
- package/src/js/obfuscation.worker.js +23 -0
- package/src/js/pow.solver.inline.js +509 -14
- package/src/js/pow.solver.js +350 -32
- package/src/js/problem-manager.js +1 -1
- package/src/js/redis-store.js +114 -42
- package/src/js/tests/cross_parity.test.js +175 -0
- package/src/js/tests/dns-circuit-breaker.test.js +55 -0
- package/src/js/tests/fingerprint.client.init.test.js +143 -143
- package/src/js/tests/fingerprint.client.test.js +128 -128
- package/src/js/tests/fingerprint.test.js +57 -2
- package/src/js/tests/ip-reputation.test.js +1 -1
- package/src/js/tests/obfuscation.test.js +48 -0
- package/src/js/tests/pow.solver.test.js +5 -2
- package/src/js/tests/usefulWorkSolver.test.js +162 -0
- package/src/php/AutoTuner.php +42 -0
- package/src/php/Challenge/ChallengeUtils.php +21 -8
- package/src/php/FingerprintClient.php +22 -5
- package/src/php/FingerprintEngine.php +233 -57
- package/src/php/Optimization/Optimization.php +23 -16
- package/src/php/Optimization/OptimizationOperators.php +27 -0
- package/src/php/ProblemManager.php +1 -1
- package/src/php/Store/MongoDbStore.php +184 -104
- package/src/php/Store/RedisStore.php +91 -11
- package/src/php/Tests/ChallengeUtilsTest.php +285 -285
- package/src/php/Tests/DnsCircuitBreakerTest.php +106 -0
- package/src/php/Tests/IpReputationTest.php +1 -1
- package/src/php/Tests/PowTest.php +1 -1
- package/src/php/Tests/RequestUtilsTest.php +29 -0
- package/src/php/Tests/TLSClientHelloParserTest.php +177 -117
- package/src/php/Utils/BigInt.php +202 -202
- package/src/php/Utils/RequestUtils.php +74 -21
- package/src/php/Utils/TLSClientHelloParser.php +212 -116
package/src/js/pow.solver.js
CHANGED
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
+
function secureRandom() {
|
|
13
|
+
if (typeof window !== 'undefined' && (window.crypto || window.msCrypto)) {
|
|
14
|
+
const array = new Uint32Array(1);
|
|
15
|
+
(window.crypto || window.msCrypto).getRandomValues(array);
|
|
16
|
+
return array[0] / 0x100000000;
|
|
17
|
+
}
|
|
18
|
+
return Math.random();
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
function cyrb53(str, seed = 0) {
|
|
13
22
|
const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
|
|
14
23
|
let h1 = 0xdeadbeef ^ seed,
|
|
@@ -269,15 +278,17 @@ export async function solveCpuTarget(message, target) {
|
|
|
269
278
|
*/
|
|
270
279
|
export async function solveMemory(seed, difficulty) {
|
|
271
280
|
const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
|
|
281
|
+
let solution;
|
|
282
|
+
let useWasm = false;
|
|
272
283
|
if (wasmModule && typeof wasmModule._solve_memory_challenge === 'function') {
|
|
273
284
|
const encoder = new TextEncoder();
|
|
274
285
|
const seedBytes = encoder.encode(seed);
|
|
275
286
|
const ptr = wasmModule._malloc(seedBytes.length + 1);
|
|
276
287
|
wasmModule.HEAPU8.set(seedBytes, ptr);
|
|
277
288
|
wasmModule.HEAPU8[ptr + seedBytes.length] = 0; // Null-terminator
|
|
278
|
-
|
|
289
|
+
solution = wasmModule._solve_memory_challenge(ptr, difficulty);
|
|
279
290
|
wasmModule._free(ptr);
|
|
280
|
-
|
|
291
|
+
useWasm = true;
|
|
281
292
|
}
|
|
282
293
|
|
|
283
294
|
const size = difficulty * 1024 * 1024;
|
|
@@ -325,19 +336,21 @@ export async function solveMemory(seed, difficulty) {
|
|
|
325
336
|
}
|
|
326
337
|
const merkleRoot = tree[tree.length - 1][0];
|
|
327
338
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
339
|
+
if (!useWasm) {
|
|
340
|
+
function readBuffer(blocks, addr) {
|
|
341
|
+
const blockIdx = Math.floor(addr / 1024);
|
|
342
|
+
const elementIdx = addr % 1024;
|
|
343
|
+
return blocks[blockIdx][elementIdx];
|
|
344
|
+
}
|
|
333
345
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
346
|
+
const totalElements = numBlocks * 1024;
|
|
347
|
+
let addr = totalElements > 0 ? readBuffer(blocks, 0) % totalElements : 0;
|
|
348
|
+
solution = 0;
|
|
349
|
+
const iterations = 1024;
|
|
350
|
+
for (let i = 0; i < iterations; i++) {
|
|
351
|
+
addr = readBuffer(blocks, addr) % totalElements;
|
|
352
|
+
solution ^= addr;
|
|
353
|
+
}
|
|
341
354
|
}
|
|
342
355
|
|
|
343
356
|
const challengedIndices = [];
|
|
@@ -430,6 +443,81 @@ export async function solveTsp(cities, targetMaxDistance) {
|
|
|
430
443
|
|
|
431
444
|
// --- Fonctions d'optimisation copiées/adaptées de library.js pour le client ---
|
|
432
445
|
|
|
446
|
+
function paretoDominates(objectivesA, objectivesB) {
|
|
447
|
+
let aIsBetterInOne = false;
|
|
448
|
+
for (let i = 0; i < objectivesA.length; i++) {
|
|
449
|
+
if (objectivesA[i] > objectivesB[i]) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
if (objectivesA[i] < objectivesB[i]) {
|
|
453
|
+
aIsBetterInOne = true;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return aIsBetterInOne;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function nonDominatedSort(populationWithObjectives) {
|
|
460
|
+
const fronts = [[]];
|
|
461
|
+
for (const p1 of populationWithObjectives) {
|
|
462
|
+
p1.dominationCount = 0;
|
|
463
|
+
p1.dominatedSolutions = [];
|
|
464
|
+
for (const p2 of populationWithObjectives) {
|
|
465
|
+
if (p1 === p2) continue;
|
|
466
|
+
if (paretoDominates(p1.objectives, p2.objectives)) {
|
|
467
|
+
p1.dominatedSolutions.push(p2);
|
|
468
|
+
} else if (paretoDominates(p2.objectives, p1.objectives)) {
|
|
469
|
+
p1.dominationCount++;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (p1.dominationCount === 0) {
|
|
473
|
+
p1.rank = 0;
|
|
474
|
+
fronts[0].push(p1);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
let i = 0;
|
|
479
|
+
while (fronts[i] && fronts[i].length > 0) {
|
|
480
|
+
const nextFront = [];
|
|
481
|
+
for (const p1 of fronts[i]) {
|
|
482
|
+
for (const p2 of p1.dominatedSolutions) {
|
|
483
|
+
p2.dominationCount--;
|
|
484
|
+
if (p2.dominationCount === 0) {
|
|
485
|
+
p2.rank = i + 1;
|
|
486
|
+
nextFront.push(p2);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
i++;
|
|
491
|
+
if (nextFront.length > 0) {
|
|
492
|
+
fronts[i] = nextFront;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return fronts;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function calculateCrowdingDistance(front) {
|
|
499
|
+
if (front.length === 0) return;
|
|
500
|
+
front.forEach((p) => (p.crowdingDistance = 0));
|
|
501
|
+
const numObjectives = front[0].objectives.length;
|
|
502
|
+
|
|
503
|
+
for (let i = 0; i < numObjectives; i++) {
|
|
504
|
+
front.sort((a, b) => a.objectives[i] - b.objectives[i]);
|
|
505
|
+
const minObj = front[0].objectives[i];
|
|
506
|
+
const maxObj = front[front.length - 1].objectives[i];
|
|
507
|
+
|
|
508
|
+
front[0].crowdingDistance = Infinity;
|
|
509
|
+
front[front.length - 1].crowdingDistance = Infinity;
|
|
510
|
+
|
|
511
|
+
if (maxObj === minObj) continue;
|
|
512
|
+
|
|
513
|
+
for (let j = 1; j < front.length - 1; j++) {
|
|
514
|
+
front[j].crowdingDistance +=
|
|
515
|
+
(front[j + 1].objectives[i] - front[j - 1].objectives[i]) /
|
|
516
|
+
(maxObj - minObj);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
433
521
|
const ClientOptimizers = {
|
|
434
522
|
simulatedAnnealing(initialSolution, evaluator, neighbor, iterations, temp, cooling) {
|
|
435
523
|
let currentSolution = initialSolution;
|
|
@@ -439,7 +527,7 @@ const ClientOptimizers = {
|
|
|
439
527
|
for (let i = 0; i < iterations; i++) {
|
|
440
528
|
const newSolution = neighbor(currentSolution);
|
|
441
529
|
const newEnergy = evaluator(newSolution);
|
|
442
|
-
if (newEnergy < currentEnergy ||
|
|
530
|
+
if (newEnergy < currentEnergy || secureRandom() < Math.exp((currentEnergy - newEnergy) / temperature)) {
|
|
443
531
|
currentSolution = newSolution;
|
|
444
532
|
currentEnergy = newEnergy;
|
|
445
533
|
}
|
|
@@ -458,15 +546,245 @@ const ClientOptimizers = {
|
|
|
458
546
|
population.sort((a, b) => a.fitness - b.fitness);
|
|
459
547
|
const newPopulation = [population[0]]; // Elitism
|
|
460
548
|
while (newPopulation.length < popSize) {
|
|
461
|
-
const p1 = population[Math.floor(
|
|
462
|
-
const p2 = population[Math.floor(
|
|
549
|
+
const p1 = population[Math.floor(secureRandom() * (popSize / 2))];
|
|
550
|
+
const p2 = population[Math.floor(secureRandom() * (popSize / 2))];
|
|
463
551
|
let offspring = crossover(p1.chromosome, p2.chromosome);
|
|
464
|
-
if (
|
|
552
|
+
if (secureRandom() < 0.1) offspring = mutate(offspring);
|
|
465
553
|
newPopulation.push({ chromosome: offspring, fitness: fitness(offspring) });
|
|
466
554
|
}
|
|
467
555
|
population = newPopulation;
|
|
468
556
|
}
|
|
469
557
|
return population;
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
geneticAlgorithmMultiObjective(createIndividual, fitnessFunction, crossover, mutate, options = {}) {
|
|
561
|
+
const generations = options.generations || 50;
|
|
562
|
+
const populationSize = options.populationSize || 40;
|
|
563
|
+
const mutationRate = options.mutationRate !== undefined ? options.mutationRate : 0.1;
|
|
564
|
+
|
|
565
|
+
let population = Array.from({ length: populationSize }, () => ({
|
|
566
|
+
individual: createIndividual(),
|
|
567
|
+
}));
|
|
568
|
+
population.forEach((p) => (p.objectives = fitnessFunction(p.individual)));
|
|
569
|
+
|
|
570
|
+
for (let gen = 0; gen < generations; gen++) {
|
|
571
|
+
const offspring = [];
|
|
572
|
+
for (let i = 0; i < populationSize; i++) {
|
|
573
|
+
const parent1 = population[Math.floor(secureRandom() * population.length)];
|
|
574
|
+
const parent2 = population[Math.floor(secureRandom() * population.length)];
|
|
575
|
+
let childIndividual = crossover(parent1.individual, parent2.individual);
|
|
576
|
+
if (secureRandom() < mutationRate) {
|
|
577
|
+
childIndividual = mutate(childIndividual);
|
|
578
|
+
}
|
|
579
|
+
const child = { individual: childIndividual };
|
|
580
|
+
child.objectives = fitnessFunction(child.individual);
|
|
581
|
+
offspring.push(child);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const combinedPopulation = [...population, ...offspring];
|
|
585
|
+
const fronts = nonDominatedSort(combinedPopulation);
|
|
586
|
+
const newPopulation = [];
|
|
587
|
+
for (const front of fronts) {
|
|
588
|
+
if (newPopulation.length + front.length <= populationSize) {
|
|
589
|
+
newPopulation.push(...front);
|
|
590
|
+
} else {
|
|
591
|
+
calculateCrowdingDistance(front);
|
|
592
|
+
front.sort((a, b) => b.crowdingDistance - a.crowdingDistance);
|
|
593
|
+
const remaining = populationSize - newPopulation.length;
|
|
594
|
+
newPopulation.push(...front.slice(0, remaining));
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
population = newPopulation;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const finalFronts = nonDominatedSort(population);
|
|
602
|
+
const bestFront = finalFronts.length > 0 ? finalFronts[0] : [];
|
|
603
|
+
const uniqueSolutionsMap = new Map();
|
|
604
|
+
for (const p of bestFront) {
|
|
605
|
+
const key = JSON.stringify(p.objectives);
|
|
606
|
+
if (!uniqueSolutionsMap.has(key)) {
|
|
607
|
+
uniqueSolutionsMap.set(key, {
|
|
608
|
+
solution: p.individual,
|
|
609
|
+
objectives: p.objectives,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return Array.from(uniqueSolutionsMap.values());
|
|
614
|
+
},
|
|
615
|
+
|
|
616
|
+
'cpc.solve'(context, options = {}) {
|
|
617
|
+
const optimalBaseCommission = context.platformParams ? context.platformParams.optimalBaseCommission : 0.3;
|
|
618
|
+
const optimalBonusFactor = context.platformParams ? context.platformParams.optimalBonusFactor : 0.1;
|
|
619
|
+
const websiteQualityScore = ((context.website && context.website.relevanceScore) || 50) / 100;
|
|
620
|
+
const effectiveCommissionRate = Math.max(0, optimalBaseCommission - websiteQualityScore * optimalBonusFactor);
|
|
621
|
+
|
|
622
|
+
const advertiserDemand = (cpc) => {
|
|
623
|
+
if (cpc <= 0) return Infinity;
|
|
624
|
+
return (context.advertiser.credits || 0) / cpc;
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
const supply = context.estimatedImpressions || 1;
|
|
628
|
+
const competingAdsCount = context.competingAds ? context.competingAds.length : 0;
|
|
629
|
+
const competitionFactor = Math.min(2.5, 1 + competingAdsCount * 0.1);
|
|
630
|
+
|
|
631
|
+
const fitnessFunction = (cpcMultiplier) => {
|
|
632
|
+
const adjustedCPC = 1.0 * cpcMultiplier * competitionFactor;
|
|
633
|
+
if (adjustedCPC < 0.1) return [Infinity, Infinity, Infinity];
|
|
634
|
+
|
|
635
|
+
const demand = advertiserDemand(adjustedCPC);
|
|
636
|
+
const estimatedClicks = Math.min(demand, supply);
|
|
637
|
+
const platformRevenue = estimatedClicks * adjustedCPC * effectiveCommissionRate;
|
|
638
|
+
const advertiserValue = estimatedClicks;
|
|
639
|
+
const marketImbalance = Math.abs(demand - supply);
|
|
640
|
+
|
|
641
|
+
return [-platformRevenue, -advertiserValue, marketImbalance];
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
const createIndividual = () => 0.5 + secureRandom() * 4.5;
|
|
645
|
+
const crossover = (cpc1, cpc2) => (cpc1 + cpc2) / 2;
|
|
646
|
+
const mutate = (cpc) => Math.max(0.1, cpc + (secureRandom() - 0.5) * 0.5);
|
|
647
|
+
|
|
648
|
+
const gaOptions = {
|
|
649
|
+
generations: 50,
|
|
650
|
+
populationSize: 40,
|
|
651
|
+
...options,
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
const paretoFront = ClientOptimizers.geneticAlgorithmMultiObjective(
|
|
655
|
+
createIndividual,
|
|
656
|
+
fitnessFunction,
|
|
657
|
+
crossover,
|
|
658
|
+
mutate,
|
|
659
|
+
gaOptions
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
return {
|
|
663
|
+
paretoFront: paretoFront.map((result) => {
|
|
664
|
+
const cpcMultiplier = result.solution;
|
|
665
|
+
const finalCpc = Math.max(0.1, 1.0 * cpcMultiplier * competitionFactor);
|
|
666
|
+
return {
|
|
667
|
+
...result,
|
|
668
|
+
solution: finalCpc,
|
|
669
|
+
};
|
|
670
|
+
})
|
|
671
|
+
};
|
|
672
|
+
},
|
|
673
|
+
|
|
674
|
+
'fraud.solve'(context, options = {}) {
|
|
675
|
+
const legitimateClicks = context.legitimateClicks || [];
|
|
676
|
+
const fraudulentClicks = context.fraudulentClicks || [];
|
|
677
|
+
|
|
678
|
+
const fitnessFunction = (solution) => {
|
|
679
|
+
const [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents] = solution;
|
|
680
|
+
if (
|
|
681
|
+
minTimeToClick < 100 ||
|
|
682
|
+
minTimeToClick > 5000 ||
|
|
683
|
+
maxClickVariance < 1 ||
|
|
684
|
+
maxClickVariance > 10000 ||
|
|
685
|
+
minMouseEntropy < 0 ||
|
|
686
|
+
minMouseEntropy > 1 ||
|
|
687
|
+
minScrollEvents < 0
|
|
688
|
+
) {
|
|
689
|
+
return [Infinity, Infinity];
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const calculateClickVariance = (clicks) => {
|
|
693
|
+
if (!clicks || clicks.length < 2) return 0;
|
|
694
|
+
const meanX = clicks.reduce((sum, c) => sum + c.clickX, 0) / clicks.length;
|
|
695
|
+
const meanY = clicks.reduce((sum, c) => sum + c.clickY, 0) / clicks.length;
|
|
696
|
+
return clicks.reduce((sum, c) => sum + Math.pow(c.clickX - meanX, 2) + Math.pow(c.clickY - meanY, 2), 0) / clicks.length;
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
const getClicksByFingerprint = (clickData) => {
|
|
700
|
+
const grouped = {};
|
|
701
|
+
for (const click of clickData) {
|
|
702
|
+
if (!grouped[click.fingerprint]) grouped[click.fingerprint] = [];
|
|
703
|
+
grouped[click.fingerprint].push(click);
|
|
704
|
+
}
|
|
705
|
+
return grouped;
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
const legitimateGroups = getClicksByFingerprint(legitimateClicks);
|
|
709
|
+
const fraudulentGroups = getClicksByFingerprint(fraudulentClicks);
|
|
710
|
+
|
|
711
|
+
let truePositives = 0;
|
|
712
|
+
let falsePositives = 0;
|
|
713
|
+
|
|
714
|
+
for (const fingerprint in fraudulentGroups) {
|
|
715
|
+
const clicks = fraudulentGroups[fingerprint];
|
|
716
|
+
if (!clicks) continue;
|
|
717
|
+
const variance = calculateClickVariance(clicks);
|
|
718
|
+
const isTooFast = clicks.some((c) => c.timeToClick < minTimeToClick);
|
|
719
|
+
const isTooUniform = variance < maxClickVariance;
|
|
720
|
+
const hasLowEntropy = clicks.some((c) => c.mouseEntropy < minMouseEntropy);
|
|
721
|
+
const hasFewScrolls = clicks.some((c) => c.scrollEvents < minScrollEvents);
|
|
722
|
+
|
|
723
|
+
if (isTooFast || isTooUniform || hasLowEntropy || hasFewScrolls) {
|
|
724
|
+
truePositives++;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
for (const fingerprint in legitimateGroups) {
|
|
729
|
+
const clicks = legitimateGroups[fingerprint];
|
|
730
|
+
if (!clicks) continue;
|
|
731
|
+
const variance = calculateClickVariance(clicks);
|
|
732
|
+
if (
|
|
733
|
+
clicks.some((c) => c.timeToClick < minTimeToClick || c.mouseEntropy < minMouseEntropy || c.scrollEvents < minScrollEvents) ||
|
|
734
|
+
variance < maxClickVariance
|
|
735
|
+
) {
|
|
736
|
+
falsePositives++;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const totalFraudulent = Object.keys(fraudulentGroups).length || 1;
|
|
741
|
+
const totalLegitimate = Object.keys(legitimateGroups).length || 1;
|
|
742
|
+
|
|
743
|
+
const objective1 = 1 - truePositives / totalFraudulent;
|
|
744
|
+
const objective2 = falsePositives / totalLegitimate;
|
|
745
|
+
|
|
746
|
+
return [objective1, objective2];
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
const createIndividual = () => [
|
|
750
|
+
100 + secureRandom() * 4900,
|
|
751
|
+
1 + secureRandom() * 9999,
|
|
752
|
+
secureRandom() * 0.5,
|
|
753
|
+
Math.floor(secureRandom() * 10)
|
|
754
|
+
];
|
|
755
|
+
|
|
756
|
+
const crossover = (s1, s2) => [
|
|
757
|
+
(s1[0] + s2[0]) / 2,
|
|
758
|
+
(s1[1] + s2[1]) / 2,
|
|
759
|
+
(s1[2] + s2[2]) / 2,
|
|
760
|
+
(s1[3] + s2[3]) / 2,
|
|
761
|
+
];
|
|
762
|
+
|
|
763
|
+
const mutate = (solution) => {
|
|
764
|
+
const newSolution = [...solution];
|
|
765
|
+
const i = Math.floor(Math.random() * 4);
|
|
766
|
+
const mutationFactors = [500, 1000, 0.1, 2];
|
|
767
|
+
newSolution[i] += (secureRandom() - 0.5) * mutationFactors[i];
|
|
768
|
+
return newSolution;
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
const gaOptions = {
|
|
772
|
+
generations: 80,
|
|
773
|
+
populationSize: 60,
|
|
774
|
+
...options,
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
const paretoFront = ClientOptimizers.geneticAlgorithmMultiObjective(
|
|
778
|
+
createIndividual,
|
|
779
|
+
fitnessFunction,
|
|
780
|
+
crossover,
|
|
781
|
+
mutate,
|
|
782
|
+
gaOptions
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
return {
|
|
786
|
+
paretoFront
|
|
787
|
+
};
|
|
470
788
|
}
|
|
471
789
|
};
|
|
472
790
|
|
|
@@ -500,16 +818,16 @@ async function solveUsefulWorkTask(task) {
|
|
|
500
818
|
};
|
|
501
819
|
const neighbor = (facilities) => {
|
|
502
820
|
const newFacilities = facilities.map((f) => ({ ...f }));
|
|
503
|
-
const i = Math.floor(
|
|
504
|
-
const moveX = (
|
|
505
|
-
const moveY = (
|
|
821
|
+
const i = Math.floor(secureRandom() * numFacilities);
|
|
822
|
+
const moveX = (secureRandom() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
|
|
823
|
+
const moveY = (secureRandom() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
|
|
506
824
|
newFacilities[i].x = Math.max(bounds.minX, Math.min(bounds.maxX, newFacilities[i].x + moveX));
|
|
507
825
|
newFacilities[i].y = Math.max(bounds.minY, Math.min(bounds.maxY, newFacilities[i].y + moveY));
|
|
508
826
|
return newFacilities;
|
|
509
827
|
};
|
|
510
828
|
const initialSolution = task.initialSolution || Array.from({ length: numFacilities }, () => ({
|
|
511
|
-
x: bounds.minX +
|
|
512
|
-
y: bounds.minY +
|
|
829
|
+
x: bounds.minX + secureRandom() * (bounds.maxX - bounds.minX),
|
|
830
|
+
y: bounds.minY + secureRandom() * (bounds.maxY - bounds.minY),
|
|
513
831
|
}));
|
|
514
832
|
return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
|
|
515
833
|
}
|
|
@@ -524,11 +842,11 @@ async function solveUsefulWorkTask(task) {
|
|
|
524
842
|
};
|
|
525
843
|
const neighbor = (path) => {
|
|
526
844
|
const newPath = [...path];
|
|
527
|
-
const [i, j] = [Math.floor(
|
|
845
|
+
const [i, j] = [Math.floor(secureRandom() * path.length), Math.floor(secureRandom() * path.length)];
|
|
528
846
|
[newPath[i], newPath[j]] = [newPath[j], newPath[i]];
|
|
529
847
|
return newPath;
|
|
530
848
|
};
|
|
531
|
-
const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 -
|
|
849
|
+
const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - secureRandom());
|
|
532
850
|
|
|
533
851
|
return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
|
|
534
852
|
}
|
|
@@ -544,9 +862,9 @@ async function solveUsefulWorkTask(task) {
|
|
|
544
862
|
if (vol > maxVolatility) return 1000 + (vol - maxVolatility);
|
|
545
863
|
return -ret;
|
|
546
864
|
};
|
|
547
|
-
const createIndividual = () => Array.from({ length: assets.length },
|
|
865
|
+
const createIndividual = () => Array.from({ length: assets.length }, secureRandom);
|
|
548
866
|
const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
|
|
549
|
-
const mutate = p => { const n = [...p], i = Math.floor(
|
|
867
|
+
const mutate = p => { const n = [...p], i = Math.floor(secureRandom() * n.length); n[i] += (secureRandom() - 0.5) * 0.2; return n.map(v => Math.max(0, v)); };
|
|
550
868
|
|
|
551
869
|
// Le client doit recréer la population si elle n'est pas fournie
|
|
552
870
|
const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
|
|
@@ -598,8 +916,8 @@ export async function solveOptimizationTask(initialPopulation, generations) {
|
|
|
598
916
|
const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
|
|
599
917
|
const mutate = (p) => {
|
|
600
918
|
const newP = [...p];
|
|
601
|
-
const i = Math.floor(
|
|
602
|
-
newP[i] += (
|
|
919
|
+
const i = Math.floor(secureRandom() * newP.length);
|
|
920
|
+
newP[i] += (secureRandom() - 0.5) * 0.2;
|
|
603
921
|
return newP;
|
|
604
922
|
};
|
|
605
923
|
|
|
@@ -611,10 +929,10 @@ export async function solveOptimizationTask(initialPopulation, generations) {
|
|
|
611
929
|
const newPopulation = [...parents]; // Élitisme
|
|
612
930
|
|
|
613
931
|
while (newPopulation.length < population.length) {
|
|
614
|
-
const parent1 = parents[Math.floor(
|
|
615
|
-
const parent2 = parents[Math.floor(
|
|
932
|
+
const parent1 = parents[Math.floor(secureRandom() * parents.length)];
|
|
933
|
+
const parent2 = parents[Math.floor(secureRandom() * parents.length)];
|
|
616
934
|
let offspring = crossover(parent1.chromosome, parent2.chromosome);
|
|
617
|
-
if (
|
|
935
|
+
if (secureRandom() < 0.1) offspring = mutate(offspring);
|
|
618
936
|
// La fitness sera recalculée côté serveur pour la vérification.
|
|
619
937
|
newPopulation.push({ chromosome: offspring, fitness: -1 });
|
|
620
938
|
}
|
|
@@ -586,7 +586,7 @@ let managerPromise = null;
|
|
|
586
586
|
* @returns {Promise<ProblemManager>} The singleton instance.
|
|
587
587
|
*/
|
|
588
588
|
export function getProblemManager(options = {}, store) {
|
|
589
|
-
const defaultPath = join(__dirname, '..', '..', 'problems.config.json');
|
|
589
|
+
const defaultPath = join(__dirname, '..', '..', 'config', 'problems.config.json');
|
|
590
590
|
const { configPath = defaultPath, config } = options;
|
|
591
591
|
|
|
592
592
|
const hasConfigChanged = problemManagerInstance && (
|
package/src/js/redis-store.js
CHANGED
|
@@ -1,43 +1,115 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Creates a store adapter for ioredis.
|
|
3
|
-
* This adapter handles the serialization and deserialization of complex objects,
|
|
4
|
-
* including the conversion of Set objects to arrays for storage in Redis.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Creates a store adapter for an ioredis client.
|
|
9
|
-
* @param {import('ioredis').Redis} redisClient - An instance of the ioredis client.
|
|
10
|
-
* @returns {import('./fingerprint.js').IStore} An object that complies with the IStore interface.
|
|
11
|
-
*/
|
|
12
|
-
export function createRedisStore(redisClient) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* @file Creates a store adapter for ioredis.
|
|
3
|
+
* This adapter handles the serialization and deserialization of complex objects,
|
|
4
|
+
* including the conversion of Set objects to arrays for storage in Redis.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a store adapter for an ioredis client.
|
|
9
|
+
* @param {import('ioredis').Redis} redisClient - An instance of the ioredis client.
|
|
10
|
+
* @returns {import('./fingerprint.js').IStore} An object that complies with the IStore interface.
|
|
11
|
+
*/
|
|
12
|
+
export function createRedisStore(redisClient) {
|
|
13
|
+
const replacer = (k, v) => (v instanceof Set ? Array.from(v) : v);
|
|
14
|
+
const reviver = (k, v) => (k === 'ips' && Array.isArray(v) ? new Set(v) : v);
|
|
15
|
+
|
|
16
|
+
const localStore = new Map();
|
|
17
|
+
const localTimeouts = new Map();
|
|
18
|
+
let isDown = false;
|
|
19
|
+
let reconnecting = false;
|
|
20
|
+
|
|
21
|
+
const triggerFallback = () => {
|
|
22
|
+
if (!isDown) {
|
|
23
|
+
isDown = true;
|
|
24
|
+
attemptReconnection();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const attemptReconnection = () => {
|
|
29
|
+
if (reconnecting) return;
|
|
30
|
+
reconnecting = true;
|
|
31
|
+
const interval = setInterval(async () => {
|
|
32
|
+
try {
|
|
33
|
+
await redisClient.ping();
|
|
34
|
+
isDown = false;
|
|
35
|
+
reconnecting = false;
|
|
36
|
+
clearInterval(interval);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
// Continue de tenter la reconnexion toutes les 5 secondes
|
|
39
|
+
}
|
|
40
|
+
}, 5000);
|
|
41
|
+
if (interval.unref) interval.unref();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const setLocal = (key, value, ttl) => {
|
|
45
|
+
localStore.set(key, value);
|
|
46
|
+
if (localTimeouts.has(key)) {
|
|
47
|
+
clearTimeout(localTimeouts.get(key));
|
|
48
|
+
}
|
|
49
|
+
if (ttl && ttl > 0) {
|
|
50
|
+
const timeout = setTimeout(() => {
|
|
51
|
+
localStore.delete(key);
|
|
52
|
+
localTimeouts.delete(key);
|
|
53
|
+
}, ttl * 1000);
|
|
54
|
+
if (timeout.unref) timeout.unref();
|
|
55
|
+
localTimeouts.set(key, timeout);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
async get(key) {
|
|
61
|
+
if (isDown) {
|
|
62
|
+
return localStore.get(key) || null;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const value = await redisClient.get(key);
|
|
66
|
+
if (!value) return null;
|
|
67
|
+
return JSON.parse(value, reviver);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
triggerFallback();
|
|
70
|
+
return localStore.get(key) || null;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
async set(key, value, ttl) {
|
|
74
|
+
if (isDown) {
|
|
75
|
+
setLocal(key, value, ttl);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const stringValue = JSON.stringify(value, replacer);
|
|
80
|
+
if (ttl && ttl > 0) {
|
|
81
|
+
await redisClient.set(key, stringValue, 'EX', ttl);
|
|
82
|
+
} else {
|
|
83
|
+
await redisClient.set(key, stringValue);
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
triggerFallback();
|
|
87
|
+
setLocal(key, value, ttl);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
async has(key) {
|
|
91
|
+
if (isDown) {
|
|
92
|
+
return localStore.has(key);
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
return (await redisClient.exists(key)) === 1;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
triggerFallback();
|
|
98
|
+
return localStore.has(key);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
async delete(key) {
|
|
102
|
+
if (localTimeouts.has(key)) {
|
|
103
|
+
clearTimeout(localTimeouts.get(key));
|
|
104
|
+
localTimeouts.delete(key);
|
|
105
|
+
}
|
|
106
|
+
localStore.delete(key);
|
|
107
|
+
if (isDown) return;
|
|
108
|
+
try {
|
|
109
|
+
await redisClient.del(key);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
triggerFallback();
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
};
|
|
43
115
|
}
|