mirror of
https://github.com/smolgrrr/TAO.git
synced 2024-09-20 09:21:25 +00:00
Merge pull request #14 from smolgrrr/refactor-submit
Add remote PoW server
This commit is contained in:
commit
51927fefc4
@ -5,6 +5,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emoji-mart/data": "^1.1.2",
|
"@emoji-mart/data": "^1.1.2",
|
||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
|
"@headlessui/react": "latest",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
"@types/node": "^17.0.45",
|
"@types/node": "^17.0.45",
|
||||||
|
@ -11,37 +11,7 @@ import { publish } from "../../utils/relays";
|
|||||||
import { renderMedia, attachFile } from "../../utils/FileUpload";
|
import { renderMedia, attachFile } from "../../utils/FileUpload";
|
||||||
import { EmojiPicker } from "./Emojis/emoji-picker";
|
import { EmojiPicker } from "./Emojis/emoji-picker";
|
||||||
import customEmojis from './custom_emojis.json';
|
import customEmojis from './custom_emojis.json';
|
||||||
|
import { useSubmitForm } from "./handleSubmit";
|
||||||
const useWorkers = (numCores: number, unsigned: UnsignedEvent, difficulty: string, deps: any[]) => {
|
|
||||||
const [messageFromWorker, setMessageFromWorker] = useState(null);
|
|
||||||
const [doingWorkProgress, setDoingWorkProgress] = useState(0);
|
|
||||||
|
|
||||||
const startWork = () => {
|
|
||||||
const workers = Array(numCores).fill(null).map(() => new Worker(new URL("../../powWorker", import.meta.url)));
|
|
||||||
|
|
||||||
workers.forEach((worker, index) => {
|
|
||||||
worker.onmessage = (event) => {
|
|
||||||
if (event.data.status === 'progress') {
|
|
||||||
console.log(`Worker progress: Checked ${event.data.currentNonce} nonces.`);
|
|
||||||
setDoingWorkProgress(event.data.currentNonce);
|
|
||||||
} else if (event.data.found) {
|
|
||||||
setMessageFromWorker(event.data.event);
|
|
||||||
// Terminate all workers once a solution is found
|
|
||||||
workers.forEach(w => w.terminate());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
worker.postMessage({
|
|
||||||
unsigned,
|
|
||||||
difficulty,
|
|
||||||
nonceStart: index, // Each worker starts from its index
|
|
||||||
nonceStep: numCores // Each worker increments by the total number of workers
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return { startWork, messageFromWorker, doingWorkProgress };
|
|
||||||
};
|
|
||||||
|
|
||||||
interface FormProps {
|
interface FormProps {
|
||||||
refEvent?: NostrEvent;
|
refEvent?: NostrEvent;
|
||||||
@ -60,26 +30,18 @@ const NewNoteCard = ({
|
|||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
const [comment, setComment] = useState("");
|
const [comment, setComment] = useState("");
|
||||||
const [file, setFile] = useState("");
|
const [file, setFile] = useState("");
|
||||||
const [sk, setSk] = useState(generatePrivateKey());
|
|
||||||
const [unsigned, setUnsigned] = useState<UnsignedEvent>({
|
const [unsigned, setUnsigned] = useState<UnsignedEvent>({
|
||||||
kind: 1,
|
kind: 1,
|
||||||
tags: [],
|
tags: [],
|
||||||
content: "",
|
content: "",
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
pubkey: getPublicKey(sk),
|
pubkey: "",
|
||||||
});
|
});
|
||||||
const [difficulty, setDifficulty] = useState(
|
const [difficulty, setDifficulty] = useState(
|
||||||
localStorage.getItem("difficulty") || "21"
|
localStorage.getItem("difficulty") || "21"
|
||||||
);
|
);
|
||||||
const [fileSizeError, setFileSizeError] = useState(false);
|
const [fileSizeError, setFileSizeError] = useState(false);
|
||||||
|
|
||||||
const [uploadingFile, setUploadingFile] = useState(false);
|
const [uploadingFile, setUploadingFile] = useState(false);
|
||||||
const [doingWorkProp, setDoingWorkProp] = useState(false);
|
|
||||||
|
|
||||||
// Initialize the worker outside of any effects
|
|
||||||
const numCores = navigator.hardwareConcurrency || 4;
|
|
||||||
|
|
||||||
const { startWork, messageFromWorker, doingWorkProgress } = useWorkers(numCores, unsigned, difficulty, [unsigned]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (refEvent && tagType && unsigned.tags.length === 0) {
|
if (refEvent && tagType && unsigned.tags.length === 0) {
|
||||||
@ -111,31 +73,21 @@ const NewNoteCard = ({
|
|||||||
...prevUnsigned,
|
...prevUnsigned,
|
||||||
content: `${comment} ${file}`,
|
content: `${comment} ${file}`,
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
pubkey: getPublicKey(sk),
|
|
||||||
}));
|
}));
|
||||||
}, [comment, file]);
|
}, [comment, file]);
|
||||||
|
|
||||||
useEffect(() => {
|
const { handleSubmit: originalHandleSubmit, doingWorkProp, doingWorkProgress } = useSubmitForm(unsigned, difficulty);
|
||||||
setDoingWorkProp(false);
|
|
||||||
if (messageFromWorker) {
|
|
||||||
try {
|
|
||||||
const signedEvent = finishEvent(messageFromWorker, sk);
|
|
||||||
publish(signedEvent);
|
|
||||||
|
|
||||||
setComment("");
|
const handleSubmit = async (event: React.FormEvent) => {
|
||||||
setFile("");
|
await originalHandleSubmit(event);
|
||||||
setSk(generatePrivateKey());
|
setComment("");
|
||||||
setUnsigned(prevUnsigned => ({
|
setFile("");
|
||||||
...prevUnsigned,
|
setUnsigned(prevUnsigned => ({
|
||||||
content: '',
|
...prevUnsigned,
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
content: '',
|
||||||
pubkey: getPublicKey(sk),
|
created_at: Math.floor(Date.now() / 1000)
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
};
|
||||||
setComment(error + " " + comment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [messageFromWorker]);
|
|
||||||
|
|
||||||
//Emoji stuff
|
//Emoji stuff
|
||||||
const emojiRef = useRef(null);
|
const emojiRef = useRef(null);
|
||||||
@ -180,11 +132,7 @@ const NewNoteCard = ({
|
|||||||
method="post"
|
method="post"
|
||||||
encType="multipart/form-data"
|
encType="multipart/form-data"
|
||||||
className=""
|
className=""
|
||||||
onSubmit={(event) => {
|
onSubmit={handleSubmit}
|
||||||
event.preventDefault();
|
|
||||||
startWork();
|
|
||||||
setDoingWorkProp(true);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<input type="hidden" name="MAX_FILE_SIZE" defaultValue={2.5 * 1024 * 1024} />
|
<input type="hidden" name="MAX_FILE_SIZE" defaultValue={2.5 * 1024 * 1024} />
|
||||||
<div className="px-4 flex flex-col rounded-lg">
|
<div className="px-4 flex flex-col rounded-lg">
|
||||||
@ -276,8 +224,8 @@ const NewNoteCard = ({
|
|||||||
{doingWorkProp ? (
|
{doingWorkProp ? (
|
||||||
<div className="flex animate-pulse text-sm text-gray-300">
|
<div className="flex animate-pulse text-sm text-gray-300">
|
||||||
<CpuChipIcon className="h-4 w-4 ml-auto" />
|
<CpuChipIcon className="h-4 w-4 ml-auto" />
|
||||||
<span>Generating Proof-of-Work:</span>
|
<span>Generating Proof-of-Work.</span>
|
||||||
<span>iteration {doingWorkProgress}</span>
|
{doingWorkProgress && <span>Current iteration {doingWorkProgress}</span>}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div id="postFormError" className="text-red-500" />
|
<div id="postFormError" className="text-red-500" />
|
||||||
|
98
client/src/components/Forms/handleSubmit.ts
Normal file
98
client/src/components/Forms/handleSubmit.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { generatePrivateKey, getPublicKey, finishEvent, UnsignedEvent } from "nostr-tools";
|
||||||
|
import { publish } from "../../utils/relays";
|
||||||
|
|
||||||
|
const useWorkers = (numCores: number, unsigned: UnsignedEvent, difficulty: string, deps: any[]) => {
|
||||||
|
const [messageFromWorker, setMessageFromWorker] = useState(null);
|
||||||
|
const [doingWorkProgress, setDoingWorkProgress] = useState(0);
|
||||||
|
|
||||||
|
const startWork = () => {
|
||||||
|
const workers = Array(numCores).fill(null).map(() => new Worker(new URL("../../powWorker", import.meta.url)));
|
||||||
|
|
||||||
|
workers.forEach((worker, index) => {
|
||||||
|
worker.onmessage = (event) => {
|
||||||
|
if (event.data.status === 'progress') {
|
||||||
|
console.log(`Worker progress: Checked ${event.data.currentNonce} nonces.`);
|
||||||
|
setDoingWorkProgress(event.data.currentNonce);
|
||||||
|
} else if (event.data.found) {
|
||||||
|
setMessageFromWorker(event.data.event);
|
||||||
|
// Terminate all workers once a solution is found
|
||||||
|
workers.forEach(w => w.terminate());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
worker.postMessage({
|
||||||
|
unsigned,
|
||||||
|
difficulty,
|
||||||
|
nonceStart: index, // Each worker starts from its index
|
||||||
|
nonceStep: numCores // Each worker increments by the total number of workers
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return { startWork, messageFromWorker, doingWorkProgress };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSubmitForm = (unsigned: UnsignedEvent, difficulty: string) => {
|
||||||
|
const [doingWorkProp, setDoingWorkProp] = useState(false);
|
||||||
|
const [sk, setSk] = useState(generatePrivateKey());
|
||||||
|
const unsignedWithPubkey = { ...unsigned, pubkey: getPublicKey(sk) };
|
||||||
|
const powServer = useState(localStorage.getItem('powserver') || '');
|
||||||
|
const [unsignedPoWEvent, setUnsignedPoWEvent] = useState<UnsignedEvent>()
|
||||||
|
|
||||||
|
// Initialize the worker outside of any effects
|
||||||
|
const numCores = navigator.hardwareConcurrency || 4;
|
||||||
|
|
||||||
|
const { startWork, messageFromWorker, doingWorkProgress } = useWorkers(numCores, unsignedWithPubkey, difficulty, [unsignedWithPubkey]);
|
||||||
|
|
||||||
|
console.log(powServer[0])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (unsignedPoWEvent) {
|
||||||
|
setDoingWorkProp(false);
|
||||||
|
const signedEvent = finishEvent(unsignedPoWEvent, sk);
|
||||||
|
publish(signedEvent);
|
||||||
|
}
|
||||||
|
}, [unsignedPoWEvent]);
|
||||||
|
|
||||||
|
const handleSubmit = (event: React.FormEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setDoingWorkProp(true);
|
||||||
|
console.log(powServer[0])
|
||||||
|
if (powServer[0]) {
|
||||||
|
const inEventFormat = { ...unsignedWithPubkey, sig: "" };
|
||||||
|
const powRequest = {
|
||||||
|
req_event: inEventFormat,
|
||||||
|
difficulty: difficulty
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(`${powServer[0]}/powgen`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(powRequest)
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data);
|
||||||
|
// handle the response data
|
||||||
|
setUnsignedPoWEvent(data.event)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
startWork();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (messageFromWorker) {
|
||||||
|
setUnsignedPoWEvent(messageFromWorker);
|
||||||
|
}
|
||||||
|
}, [messageFromWorker]);
|
||||||
|
|
||||||
|
return { handleSubmit, doingWorkProp, doingWorkProgress };
|
||||||
|
};
|
@ -1,6 +1,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||||
import { ArrowUpOnSquareIcon, PlusCircleIcon } from '@heroicons/react/24/outline';
|
import { ArrowUpOnSquareIcon, PlusCircleIcon } from '@heroicons/react/24/outline';
|
||||||
|
import { Dialog, Transition } from '@headlessui/react';
|
||||||
|
import { Fragment } from 'react';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Navigator {
|
interface Navigator {
|
||||||
@ -44,7 +46,21 @@ const AddToHomeScreenPrompt: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overscroll-contain fixed inset-0 bg-gray-800/40 flex items-center justify-center animate-fade-in">
|
<Transition appear show={inMobileBrowser} as={Fragment}>
|
||||||
|
<Dialog
|
||||||
|
as="div"
|
||||||
|
className="fixed inset-0 z-10 overflow-y-auto"
|
||||||
|
onClose={() => setInMobileBrowser(false)}
|
||||||
|
>
|
||||||
|
<div className="min-h-screen px-4 text-center">
|
||||||
|
<Dialog.Overlay className="fixed inset-0 bg-gray-800 opacity-40" />
|
||||||
|
|
||||||
|
<span
|
||||||
|
className="inline-block h-screen align-middle"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
​
|
||||||
|
</span>
|
||||||
<div className="fixed bottom-0 left-0 right-0 p-4 bg-neutral-900 rounded-lg m-2 border border-neutral-700 shadow-md flex justify-between items-center animate-slide-up">
|
<div className="fixed bottom-0 left-0 right-0 p-4 bg-neutral-900 rounded-lg m-2 border border-neutral-700 shadow-md flex justify-between items-center animate-slide-up">
|
||||||
<div className="flex flex-col text-white">
|
<div className="flex flex-col text-white">
|
||||||
<span className="font-semibold">Stay Wired</span>
|
<span className="font-semibold">Stay Wired</span>
|
||||||
@ -65,8 +81,10 @@ const AddToHomeScreenPrompt: React.FC = () => {
|
|||||||
<button className="absolute top-2 right-2" onClick={() => {setInMobileBrowser(!inMobileBrowser);}}>
|
<button className="absolute top-2 right-2" onClick={() => {setInMobileBrowser(!inMobileBrowser);}}>
|
||||||
<XMarkIcon className="h-6 w-6 text-white" />
|
<XMarkIcon className="h-6 w-6 text-white" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,25 +1,61 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
// import {powEvent} from './system';
|
|
||||||
// import {publish} from './relays';
|
|
||||||
import { addRelay } from '../utils/relays';
|
|
||||||
import { CpuChipIcon } from '@heroicons/react/24/outline';
|
import { CpuChipIcon } from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
|
type TestResponse = {
|
||||||
|
timeTaken: string;
|
||||||
|
hashrate: string;
|
||||||
|
};
|
||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
const [filterDifficulty, setFilterDifficulty] = useState(localStorage.getItem('filterDifficulty') || 20);
|
const [filterDifficulty, setFilterDifficulty] = useState(localStorage.getItem('filterDifficulty') || 20);
|
||||||
const [difficulty, setDifficulty] = useState(localStorage.getItem('difficulty') || 21);
|
const [difficulty, setDifficulty] = useState(localStorage.getItem('difficulty') || 21);
|
||||||
|
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||||
|
const [powServer, setPowServer] = useState(localStorage.getItem('powserver') || '');
|
||||||
|
const [testDiff, setTestDiff] = useState('21')
|
||||||
|
const [testResult, setTestResult] = useState<TestResponse>()
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
localStorage.setItem('filterDifficulty', String(filterDifficulty));
|
localStorage.setItem('filterDifficulty', String(filterDifficulty));
|
||||||
localStorage.setItem('difficulty', String(difficulty));
|
localStorage.setItem('difficulty', String(difficulty));
|
||||||
|
localStorage.setItem('powserver', String(powServer));
|
||||||
|
|
||||||
const eventData = {
|
const eventData = {
|
||||||
difficulty: String(difficulty),
|
difficulty: String(difficulty),
|
||||||
filterDifficulty: String(filterDifficulty),
|
filterDifficulty: String(filterDifficulty),
|
||||||
|
powServer: String(powServer),
|
||||||
};
|
};
|
||||||
const event = new CustomEvent('settingsChanged', { detail: eventData });
|
const event = new CustomEvent('settingsChanged', { detail: eventData });
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
};
|
};
|
||||||
|
console.log(powServer)
|
||||||
|
|
||||||
|
const handleTest = () => {
|
||||||
|
setTestResult({ timeTaken: '...', hashrate: '...' });
|
||||||
|
console.log(powServer[0])
|
||||||
|
if (powServer[0]) {
|
||||||
|
const testRequest = {
|
||||||
|
Difficulty: testDiff
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(`${powServer}/test`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(testRequest)
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data);
|
||||||
|
// handle the response data
|
||||||
|
setTestResult(data)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-page bg-black text-white p-8 flex flex-col h-full">
|
<div className="settings-page bg-black text-white p-8 flex flex-col h-full">
|
||||||
@ -56,7 +92,47 @@ const Settings = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button className="bg-black border text-white font-bold py-2 px-4 rounded">
|
<div className='pb-4'>
|
||||||
|
<span onClick={() => setShowAdvancedSettings(!showAdvancedSettings)} className="">
|
||||||
|
{">"} Advanced Settings
|
||||||
|
</span>
|
||||||
|
{showAdvancedSettings && (
|
||||||
|
<><div className={`transition-height duration-200 ease-in-out overflow-hidden ${showAdvancedSettings ? 'h-auto' : 'h-0'} w-full md:w-1/3 px-2 mb-4 md:mb-0`}>
|
||||||
|
<label className="block text-xs mb-2" htmlFor="powServer">
|
||||||
|
Remote PoW Server:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="powServer"
|
||||||
|
type="text"
|
||||||
|
value={powServer}
|
||||||
|
onChange={e => setPowServer(e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border rounded-md bg-black"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="px-2">
|
||||||
|
<label className="block text-xs mb-2" htmlFor="powServer">
|
||||||
|
Test Your PoW Server (difficulty):
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="testAPI"
|
||||||
|
type="text"
|
||||||
|
value={testDiff}
|
||||||
|
onChange={e => setTestDiff(e.target.value)}
|
||||||
|
className="w-12 px-3 py-2 border rounded-md bg-black"
|
||||||
|
/>
|
||||||
|
<button type="button" onClick={handleTest} className="bg-black border text-white font-bold py-2 px-4 rounded">
|
||||||
|
Test
|
||||||
|
</button>
|
||||||
|
{testResult && (
|
||||||
|
<span>Time: {testResult.timeTaken}s with a hashrate of {testResult.hashrate}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="bg-black border text-white font-bold py-2 px-4 rounded">
|
||||||
Save Settings
|
Save Settings
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@ -76,7 +152,7 @@ const Settings = () => {
|
|||||||
</a>
|
</a>
|
||||||
<div>
|
<div>
|
||||||
<span>Found a bug? dm me: <a className="underline" href="https://njump.me/npub13azv2cf3kd3xdzcwqxlgcudjg7r9nzak37usnn7h374lkpvd6rcq4k8m54">doot</a> or <a className="underline" href="mailto:smolgrrr@protonmail.com">smolgrrr@protonmail.com</a></span>
|
<span>Found a bug? dm me: <a className="underline" href="https://njump.me/npub13azv2cf3kd3xdzcwqxlgcudjg7r9nzak37usnn7h374lkpvd6rcq4k8m54">doot</a> or <a className="underline" href="mailto:smolgrrr@protonmail.com">smolgrrr@protonmail.com</a></span>
|
||||||
<img className="h-16" src="doot.jpeg"/>
|
<img className="h-16" src="doot.jpeg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,15 +11,9 @@ import (
|
|||||||
"github.com/nbd-wtf/go-nostr/nip13"
|
"github.com/nbd-wtf/go-nostr/nip13"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EventContent struct for request body
|
|
||||||
type EventContent struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
Pubkey string `json:"pubkey"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PowRequest struct for the POST request
|
// PowRequest struct for the POST request
|
||||||
type PowRequest struct {
|
type PowRequest struct {
|
||||||
ReqEvent EventContent `json:"req_event"`
|
ReqEvent *nostr.Event `json:"req_event"`
|
||||||
Difficulty string `json:"difficulty"`
|
Difficulty string `json:"difficulty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,41 +38,100 @@ func handlePOW(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a nostr Event
|
// Generate proof of work for the event
|
||||||
unsignedEvent := &nostr.Event{
|
generatedEvent, err := nip13.Generate(powReq.ReqEvent, difficulty, 3*time.Hour)
|
||||||
Kind: nostr.KindTextNote,
|
if err != nil {
|
||||||
CreatedAt: nostr.Now(),
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
Content: powReq.ReqEvent.Content,
|
return
|
||||||
PubKey: powReq.ReqEvent.Pubkey,
|
}
|
||||||
|
|
||||||
|
// Create a response struct
|
||||||
|
type Response struct {
|
||||||
|
Event *nostr.Event `json:"event"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respond with the generated event and the time taken
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(Response{Event: generatedEvent})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PowRequest struct for the POST request
|
||||||
|
type TestRequest struct {
|
||||||
|
Difficulty string `json:"difficulty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePOW is the handler function for the "/powgen" endpoint
|
||||||
|
func handleTest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "POST" {
|
||||||
|
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var powReq PowRequest
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&powReq)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
difficulty, err := strconv.Atoi(powReq.Difficulty)
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the timer
|
// Start the timer
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// Generate proof of work for the event
|
event := &nostr.Event{
|
||||||
generatedEvent, err := nip13.Generate(unsignedEvent, difficulty, 3*time.Hour)
|
Kind: nostr.KindTextNote,
|
||||||
|
Content: "It's just me mining my own business",
|
||||||
|
PubKey: "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243",
|
||||||
|
}
|
||||||
|
pow, err := nip13.Generate(event, difficulty, 3*time.Hour)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the duration in milliseconds
|
// Calculate the duration in milliseconds
|
||||||
iterations, _ := strconv.ParseFloat(generatedEvent.Tags[0][1], 64)
|
iterations, _ := strconv.ParseFloat(pow.Tags[0][1], 64)
|
||||||
|
timeTaken := time.Since(start).Seconds()
|
||||||
hashrate := iterations / time.Since(start).Seconds()
|
hashrate := iterations / time.Since(start).Seconds()
|
||||||
|
|
||||||
// Create a response struct
|
// Create a response struct
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Event *nostr.Event `json:"event"`
|
TimeTaken float64 `json:"timeTaken"`
|
||||||
Hashrate float64 `json:"hashrate"`
|
Hashrate float64 `json:"hashrate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respond with the generated event and the time taken
|
// Respond with the generated event and the time taken
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(Response{Event: generatedEvent, Hashrate: hashrate})
|
json.NewEncoder(w).Encode(Response{TimeTaken: timeTaken, Hashrate: hashrate})
|
||||||
|
}
|
||||||
|
|
||||||
|
func corsMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Set headers
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
||||||
|
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
||||||
|
|
||||||
|
// If it's a preflight request, respond with 200
|
||||||
|
if r.Method == "OPTIONS" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
http.HandleFunc("/powgen", handlePOW)
|
http.Handle("/powgen", corsMiddleware(http.HandlerFunc(handlePOW)))
|
||||||
|
http.Handle("/test", corsMiddleware(http.HandlerFunc(handleTest)))
|
||||||
|
|
||||||
log.Fatal(http.ListenAndServe("0.0.0.0:8080", nil))
|
log.Fatal(http.ListenAndServe("0.0.0.0:8080", nil))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user