import std/os import std/streams import pkg/chronicles import pkg/chronos import pkg/ethers import pkg/questionable import pkg/questionable/results import pkg/zippy/tarballs import pkg/chronos/apps/http/httpclient import ../../codex/contracts/marketplace import ../../codex/contracts/deployment proc consoleLog(logLevel: LogLevel, msg: LogOutputStr) {.gcsafe.} = try: stdout.write(msg) stdout.flushFile() except IOError as err: logLoggingFailure(cstring(msg), err) proc noOutput(logLevel: LogLevel, msg: LogOutputStr) = discard defaultChroniclesStream.outputs[0].writer = consoleLog defaultChroniclesStream.outputs[1].writer = noOutput defaultChroniclesStream.outputs[2].writer = noOutput proc printHelp() = info "Usage: ./cirdl [circuitPath] [rpcEndpoint] ([marketplaceAddress])" info " circuitPath: path where circuit files will be placed." info " rpcEndpoint: URL of web3 RPC endpoint." info " marketplaceAddress: Address of deployed Codex marketplace contracts. If left out, will auto-discover based on connected network." proc getMarketplaceAddress( provider: JsonRpcProvider, mpAddressOverride: ?Address ): Future[?Address] {.async.} = let deployment = Deployment.new(provider, mpAddressOverride) let address = await deployment.address(Marketplace) return address proc getCircuitHash( provider: JsonRpcProvider, marketplaceAddress: Address ): Future[?!string] {.async.} = let marketplace = Marketplace.new(marketplaceAddress, provider) let config = await marketplace.configuration() return success config.proofs.zkeyHash proc formatUrl(hash: string): string = "https://circuit.codex.storage/proving-key/" & hash proc retrieveUrl(uri: string): Future[seq[byte]] {.async.} = let httpSession = HttpSessionRef.new() try: let resp = await httpSession.fetch(parseUri(uri)) return resp.data finally: await noCancel(httpSession.closeWait()) proc downloadZipfile(url: string, filepath: string): Future[?!void] {.async.} = try: let file = await retrieveUrl(url) var s = newFileStream(filepath, fmWrite) for b in file: s.write(b) s.close() except Exception as exc: return failure(exc.msg) success() proc unzip(zipfile: string, targetPath: string): ?!void = try: extractAll(zipfile, targetPath) except Exception as exc: return failure(exc.msg) success() proc copyFiles(unpackDir: string, circuitPath: string): ?!void = try: for file in walkDir(unpackDir): copyFileToDir(file.path, circuitPath) except Exception as exc: return failure(exc.msg) success() proc main() {.async.} = info "Codex Circuit Downloader, Aww yeah!" let args = os.commandLineParams() if args.len < 2 or args.len > 3: printHelp() return let circuitPath = args[0] rpcEndpoint = args[1] zipfile = "circuit.tar.gz" unpackFolder = "." / "tempunpackfolder" var mpAddressOverride: ?Address if args.len == 3: without parsed =? Address.init(args[2]): raise newException(ValueError, "Invalid ethereum address") mpAddressOverride = some parsed debug "Starting", circuitPath, rpcEndpoint if (dirExists(unpackFolder)): removeDir(unpackFolder) let provider = JsonRpcProvider.new(rpcEndpoint) without marketplaceAddress =? (await getMarketplaceAddress(provider, mpAddressOverride)), err: error "No known marketplace address, nor any specified manually", msg = err.msg return info "Marketplace address", address = $marketplaceAddress without circuitHash =? (await getCircuitHash(provider, marketplaceAddress)), err: error "Failed to get circuit hash", msg = err.msg return debug "Got circuithash", circuitHash let url = formatUrl(circuitHash) info "Download URL", url if dlErr =? (await downloadZipfile(url, zipfile)).errorOption: error "Failed to download circuit file", msg = dlErr.msg return debug "Download completed" if err =? unzip(zipfile, unpackFolder).errorOption: error "Failed to unzip file", msg = err.msg return debug "Unzip completed" # Unpack library cannot unpack into existing directory. We also cannot # delete the targer directory and have the library recreate it because # Codex has likely created it and set correct permissions. # So, we unpack to a temp folder and move the files. if err =? copyFiles(unpackFolder, circuitPath).errorOption: error "Failed to copy files", msg = err.msg return debug "Files copied" removeFile(zipfile) removeDir(unpackFolder) debug "file and unpack folder removed" when isMainModule: waitFor main() info "Done!"