Skip to content

Commit

Permalink
frontend poc finished
Browse files Browse the repository at this point in the history
  • Loading branch information
Okm165 committed Jul 3, 2024
1 parent 06b5a38 commit dd57d78
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 68 deletions.
39 changes: 22 additions & 17 deletions crates/delegator/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use async_stream::stream;
use axum::{
extract::State,
extract::{Query, State},
response::{sse::Event, Sse},
Json,
};
Expand All @@ -12,7 +12,7 @@ use std::hash::{DefaultHasher, Hash, Hasher};
use std::{io, time::Duration};
use tokio::sync::{broadcast, mpsc};
use tokio_stream::Stream;
use tracing::{debug, info};
use tracing::info;
use zetina_common::{
hash,
job::{Job, JobData},
Expand Down Expand Up @@ -40,57 +40,62 @@ impl Clone for ServerState {

#[derive(Debug, Deserialize)]
pub struct DelegateRequest {
cairo_pie: Vec<u8>,
trace: Vec<u8>,
}

#[derive(Debug, Serialize)]
pub struct DelegateResponse {
job_hash: u64,
job_hash: String,
}

pub async fn deletage_handler(
State(state): State<ServerState>,
Json(input): Json<DelegateRequest>,
) -> Result<Json<DelegateResponse>, StatusCode> {
let cairo_pie = input.cairo_pie;
let cairo_pie = input.trace;
let job_data = JobData::new(0, cairo_pie);
let job = Job::try_from_job_data(job_data, &state.signing_key);
let serialized_job = serde_json::to_string(&job).unwrap();
state.job_topic_tx.send(serialized_job.into()).await.unwrap();
info!("Sent a new job: {}", hash!(&job));
Ok(Json(DelegateResponse { job_hash: hash!(&job) }))
Ok(Json(DelegateResponse { job_hash: hash!(&job).to_string() }))
}

#[derive(Debug, Deserialize)]
pub struct JobEventsRequest {
job_hash: u64,
job_hash: String,
}

#[derive(Debug, Serialize)]
#[serde(tag = "type", content = "data")]
pub enum JobEventsResponse {
Picked(u64),
Picked(String),
Witness(Vec<u8>),
}

pub async fn job_events_handler(
State(mut state): State<ServerState>,
Json(input): Json<JobEventsRequest>,
Query(input): Query<JobEventsRequest>,
) -> Sse<impl Stream<Item = Result<Event, io::Error>>> {
let job_hash = input.job_hash;

let stream = stream! {
let job_hash = input.job_hash.parse::<u64>()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
loop {
tokio::select! {
Ok(job) = state.job_picked_rx.recv() => {
debug!("Received job picked: {}", hash!(job));
info!("Received job picked: {}", hash!(job));
if hash!(job) == job_hash {
yield Event::default().json_data(JobEventsResponse::Picked(hash!(job))).map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e.to_string()));
yield Event::default()
.json_data(JobEventsResponse::Picked(hash!(job).to_string()))
.map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e.to_string()));
}
},
Ok(job_witness) = state.job_witness_rx.recv() => {
debug!("Received job witness: {}", &job_witness.job_hash);
if hash!(job_witness.job_hash) == job_hash {
yield Event::default().json_data(JobEventsResponse::Witness(job_witness.proof)).map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e.to_string()));
info!("Received job witness: {}", &job_witness.job_hash);
if job_witness.job_hash == job_hash {
yield Event::default()
.json_data(JobEventsResponse::Witness(job_witness.proof))
.map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e.to_string()));
}
}
else => break
Expand All @@ -101,7 +106,7 @@ pub async fn job_events_handler(

Sse::new(stream).keep_alive(
axum::response::sse::KeepAlive::new()
.interval(Duration::from_secs(1))
.interval(Duration::from_secs(5))
.text("keep-alive-text"),
)
}
Binary file added dashboard/cairo_pie.zip
Binary file not shown.
Binary file added dashboard/public/zetina-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 11 additions & 15 deletions dashboard/src/app/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,30 @@ import { z } from "zod";

// Zod for DelegateRequest
export const DelegateRequest = z.object({
cairo_pie: z.instanceof(Uint8Array),
trace: z.array(z.number()),
});
export type DelegateRequest = z.infer<typeof DelegateRequest>;

// Zod for DelegateResponse
export const DelegateResponse = z.object({
job_hash: z.number(),
job_hash: z.coerce.bigint(),
});
export type DelegateResponse = z.infer<typeof DelegateResponse>;

// Zod for JobEventsRequest
export const JobEventsRequest = z.object({
job_hash: z.number(),
job_hash: z.coerce.bigint(),
});
export type JobEventsRequest = z.infer<typeof JobEventsRequest>;

// Zod for JobEventsResponse
export const Picked = z.object({
type: z.literal("Picked"),
data: z.number(),
export const JobEventsResponse = z.object({
type: z.literal("Picked").or(z.literal("Witness")),
data: z.any(),
});
export type Picked = z.infer<typeof Picked>;
export type JobEventsResponse = z.infer<typeof JobEventsResponse>;

export const Witness = z.object({
type: z.literal("Witness"),
data: z.instanceof(Uint8Array),
});
export type Witness = z.infer<typeof Witness>;
export const JobHash = z.coerce.bigint();
export type JobHash = z.infer<typeof JobHash>;

export const JobEventsResponse = z.union([Picked, Witness]);
export type JobEventsResponse = z.infer<typeof JobEventsResponse>;
export const Proof = z.array(z.number());
export type Proof = z.infer<typeof Proof>;
1 change: 1 addition & 0 deletions dashboard/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<head><link rel="icon" href="zetina-logo.png" /></head>
<body className={inter.className}>{children}</body>
</html>
);
Expand Down
83 changes: 61 additions & 22 deletions dashboard/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import Check from "@mui/icons-material/Check";
import StepConnector, {
stepConnectorClasses,
} from "@mui/material/StepConnector";
import { DelegateRequest, DelegateResponse } from "./api";
import { DelegateRequest, DelegateResponse, JobEventsResponse, JobHash, Proof } from "./api";
import { useState } from "react";
import subscribeEvents from "./subscribeEvents";
import assert from "assert";

const steps = [
"Job propagated to network",
Expand All @@ -27,11 +29,6 @@ const QontoConnector = styled(StepConnector)(({ theme }) => ({
left: "calc(-50% + 16px)",
right: "calc(50% + 16px)",
},
[`&.${stepConnectorClasses.active}`]: {
[`& .${stepConnectorClasses.line}`]: {
borderColor: "#784af4",
},
},
[`&.${stepConnectorClasses.completed}`]: {
[`& .${stepConnectorClasses.line}`]: {
borderColor: "#784af4",
Expand All @@ -51,9 +48,6 @@ const QontoStepIconRoot = styled("div")<{ ownerState: { active?: boolean } }>(
display: "flex",
height: 22,
alignItems: "center",
...(ownerState.active && {
color: "#784af4",
}),
"& .QontoStepIcon-completedIcon": {
color: "#784af4",
zIndex: 1,
Expand Down Expand Up @@ -86,6 +80,7 @@ export default function Home() {
const darkTheme = createTheme({
palette: {
mode: "dark",
primary: { main: "#784af4", dark: "#784af4" }
},
});

Expand All @@ -100,11 +95,15 @@ export default function Home() {
reader.onload = async (e) => {
if (e.target && e.target.result) {
const fileBytes = new Uint8Array(e.target.result as ArrayBuffer);

console.log(Array.from(fileBytes));
const requestBody: DelegateRequest = DelegateRequest.parse({
cairo_pie: fileBytes,
trace: Array.from(fileBytes),
});

console.log(requestBody);

let subscriber: EventSource | null = null

try {
const response = await fetch("http://localhost:3010/delegate", {
method: "POST",
Expand All @@ -122,18 +121,43 @@ export default function Home() {
await response.json(),
);
console.log("Job hash:", data.job_hash);
setIsProcessing(data.job_hash)

setActiveStep(1)
setIsProcessing(data.job_hash);

subscriber = subscribeEvents(
"http://localhost:3010/job_events",
`job_hash=${data.job_hash.toString()}`,
(event) => {
let job_event = JobEventsResponse.parse(event);
if (job_event.type == "Picked") {
let job_hash = JobHash.parse(job_event.data);
assert(job_hash == data.job_hash)
setActiveStep(2)
}
if (job_event.type == "Witness") {
let proof = Proof.parse(job_event.data);
setActiveStep(3)
setDownloadBlob([new Blob([new Uint8Array(proof)]), `${data.job_hash}_proof.json`])
setIsProcessing(null)
subscriber?.close()
}
},
);
} catch (error) {
console.error("Failed to upload file:", error);
setIsProcessing(null)
setIsProcessing(null);
subscriber?.close()
}
}
};

reader.readAsArrayBuffer(file);
};

const [isProcessing, setIsProcessing] = useState<number | null>(null);
const [isProcessing, setIsProcessing] = useState<bigint | null>(null);
const [activeStep, setActiveStep] = useState<number>(0);
const [downloadBlob, setDownloadBlob] = useState<[Blob, string] | null>(null);

const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop: ondrop,
Expand All @@ -144,28 +168,32 @@ export default function Home() {
<div className="h-screen flex flex-col background">
<main className="flex-1 grid justify-center items-center">
<div className="p-10 border-2 border-gray-800 rounded-2xl backdrop-blur-md grid grid-flow-row gap-8 w-[800px]">
<div className="text-center font-medium text-xl">
Supply program Trace
<div className="text-center font-medium grid grid-flow-row gap-1">
<div className="text-xl font-bold">ZK prover network</div>
<div className="text-md">Prove program Trace</div>
</div>
<div
className="cursor-pointer p-10 border-2 rounded-2xl border-dashed border-gray-800 hover:bg"
{...getRootProps()}
>
<input className="w-full" {...getInputProps()} />
{isProcessing != null ? (
<p className="text-center">Processing job hash: {isProcessing}</p>
<p className="text-center">
Processing job: {isProcessing.toString()}
</p>
) : isDragActive ? (
<p className="text-center">Drop the Trace here ...</p>
) : (
<p className="text-center">
Drag Trace here, or click to select files
</p>
)
}
)}
</div>
<LinearProgress sx={{ backgroundColor: "transparent" }} />
<LinearProgress
sx={{ backgroundColor: 'transparent', display: isProcessing != null ? 'block' : 'none' }}
/>
<Stepper
activeStep={4}
activeStep={activeStep}
alternativeLabel
connector={<QontoConnector />}
>
Expand All @@ -178,7 +206,18 @@ export default function Home() {
))}
</Stepper>
<div className="grid justify-center items-center">
<Button variant="outlined" size="large" disabled>
<Button variant="outlined" size="large" disabled={downloadBlob == null} onClick={() => {
if (downloadBlob != null) {
const download_url = window.URL.createObjectURL(downloadBlob[0]);
const a = document.createElement('a');
a.href = download_url;
a.download = downloadBlob[1];
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(download_url);
}
}}>
Download Proof
</Button>
</div>
Expand Down
16 changes: 4 additions & 12 deletions dashboard/src/app/subscribeEvents.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
export default async function subscribeEvents(
export default function subscribeEvents(
url: string,
fetchparams: string,
sseparams: string,
params: string,
callback: (data: any) => void,
) {
const events = new EventSource(`${url}?${sseparams}`, {
withCredentials: true,
const events = new EventSource(`${url}?${params}`, {
withCredentials: false,
});

const eventSource = new ReadableStream({
Expand All @@ -29,13 +28,6 @@ export default async function subscribeEvents(
},
});

const response = await fetch(`${url}?${fetchparams}`, {
method: "GET",
credentials: "same-origin",
}).then((r) => r.json());

callback(response);

eventSource.pipeTo(eventSink);

return events;
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ services:
cpus: '8'
memory: '10G'
ports:
- "50051:50051/tcp"
- "3010:3010/tcp"
environment:
- RUST_LOG=info
networks:
Expand All @@ -40,7 +40,7 @@ services:
cpus: '8'
memory: '10G'
ports:
- "50052:50052/tcp"
- "3020:3020/tcp"
environment:
- RUST_LOG=info
networks:
Expand Down

0 comments on commit dd57d78

Please sign in to comment.