cvsa/packages/next/app/[locale]/test-vdf/TestCard.tsx

207 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { N_ARRAY } from "@/lib/const";
import { UAParser } from "ua-parser-js";
import { useEffect, useState } from "react";
import { computeVdfInWorker } from "@/lib/vdf";
import { FilledButton } from "@/components/ui/Buttons/FilledButton";
let bigintSupported = typeof BigInt !== "undefined";
function generateRandomBigInt(min: bigint, max: bigint) {
const range = max - min;
const bitLength = range.toString(2).length;
const byteLength = Math.ceil(bitLength / 8);
const mask = (1n << BigInt(bitLength)) - 1n;
let result;
do {
const randomBytes = new Uint8Array(byteLength);
crypto.getRandomValues(randomBytes);
result = 0n;
for (let i = 0; i < byteLength; i++) {
result = (result << 8n) | BigInt(randomBytes[i]);
}
result = result & mask;
} while (result > range);
return min + result;
}
function generateValidG(N: bigint) {
if (N <= 4n) throw new Error("N must be > 4");
while (true) {
const r = generateRandomBigInt(2n, N - 1n);
const g = (r * r) % N;
if (g !== 1n && g !== 0n && g !== N - 1n) {
return g;
}
}
}
export const VDFtestCard = () => {
const [browserInfo, setBrowserInfo] = useState<string | null>(null);
const [isBenchmarking, setIsBenchmarking] = useState(false);
const [benchmarkResults, setBenchmarkResults] = useState<{ N: bigint; difficulty: bigint; time: number }[]>([]);
const [currentProgress, setCurrentProgress] = useState(0);
const [currentN, setCurrentN] = useState<bigint | null>(null);
const [currentDifficulty, setCurrentDifficulty] = useState<bigint | null>(null);
const [currentTestIndex, setCurrentTestIndex] = useState(0);
const difficulties = [BigInt(20000), BigInt(200000)];
const [testCombinations, setTestCombinations] = useState<{ N: bigint; difficulty: bigint }[]>([]);
const speedSampleIndex = 1;
const [speedSample, setSpeedSample] = useState<{ N: bigint; difficulty: bigint; time: number } | undefined>(
undefined
);
useEffect(() => {
// 创建需要测试的 N 和难度的组合
const combinations: { N: bigint; difficulty: bigint }[] = [];
N_ARRAY.forEach((n) => {
difficulties.forEach((difficulty) => {
combinations.push({ N: n, difficulty });
});
});
setTestCombinations(combinations);
const ua = navigator ? navigator.userAgent : "";
const { browser } = UAParser(ua);
setBrowserInfo(browser.name + " " + browser.version);
}, []);
async function startBenchmark() {
if (testCombinations.length === 0) {
alert("No N values provided in src/const N_ARRAY.");
return;
}
setIsBenchmarking(true);
setBenchmarkResults([]);
setCurrentTestIndex(0);
async function runTest(index: number) {
if (index >= testCombinations.length) {
setIsBenchmarking(false);
setCurrentN(null);
setCurrentDifficulty(null);
return;
}
const { N, difficulty } = testCombinations[index];
const g = generateValidG(N);
try {
const { time } = await computeVdfInWorker(g, N, difficulty, (progress) => {
setCurrentProgress(progress);
setCurrentN(N);
setCurrentDifficulty(difficulty);
});
setBenchmarkResults((prevResults) => [...prevResults, { N, difficulty, time }]);
setCurrentProgress(0);
setCurrentTestIndex((prevIndex) => prevIndex + 1);
runTest(index + 1);
} catch (error) {
setIsBenchmarking(false);
setCurrentN(null);
setCurrentDifficulty(null);
}
}
runTest(0);
}
function getAccumulatedTime() {
return benchmarkResults.reduce((acc, result) => acc + result.time, 0);
}
function calculateSpeed() {
const sample = benchmarkResults[speedSampleIndex];
if (!sample) return 0;
return (Number(sample.difficulty) / sample.time) * 1000;
}
useEffect(() => {
if (benchmarkResults.length > speedSampleIndex) {
setSpeedSample(benchmarkResults[speedSampleIndex]);
}
}, [benchmarkResults]);
return (
<div className="relative mt-8 mb-12 h-auto duration-300">
<h2 className="text-2xl font-medium mb-5">VDF </h2>
{!bigintSupported ? (
<p className="text-red-500 dark:text-red-400"> BigInt</p>
) : !isBenchmarking ? (
<FilledButton onClick={startBenchmark} disabled={!bigintSupported} shape="square">
</FilledButton>
) : null}
{isBenchmarking && (
<>
<p className="mb-8">
: {currentTestIndex + 1}/{testCombinations.length}
</p>
{currentN !== null && currentDifficulty !== null && (
<>
<p className="mb-2">: {currentN.toString(2).length} </p>
<p className="mb-2">: {currentDifficulty.toLocaleString()}</p>
<div className="w-full rounded-full h-1 relative overflow-hidden">
<div
className="bg-primary dark:bg-dark-primary h-full rounded-full absolute"
style={{ width: `${currentProgress}%` }}
></div>
<div
className="bg-secondary-container dark:bg-dark-secondary-container h-full rounded-full absolute right-0"
style={{ width: `calc(${100 - currentProgress}% - 0.25rem)` }}
></div>
<div className="bg-primary dark:bg-dark-primary h-full w-1 rounded-full absolute right-0"></div>
</div>
</>
)}
</>
)}
{benchmarkResults.length > 0 && !isBenchmarking && (
<>
<h3 className="text-lg font-medium mt-4 mb-2"></h3>
<p className="mb-4 text-sm">
{(getAccumulatedTime() / 1000).toFixed(3)} . <br />
: {Math.round(calculateSpeed()).toLocaleString()} / . <br />
<span className="text-sm text-on-surface-variant dark:text-dark-on-surface-variant">
N = {speedSample?.N.toString(2).length} bits, T = {speedSample?.difficulty}{" "}
.
</span>
<br />
{browserInfo && <>{browserInfo}</>}
</p>
<table className="w-full text-sm text-left rtl:text-right mt-4">
<thead className="text-sm uppercase font-medium border-b border-outline dark:border-dark-outline">
<tr>
<th scope="col" className="px-6 py-3">
(ms)
</th>
<th scope="col" className="px-6 py-3">
N (bits)
</th>
<th scope="col" className="px-6 py-3">
T ()
</th>
</tr>
</thead>
<tbody>
{benchmarkResults.map((result) => (
<tr
key={`${result.N}-${result.difficulty}-${result.time}`}
className="border-b border-outline-variant dark:border-dark-outline-variant"
>
<td className="px-6 py-4 whitespace-nowrap">{result.time.toFixed(2)}</td>
<td className="px-6 py-4 whitespace-nowrap">{result.N.toString(2).length}</td>
<td className="px-6 py-4 whitespace-nowrap">{Number(result.difficulty)}</td>
</tr>
))}
</tbody>
</table>
</>
)}
</div>
);
};