From 6dfdc69bac79fa0c84bfcf26aeb8224ee3de9036 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 8 Mar 2024 10:25:58 -0700 Subject: [PATCH] add journal parser (#8) * add journal parser * cleanup --- react/package.json | 4 +- react/src/JournalParser.js | 191 ++++++ react/src/Verifier.js | 35 +- wasm/Cargo.lock | 1129 ++++++++++++++++++++++++++++++++++++ web/package.json | 7 +- web/src/App.css | 12 + web/src/App.js | 7 +- yarn.lock | 960 +++++++++++++++++++++++++++++- 8 files changed, 2324 insertions(+), 21 deletions(-) create mode 100644 react/src/JournalParser.js create mode 100644 wasm/Cargo.lock diff --git a/react/package.json b/react/package.json index 3d57646..3e4801b 100644 --- a/react/package.json +++ b/react/package.json @@ -7,7 +7,9 @@ "test": "jest" }, "dependencies": { - "@eqtylab/risc-zero-verifier": "*" + "@eqtylab/risc-zero-verifier": "*", + "react-json-tree": "^0.18.0", + "react-markdown": "^9.0.1" }, "peerDependencies": { "react": "^18.2.0", diff --git a/react/src/JournalParser.js b/react/src/JournalParser.js new file mode 100644 index 0000000..8890565 --- /dev/null +++ b/react/src/JournalParser.js @@ -0,0 +1,191 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { JSONTree } from "react-json-tree"; +import Markdown from "react-markdown"; + +const DEFAULT_REGISTRY = 'https://raw.githubusercontent.com/cameronfyfe/risc0-journal-parser-registry/main/registry.json'; + +const cssPrefix = "risc-zero-journal-parser"; + +function cssClass(className) { + return `${cssPrefix}-${className}`; +} + +function JournalParser({ + guestCodeId, + journalBytes, + registryUrl = DEFAULT_REGISTRY, + ipfsGateway = "https://ipfs.io", +}) { + const journalBytesString = useMemo(() => + journalBytes ? JSON.stringify(Array.from(journalBytes)) : null, + [journalBytes] + ); + + const [registry, setRegistry] = useState(null); + useEffect(() => { + (async () => { + try { + const response = await fetch(registryUrl); + const json = await response.json(); + setRegistry(json); + } catch { + setRegistry(null); + } + })(); + }, [registryUrl]); + + const parsers = useMemo(() => { + if (registry && guestCodeId) { + return registry.parsers.filter((parser) => + parser.guestCodeId === guestCodeId + ); + } else { + return []; + } + }, [registry, guestCodeId]); + + const [selectedParserIndex, setSelectedParserIndex] = useState(undefined); + + useEffect(() => { + if (parsers && parsers.length > 0) { + setSelectedParserIndex(0); + } else { + setSelectedParserIndex(undefined); + } + }, [parsers]); + + const selectedParser = useMemo( + () => selectedParserIndex != undefined ? parsers[selectedParserIndex] : null, + [parsers, selectedParserIndex], + ); + + const wasmEntryUrl = useMemo( + () => + selectedParser + ? selectedParser.journalParser.protocol === "ipfs" + ? `${ipfsGateway}/ipfs/${selectedParser.journalParser.src}` + : null + : null, + [selectedParser], + ); + + const [wasmModule, setWasmModule] = useState(null); + useEffect(() => { + if (wasmEntryUrl) { + (async () => { + // TODO: This is a hack to get around the fact that import() doesn't work with dynamic URLs + // when webpack/babel are configured to statically resolve imports. + // It's easy to disable globally in .babelrc, but then the + // 'import("@eqtylab/risc-zero-verifier")' in Verifier.js breaks. + // This hack works for now but is not ideal. We need to be careful what the value of + // wasmEntryUrl is to use this method with eval(). + const wm = await eval(`import("${wasmEntryUrl}")`); + await wm.default(); + setWasmModule(wm); + })(); + } else { + setWasmModule(null); + } + }, [wasmEntryUrl]); + + const getJsonObjectFromReceiptBinary = useMemo(() => + wasmModule && wasmModule.json_obj_from_journal_bytes || null, + [wasmModule] + ); + + const getStatementFromReceiptBinary = useMemo(() => + wasmModule && wasmModule.statement_from_journal_bytes || null, + [wasmModule] + ); + + const [journalObj, setJournalObj] = useState(null); + useEffect(() => { + (async () => { + if (journalBytes && getJsonObjectFromReceiptBinary) { + try { + const obj = await getJsonObjectFromReceiptBinary(journalBytes); + setJournalObj(obj); + } catch { + setJournalObj(null); + } + } else { + setJournalObj(null); + } + })(); + }, [journalBytes, getJsonObjectFromReceiptBinary]); + + const [statement, setStatement] = useState(null); + useEffect(() => { + (async () => { + if (journalBytes && getStatementFromReceiptBinary) { + try { + const stmt = await getStatementFromReceiptBinary(journalBytes); + setStatement(stmt); + } catch { + setStatement(null); + } + } else { + setStatement(null); + } + })(); + }, [journalBytes, getStatementFromReceiptBinary]); + + return ( +
+ Journal Parser: +
+ {guestCodeId ? ( + + ) : ( +

No parsers available for this guest code id.

+ )} + +

+ + {statement && +
+ Statement: +
+ {statement} +
+ } + +

+ + {journalObj && +
+ Journal (JSON): +
+
+ +
+
+ } + +

+ + {journalBytesString && +
+ Journal (RAW): +
+