From 0969bb906eb1f6ab08b5529769747237147e5f5e Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Sun, 23 Jul 2023 11:28:36 +1000 Subject: [PATCH] simplify for cannonv2 --- .gitignore | 17 +++++-- Cargo.lock | 64 ----------------------- Cargo.toml | 7 +-- Dockerfile | 46 ----------------- LICENSE | 21 -------- Makefile | 7 --- README.md | 40 +++++++-------- build.rs | 5 -- build.sh | 15 ------ elf2bin.py | 60 ---------------------- mips-unknown-none.json | 19 ------- requirements.txt | 4 -- src/asm.S | 30 ----------- src/heap.rs | 44 ---------------- src/iommu.rs | 112 ----------------------------------------- src/main.rs | 43 +++++++--------- startup/README.md | 12 ----- startup/startup.bin | Bin 40 -> 0 bytes startup/startup.s | 24 --------- 19 files changed, 53 insertions(+), 517 deletions(-) delete mode 100644 Cargo.lock delete mode 100644 Dockerfile delete mode 100644 LICENSE delete mode 100644 Makefile delete mode 100644 build.rs delete mode 100755 build.sh delete mode 100755 elf2bin.py delete mode 100644 mips-unknown-none.json delete mode 100644 requirements.txt delete mode 100644 src/asm.S delete mode 100644 src/heap.rs delete mode 100644 src/iommu.rs delete mode 100644 startup/README.md delete mode 100644 startup/startup.bin delete mode 100644 startup/startup.s diff --git a/.gitignore b/.gitignore index 0fde8dd..6985cf1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,14 @@ -target -build -venv \ No newline at end of file +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 220364f..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,64 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[package]] -name = "linked_list_allocator" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e8da0e6283aace40e4e0395fe5ad7a147fac6ff47bda1f038b5044fb11683c2" -dependencies = [ - "spinning_top", -] - -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "rlibc" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe" - -[[package]] -name = "rust-in-my-cannon" -version = "0.1.0" -dependencies = [ - "cc", - "linked_list_allocator", - "rlibc", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "spinning_top" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75adad84ee84b521fb2cca2d4fd0f1dab1d8d026bda3c5bea4ca63b5f9f9293c" -dependencies = [ - "lock_api", -] diff --git a/Cargo.toml b/Cargo.toml index 8e0688d..2366b55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,8 @@ authors = ["{{authors}}"] edition = "2021" [dependencies] -linked_list_allocator = "0.10.3" -rlibc = "1.0.0" - -[build-dependencies] -cc = "*" +cannon-io = { git = "https://github.com/badboilabs/Cannon-rs" } +cannon-heap = { git = "https://github.com/badboilabs/Cannon-rs" } [profile.dev] panic = "abort" diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 43bd7ad..0000000 --- a/Dockerfile +++ /dev/null @@ -1,46 +0,0 @@ -FROM ubuntu:20.04 - -ENV SHELL=/bin/bash -ENV DEBIAN_FRONTEND noninteractive - -RUN apt-get update -RUN apt-get install --assume-yes --no-install-recommends \ - build-essential \ - curl \ - g++-mips-linux-gnu \ - libc6-dev-mips-cross \ - make \ - cmake \ - git \ - python3 python3.8-venv python3-pip - -RUN pip3 install wheel - -ENV CC_mips_unknown_none=mips-linux-gnu-gcc \ - CXX_mips_unknown_none=mips-linux-gnu-g++ \ - CARGO_TARGET_MIPS_UNKNOWN_NONE_LINKER=mips-linux-gnu-gcc - -# -# Install Rustup and Rust -# -# Use this specific version of rust. This is needed because versions of rust after this one broke -# support for -Zbuild-std for the embedded targets. -RUN curl https://sh.rustup.rs -sSf | bash -s -- -y --default-toolchain nightly-2022-05-31 --component rust-src -ENV PATH="/root/.cargo/bin:${PATH}" - -# Used for build caching -RUN cargo install cargo-chef - -WORKDIR /code - -COPY . . - -RUN git config --global --add safe.directory '*' - -# Generate recipe -RUN cargo chef prepare --recipe-path recipe.json - -# Download and build depdencies -RUN cargo chef cook --release --recipe-path recipe.json - -CMD ["/bin/bash", "build.sh"] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 5ce29a0..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Willem Olding - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Makefile b/Makefile deleted file mode 100644 index 128e8aa..0000000 --- a/Makefile +++ /dev/null @@ -1,7 +0,0 @@ - -build: - docker run --rm -v $(shell pwd):/code {{project-name}}/builder - -docker_image: - docker build . -t {{project-name}}/builder - diff --git a/README.md b/README.md index b1efee3..fea05d9 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,45 @@ # Rust Cannon Template 🦀💣💥 -This repo contains a build system and a minimal Rust program for building MIPS binaries that are executable in the context of [Optimism Cannon](https://github.com/ethereum-optimism/cannon). - -It comes with a few barebones helpers for reading the input hashes, writing output and using the pre-image oracle. +This repo contains a build system and a minimal Rust program for building MIPS binaries that are executable in the context of [Optimism Cannon](https://github.com/ethereum-optimism/optimism/tree/develop/cannon). It is intended to be used as a template with `cargo generate` e.g. ```shell -cargo generate willemolding/rust-cannon-template +cargo generate BadBoiLabs/rust-cannon-template ``` -## Usage +## Building + +### Building with Docker -The template uses Docker for cross-compiling MIPS on any host. First build the docker image by running: +Preferred build method is using docker. BadBoiLabs provides a builder image for Cannon-rs projects ```shell -make docker_image +docker run --rm -v `pwd`/..:/code -w="/code" ghcr.io/badboilabs/cannon-rs/builder:main cargo build --release -Zbuild-std ``` -After this a Cannon ready MIPS binary can be build with: +## Patching and running in Cannon + +Install the `cannon` tool from Optimism (requires Golang installed) + ```shell -make build +git clone https://github.com/ethereum-optimism/optimism +cd optimism/cannon +go install . ``` -This will write an `out.bin` file to the build directory. - ---- +Patch the elf -Alternatively if you want to experiment in the build environment you can load up an interactive shell with - ```shell -make docker_image -docker run -it --rm -v $(pwd):/code {{project-name}}/builder bash +cannon load-elf --path ../target/mips-unknown-none/release/{{project-name}} --patch stack ``` -(replace with your project name as required) -and from there you can run +Run it in the Cannon emulator ```shell -./build.sh +cannon run --input ./state.json --info-at %100 --stop-at never ``` -to produce the output ## Credits -The majority of this amazing work was done by @pepyakin in their [Cannon fork](https://github.com/pepyakin/rusty-cannon/). This just pulls out the relevant pieces and adds a few quality of life improvements to the build system. +The origins of this amazing work is from @pepyakin in their [Cannon fork](https://github.com/pepyakin/rusty-cannon/). This has evolved into a streamlined Cannon build system and been migrated to the latest version of Cannon diff --git a/build.rs b/build.rs deleted file mode 100644 index d481dab..0000000 --- a/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - let mut build = cc::Build::new(); - build.file("src/asm.S"); - build.compile("asm"); -} diff --git a/build.sh b/build.sh deleted file mode 100755 index 7d3252d..0000000 --- a/build.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -set -e - -ELF_NAME={{project-name}} - -mkdir -p build - -RUSTFLAGS="-Clink-arg=-e_start" cargo build --release --target=mips-unknown-none.json -Zbuild-std=core,std,alloc,panic_abort,compiler_builtins -Zbuild-std-features=compiler-builtins-mem - -python3 -m venv venv - -source venv/bin/activate -pip3 install -r requirements.txt -./elf2bin.py ./target/mips-unknown-none/release/$ELF_NAME ./build/out.bin -deactivate diff --git a/elf2bin.py b/elf2bin.py deleted file mode 100755 index 01d65a5..0000000 --- a/elf2bin.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import struct -import hashlib -from elftools.elf.elffile import ELFFile - -def load_and_convert(fn): - print("compiling ", os.path.abspath(fn)) - - elf = open(fn, "rb") - data = elf.read() - elf.seek(0) - - elffile = ELFFile(elf) - - end_addr = 0 - for seg in elffile.iter_segments(): - end_addr = max(end_addr, seg.header.p_vaddr + seg.header.p_memsz) - for section in elffile.iter_sections(): - if section.name == ".got": - end_addr = max(end_addr, section.header.sh_addr + section.header.sh_size) - - # program memory (16 MB) - prog_size = (end_addr+0xFFF) & ~0xFFF - prog_dat = bytearray(prog_size) - print("malloced 0x%x for program" % prog_size) - - for seg in elffile.iter_segments(): - print(seg.header, hex(seg.header.p_vaddr)) - prog_dat[seg.header.p_vaddr:seg.header.p_vaddr+len(seg.data())] = seg.data() - - entry = elffile.header.e_entry - print("entrypoint: 0x%x" % entry) - - # moved to MIPS - sf = os.path.join(os.path.dirname(os.path.abspath(__file__)), "startup", "startup.bin") - start = open(sf, "rb").read() + struct.pack(">I", entry) - prog_dat[:len(start)] = start - entry = 0 - - - # Copy the GOT section to its address in the prog_dat blob. - for section in elffile.iter_sections(): - if section.name == ".got": - print(hex(section.header.sh_addr)) - prog_dat[section.header.sh_addr:section.header.sh_addr+len(section.data())] = section.data() - print("copied .got to 0x%x" % (section.header.sh_addr)) - - return prog_dat, prog_size - -if __name__ == "__main__": - fn = sys.argv[1] - outfn = sys.argv[2] - - prog_dat, prog_size = load_and_convert(fn) - print("compiled %d bytes with md5 %s" % (prog_size, hashlib.md5(prog_dat).hexdigest())) - - with open(outfn, "wb") as f: - f.write(prog_dat) \ No newline at end of file diff --git a/mips-unknown-none.json b/mips-unknown-none.json deleted file mode 100644 index 41a77e1..0000000 --- a/mips-unknown-none.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "arch": "mips", - "cpu": "mips32r2", - "llvm-target": "mips-unknown-none", - "data-layout": "E-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64", - "target-endian": "big", - "target-pointer-width": "32", - "target-c-int-width": "32", - "os": "none", - "features": "+mips32r2", - "max-atomic-width": "32", - "linker": "rust-lld", - "linker-flavor": "ld.lld", - "executables": true, - "panic-strategy": "abort", - "relocation-model": "static", - "emit-debug-gdb-scripts": false, - "singlethread": true -} diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c8f50ae..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -pyelftools==0.27 -hexdump==3.3 -termcolor==1.1.0 -capstone==4.0.2 diff --git a/src/asm.S b/src/asm.S deleted file mode 100644 index 68a0c56..0000000 --- a/src/asm.S +++ /dev/null @@ -1,30 +0,0 @@ -.global halt -.global preimage_oracle -.global write - -halt: - # the actual halt condition is pc=0x5ead0000, and that's what the 4246 (exit) syscall will do. - # Just rely on that to avoid accidental compatibility hazards. - li $v0, 4246 - syscall - -brk: - # this syscall does not do anything, it just returns the current program break address. - li $v0, 4045 - syscall - jr $ra - nop - -preimage_oracle: - # 4020 is the syscall number for getpid. In cannon, it is used for triggering the preimage - # oracle. - li $v0, 4020 - syscall - jr $ra - nop - -write: - li $v0, 4004 - syscall - jr $ra - nop diff --git a/src/heap.rs b/src/heap.rs deleted file mode 100644 index 086ff1d..0000000 --- a/src/heap.rs +++ /dev/null @@ -1,44 +0,0 @@ -use core::alloc::{GlobalAlloc, Layout}; -use core::cell::RefCell; -use core::mem::MaybeUninit; -use core::ptr::{self, NonNull}; - -struct Alloc { - heap: RefCell, -} - -impl Alloc { - const fn new() -> Self { - Self { - heap: RefCell::new(linked_list_allocator::Heap::empty()), - } - } -} - -unsafe impl GlobalAlloc for Alloc { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - self.heap - .borrow_mut() - .allocate_first_fit(layout) - .ok() - .map_or(ptr::null_mut(), |allocation| allocation.as_ptr()) - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - self.heap - .borrow_mut() - .deallocate(NonNull::new_unchecked(ptr), layout) - } -} - -#[global_allocator] -static mut ALLOCATOR: Alloc = Alloc::new(); - -pub unsafe fn init() { - const HEAP_SIZE: usize = 0x400000; - static mut HEAP: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - ALLOCATOR - .heap - .borrow_mut() - .init(HEAP.as_mut_ptr() as *mut u8, HEAP_SIZE) -} diff --git a/src/iommu.rs b/src/iommu.rs deleted file mode 100644 index f086473..0000000 --- a/src/iommu.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! This module is used when the state machine compiled into MIPS to interact with the host -//! environment. The host environment is either the prover or the onchain one step verifier. - -use alloc::{collections::btree_map::BTreeMap, vec::Vec}; -use core::ptr; - -type H256 = [u8; 32]; - -/// The address of the input hash. -const PTR_INPUT_HASH: usize = 0x30000000; -/// The address where the output hash is written at the end of execution. -const PTR_OUTPUT_HASH: usize = 0x30000804; -/// The address where a special magic value is written at the end of execution. -const PTR_MAGIC: usize = 0x30000800; -/// The address where the preimage hash for the preimage oracle is written by the guest. -const PTR_PREIMAGE_ORACLE_HASH: usize = 0x30001000; -/// The address where the preimage oracle output size is written by the host. -const PTR_PREIMAGE_ORACLE_SIZE: usize = 0x31000000; -/// The address where the preimage oracle output data is written by the host. -const PTR_PREIMAGE_ORACLE_DATA: usize = 0x31000004; - -/// Loads the input hash from the host environment. -pub fn input_hash() -> H256 { - unsafe { ptr::read_volatile(PTR_INPUT_HASH as *const H256) } -} - -/// Prepares the guest envrionment to exiting. Writes the output hash and the magic to be read by -/// the host and then halts the execution. -pub fn output(hash: H256) -> ! { - unsafe { - ptr::write_volatile(PTR_MAGIC as *mut u32, 0x1337f00d); - ptr::write_volatile(PTR_OUTPUT_HASH as *mut H256, hash); - ffi::halt(); - } -} - -/// Request a preimage from the oracle. -/// -/// The returned slice is valid until the end of the program. -pub fn preimage(hash: H256) -> Option<&'static [u8]> { - // The cache of all requested preimages to avoid going via the host boundary every time. - // - // Under MIPS this is running exclusively in single-threaded mode. We could've avoided using - // a Mutex, but it seems to be fine. Uncontended use is just atomic writes. - static mut PREIMAGE_CACHE: Option>> = None; - - // assume the given reference is valid for the whole program lifetime. - let eternalize = |v: &Vec| -> &'static [u8] { - // SAFETY: this is safe because we are creating the slice from the pointer and the size - // that were already produced by a vec. - // - // use-after-free is also a non concern because the vec is owned by the cache and - // the cache is never pruned. - unsafe { core::slice::from_raw_parts(v.as_ptr(), v.len()) } - }; - - // Check if the preimage is already cached. - unsafe { - let mut preimage_cache = match PREIMAGE_CACHE { - Some(ref mut cache) => cache, - None => { - let cache = BTreeMap::new(); - PREIMAGE_CACHE = Some(cache); - PREIMAGE_CACHE.as_mut().unwrap() - } - }; - - if let Some(preimage) = preimage_cache.get(&hash) { - return Some(eternalize(preimage)); - } - - *(PTR_PREIMAGE_ORACLE_HASH as *mut [u8; 32]) = hash; - - ffi::preimage_oracle(); - - // Read the size of the preimage. It seems to be BE, so no conversion needed. - let size = *(PTR_PREIMAGE_ORACLE_SIZE as *const u32); - if size == 0 { - return None; - } - - // Read the preimage. - // - // SAFETY: The pointer is aligned by definition and is not null. - let preimage = - core::slice::from_raw_parts(PTR_PREIMAGE_ORACLE_DATA as *const u8, size as usize) - .to_vec(); - - // if arbitrary_state_machine::keccak256(&preimage) != hash { - // panic!("preimage oracle returned invalid preimage"); - // } - - let s = eternalize(&preimage); - preimage_cache.insert(hash, preimage); - Some(s) - } -} - -pub fn print(s: &str) { - unsafe { - ffi::write(1, s.as_ptr(), s.len()); - } -} - -mod ffi { - //! See asm.S - extern "C" { - pub fn halt() -> !; - pub fn preimage_oracle(); - pub fn write(fd: usize, buf: *const u8, count: usize); - } -} diff --git a/src/main.rs b/src/main.rs index eb4c07a..a14663c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,45 +1,38 @@ -#![feature(alloc_error_handler)] // no_std and allocator support is not stable. -#![feature(stdsimd)] // for `mips::break_`. If desired, this could be replaced with asm. #![no_std] #![no_main] +#![feature(core_intrinsics)] +#![feature(alloc_error_handler)] -extern crate alloc; -extern crate rlibc; // memcpy, and friends +/// Defines the size of the heap in bytes +/// Changing this will change the size of the resulting json file built by converting the elf file +/// How big you can make this depends on the program size but it should be possible to make it very large (close to 4GB). +/// See https://image1.slideserve.com/3443033/memory-map-l.jpg +const HEAP_SIZE: usize = 0x400000; + +use cannon_io::prelude::*; +use cannon_heap::init_heap; -mod heap; -mod iommu; +extern crate alloc; /// Main entrypoint for a verifiable computation #[no_mangle] pub extern "C" fn _start() { - unsafe { heap::init() }; - // grab the input hash - let input_hash = iommu::input_hash(); + init_heap!(HEAP_SIZE); - // Do something amazing (‾⌣‾) + print("Lets do something amazing!\n"); - // Write the output - iommu::output([0; 32]); + exit(0); // 0 code indicates success } #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { - // Uncomment code below if you're in trouble - /* let msg = alloc::format!("Panic: {}", info); - iommu::print(&msg); - */ - - unsafe { - core::arch::mips::break_(); - } + let _ = print(&msg); + exit(2); } #[alloc_error_handler] fn alloc_error_handler(_layout: alloc::alloc::Layout) -> ! { - // NOTE: avoid `panic!` here, technically, it might not be allowed to panic in an OOM situation. - // with panic=abort it should work, but it's no biggie use `break` here anyway. - unsafe { - core::arch::mips::break_(); - } + let _ = print("alloc error! (probably out of memory)"); + exit(3); } diff --git a/startup/README.md b/startup/README.md deleted file mode 100644 index 4c20da8..0000000 --- a/startup/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Startup - -This director contains "a startup stub which functions as an ELF loader". - -It contains both the MIPS assemble version and a compiled binary version. - -## How it works - -This is appended to the start of the binary. - -TODO: Need to figure what it does and why! - diff --git a/startup/startup.bin b/startup/startup.bin deleted file mode 100644 index 6a80327909418f92ad5eb80b4cfacaa3c6981bf9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40 tcmcDKt^aSn_X2|n2Ls#s6%0Hk90CkLTEIr0fua9Dg9h^h1`Y-w008ng3Ge^_ diff --git a/startup/startup.s b/startup/startup.s deleted file mode 100644 index 4289472..0000000 --- a/startup/startup.s +++ /dev/null @@ -1,24 +0,0 @@ - .section .test, "x" - .balign 4 - .set noreorder - .global test - .ent test -test: - -lui $sp, 0x7fff -ori $sp, 0xd000 - -# http://articles.manugarg.com/aboutelfauxiliaryvectors.html -# _AT_PAGESZ = 6 -ori $t0, $0, 6 -sw $t0, 0xC($sp) -ori $t0, $0, 0x1000 -sw $t0, 0x10($sp) - -lw $ra, dat($0) -jr $ra -nop - -dat: - -.end test