diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 935ead6..32e52b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,6 +125,38 @@ jobs: env: RISC0_DEV_MODE: 1 + local-sequencer-integration-tests: + name: Local Sequencer Integration Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: ./.github/actions/install-risc0 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: "1.94.0" + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + ~/.cache/logos-scaffold + target + key: ${{ runner.os }}-cargo-scaffold-test-node-${{ hashFiles('**/Cargo.lock', 'scaffold.toml') }} + restore-keys: | + ${{ runner.os }}-cargo-scaffold-test-node- + ${{ runner.os }}-cargo- + + - name: Local sequencer integration tests + run: cargo test -p integration_tests --features local-sequencer-tests -- --nocapture + env: + RISC0_BUILD_DEBUG: 0 + RISC0_DEV_MODE: 1 + RUST_TEST_THREADS: 1 + build-programs: name: Build Guest Programs runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 368976c..2a48cbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target/ +.scaffold/ *.bin **/*/result diff --git a/Cargo.lock b/Cargo.lock index a4aec91..2d23418 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.8.12" @@ -110,6 +116,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.103" @@ -462,9 +518,9 @@ checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" [[package]] name = "arrayvec" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "ata-methods" @@ -550,6 +606,24 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.13.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.3.0", + "syn 2.0.118", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -770,6 +844,16 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "camino" version = "1.2.4" @@ -809,7 +893,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", - "shlex", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", ] [[package]] @@ -858,6 +953,66 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_complete" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8b397918185f0161ff3d6fcaa9e4bfc09b8367caf6e1d4a2848e5477ed027b" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + [[package]] name = "clock_core" version = "0.1.0" @@ -882,6 +1037,25 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + [[package]] name = "const-hex" version = "1.19.1" @@ -981,6 +1155,30 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crunchy" version = "0.2.4" @@ -1357,6 +1555,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1450,6 +1654,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1468,6 +1682,16 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1522,6 +1746,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "funty" version = "2.0.0" @@ -1639,6 +1872,12 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "group" version = "0.13.0" @@ -1811,7 +2050,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 1.0.8", ] [[package]] @@ -2005,6 +2244,25 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee796ad498c8d9a1d68e477df8f754ed784ef875de1414ebdaf169f70a6a784" +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indenter" version = "0.3.4" @@ -2034,6 +2292,39 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea94e891b3606826e9c998be69ddca42247dad8ad50b1649a5cb7e1c9ae06fd" +dependencies = [ + "libc", +] + [[package]] name = "inout" version = "0.2.2" @@ -2051,9 +2342,13 @@ dependencies = [ "amm_core", "ata-methods", "ata_core", + "base64", + "borsh", "clock_core", "lee", "lee_core", + "logos-scaffold", + "rocksdb", "stablecoin-methods", "stablecoin_core", "token-methods", @@ -2068,6 +2363,12 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -2101,6 +2402,16 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.103" @@ -2181,6 +2492,26 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" +[[package]] +name = "kqueue" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.13.0", + "libc", +] + [[package]] name = "lazy-regex" version = "3.6.0" @@ -2257,6 +2588,16 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libm" version = "0.2.16" @@ -2272,6 +2613,30 @@ dependencies = [ "libc", ] +[[package]] +name = "librocksdb-sys" +version = "0.17.3+10.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef2a00ee60fe526157c9023edab23943fae1ce2ab6f4abb2a807c1746835de9" +dependencies = [ + "bindgen", + "bzip2-sys", + "cc", + "libc", + "libz-sys", +] + +[[package]] +name = "libz-sys" +version = "1.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2290,6 +2655,31 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "logos-scaffold" +version = "0.1.1" +source = "git+https://github.com/logos-co/scaffold.git?rev=9e4d148d08fd86213d3becc17d6863895048e9cb#9e4d148d08fd86213d3becc17d6863895048e9cb" +dependencies = [ + "anyhow", + "base64", + "bs58", + "clap", + "clap_complete", + "flate2", + "include_dir", + "indicatif", + "notify", + "serde", + "serde_json", + "sha2", + "tar", + "tempfile", + "thiserror 2.0.18", + "toml_edit 0.22.27", + "ureq", + "walkdir", +] + [[package]] name = "lru-slab" version = "0.1.2" @@ -2349,6 +2739,34 @@ dependencies = [ "paste", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.2.1" @@ -2391,6 +2809,35 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.13.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio 0.8.11", + "walkdir", + "windows-sys 0.48.0", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2474,6 +2921,12 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "objc" version = "0.2.7" @@ -2489,6 +2942,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "option-ext" version = "0.2.0" @@ -2591,6 +3050,18 @@ dependencies = [ "spki 0.8.0", ] +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "postcard" version = "1.1.3" @@ -2980,7 +3451,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", + "webpki-roots 1.0.8", ] [[package]] @@ -3240,6 +3711,16 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rocksdb" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddb7af00d2b17dbd07d82c0063e25411959748ff03e8d4f96134c2ff41fce34f" +dependencies = [ + "libc", + "librocksdb-sys", +] + [[package]] name = "rrs-lib" version = "0.1.0" @@ -3307,9 +3788,9 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc-hex" @@ -3354,6 +3835,7 @@ version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -3426,6 +3908,15 @@ dependencies = [ "yaml-rust2", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schemars" version = "0.9.0" @@ -3651,6 +4142,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "shlex" version = "2.0.1" @@ -3667,6 +4164,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "slab" version = "0.4.12" @@ -3880,6 +4383,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -4023,7 +4537,7 @@ checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", - "mio", + "mio 1.2.1", "pin-project-lite", "socket2", "windows-sys 0.61.2", @@ -4290,6 +4804,12 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -4313,6 +4833,24 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.11", +] + [[package]] name = "url" version = "2.5.8" @@ -4331,12 +4869,24 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -4352,6 +4902,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -4464,6 +5024,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.8", +] + [[package]] name = "webpki-roots" version = "1.0.8" @@ -4473,6 +5042,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -4532,6 +5110,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -4541,6 +5128,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -4559,6 +5155,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -4592,6 +5203,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4604,6 +5221,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4616,6 +5239,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4640,6 +5269,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4652,6 +5287,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4664,6 +5305,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4676,6 +5323,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4727,6 +5380,16 @@ dependencies = [ "tap", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "yaml-rust2" version = "0.10.4" diff --git a/README.md b/README.md index 91a73ec..0251766 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,17 @@ RISC0_DEV_MODE=1 cargo test -p token_program -p amm_program -p ata_program -p st # Run integration tests (dev mode skips ZK proof generation) RISC0_DEV_MODE=1 cargo test -p integration_tests +# Run integration tests through scaffold-managed local sequencer test nodes. +# RISC0_BUILD_DEBUG=0 keeps embedded guest ELFs on the release profile while +# RISC0_DEV_MODE=1 skips proof generation. +RISC0_BUILD_DEBUG=0 RISC0_DEV_MODE=1 RUST_TEST_THREADS=1 cargo test -p integration_tests --features local-sequencer-tests -- --nocapture + # Run all tests make test ``` +The `local-sequencer-tests` feature mirrors the suite through scaffold-managed test nodes configured by `scaffold.toml`. + Integration tests live in `programs/integration_tests/tests/` and cover `token`, `amm`, and `ata` programs end-to-end through the zkVM using `RISC0_DEV_MODE=1` to skip proof generation. Each test file corresponds to a program: - `programs/integration_tests/tests/token.rs` diff --git a/programs/integration_tests/Cargo.toml b/programs/integration_tests/Cargo.toml index ce1e5de..de8c86e 100644 --- a/programs/integration_tests/Cargo.toml +++ b/programs/integration_tests/Cargo.toml @@ -6,10 +6,22 @@ edition = "2021" [lints] workspace = true +[features] +local-sequencer-tests = [ + "dep:base64", + "dep:borsh", + "dep:logos-scaffold", + "dep:rocksdb", +] + [dependencies] nssa = { workspace = true } nssa_core = { workspace = true, features = ["host", "test_utils"] } clock_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc6" } +base64 = { version = "0.22", optional = true } +borsh = { workspace = true, optional = true } +logos-scaffold = { git = "https://github.com/logos-co/scaffold.git", rev = "9e4d148d08fd86213d3becc17d6863895048e9cb", package = "logos-scaffold", optional = true } +rocksdb = { version = "0.24.0", default-features = false, features = ["snappy", "bindgen-runtime"], optional = true } amm_core = { workspace = true } token_core = { workspace = true } ata_core = { workspace = true } diff --git a/programs/integration_tests/src/lib.rs b/programs/integration_tests/src/lib.rs index 8b13789..36fbf63 100644 --- a/programs/integration_tests/src/lib.rs +++ b/programs/integration_tests/src/lib.rs @@ -1 +1,8 @@ +#[cfg(feature = "local-sequencer-tests")] +mod local_sequencer; +#[cfg(feature = "local-sequencer-tests")] +pub use local_sequencer::TestState; + +#[cfg(not(feature = "local-sequencer-tests"))] +pub type TestState = nssa::V03State; diff --git a/programs/integration_tests/src/local_sequencer.rs b/programs/integration_tests/src/local_sequencer.rs new file mode 100644 index 0000000..c2629cd --- /dev/null +++ b/programs/integration_tests/src/local_sequencer.rs @@ -0,0 +1,635 @@ +use std::{ + env, + error::Error, + fmt::Write as _, + fs, io, + path::{Path, PathBuf}, + sync::OnceLock, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _}; +use borsh::BorshSerialize; +use logos_scaffold::api::{ + testnode::{ + AccountValue, PinOverrides, ProofValue, ReadAt, RejectionPhase, TestNode, TestNodeClient, + TestNodeConfig, TransactionBytes, TransactionOutcome, WaitOptions, + }, + Project, +}; +use nssa::{ + error::LeeError, privacy_preserving_transaction::PrivacyPreservingTransaction, + program_deployment_transaction::ProgramDeploymentTransaction, + public_transaction::PublicTransaction, +}; +use nssa_core::{ + account::{Account, AccountId}, + Commitment, MembershipProof, Nullifier, Timestamp, +}; +use rocksdb::{ColumnFamilyDescriptor, DBWithThreadMode, MultiThreaded, Options}; + +type DynError = Box; +type DynResult = Result; + +const TEST_NODE_CIRCUITS_VERSION_ENV: &str = "LOGOS_SCAFFOLD_TEST_NODE_CIRCUITS_VERSION"; +const RISC0_BUILD_DEBUG_ENV: &str = "RISC0_BUILD_DEBUG"; +const DEFAULT_CIRCUITS_VERSION: &str = "0.4.2"; +const CF_BLOCK_NAME: &str = "cf_block"; +const CF_META_NAME: &str = "cf_meta"; +const CF_NSSA_STATE_NAME: &str = "cf_nssa_state"; +const DB_NSSA_STATE_KEY: &str = "nssa_state"; +const POLL_INTERVAL: Duration = Duration::from_millis(100); +const COMMIT_TIMEOUT: Duration = Duration::from_secs(20); +const HEALTH_TIMEOUT: Duration = Duration::from_secs(60); + +pub struct TestState { + inner: nssa::V03State, + sequencer: Option, + dirty: bool, +} + +impl TestState { + #[must_use] + pub fn new() -> Self { + Self { + inner: nssa::V03State::new(), + sequencer: None, + dirty: true, + } + } + + #[must_use] + pub fn new_with_genesis_accounts( + public_accounts: &[(AccountId, u128)], + private_accounts: Vec<(Commitment, Nullifier)>, + _genesis_timestamp: Timestamp, + ) -> Self { + Self { + inner: nssa::V03State::new() + .with_public_account_balances(public_accounts.iter().copied()) + .with_private_accounts(private_accounts), + sequencer: None, + dirty: true, + } + } + + pub fn transition_from_public_transaction( + &mut self, + tx: &PublicTransaction, + block_id: u64, + timestamp: Timestamp, + ) -> Result<(), LeeError> { + let rpc_tx = RpcTransaction::Public(Box::new(tx.clone())); + match self.mirror_transaction(&rpc_tx) { + MirrorOutcome::Committed(context) => { + let mut next = self.inner.clone(); + next.transition_from_public_transaction(tx, context.block_id, context.timestamp) + .unwrap_or_else(|err| { + panic!( + "local replay rejected public transaction committed by sequencer at \ + block {}: {err}", + context.block_id + ) + }); + self.inner = next; + self.assert_affected_accounts_match(&rpc_tx); + Ok(()) + } + MirrorOutcome::NotCommitted(rejection) => { + let mut expected = self.inner.clone(); + let result = if let Some(context) = rejection.validation_context { + expected.transition_from_public_transaction( + tx, + context.block_id, + context.timestamp, + ) + } else { + expected.transition_from_public_transaction(tx, block_id, timestamp) + }; + + if result.is_ok() { + panic!("local replay accepted public transaction dropped by sequencer"); + } + result + } + } + } + + pub fn transition_from_privacy_preserving_transaction( + &mut self, + tx: &PrivacyPreservingTransaction, + block_id: u64, + timestamp: Timestamp, + ) -> Result<(), LeeError> { + let rpc_tx = RpcTransaction::PrivacyPreserving(Box::new(tx.clone())); + match self.mirror_transaction(&rpc_tx) { + MirrorOutcome::Committed(context) => { + let mut next = self.inner.clone(); + next.transition_from_privacy_preserving_transaction( + tx, + context.block_id, + context.timestamp, + ) + .unwrap_or_else(|err| { + panic!( + "local replay rejected privacy-preserving transaction committed by \ + sequencer at block {}: {err}", + context.block_id + ) + }); + self.inner = next; + self.assert_affected_accounts_match(&rpc_tx); + Ok(()) + } + MirrorOutcome::NotCommitted(rejection) => { + let mut expected = self.inner.clone(); + let result = if let Some(context) = rejection.validation_context { + expected.transition_from_privacy_preserving_transaction( + tx, + context.block_id, + context.timestamp, + ) + } else { + expected.transition_from_privacy_preserving_transaction(tx, block_id, timestamp) + }; + + if result.is_ok() { + panic!( + "local replay accepted privacy-preserving transaction dropped by sequencer" + ); + } + result + } + } + } + + pub fn transition_from_program_deployment_transaction( + &mut self, + tx: &ProgramDeploymentTransaction, + ) -> Result<(), LeeError> { + let rpc_tx = RpcTransaction::ProgramDeployment(Box::new(tx.clone())); + match self.mirror_transaction(&rpc_tx) { + MirrorOutcome::Committed(context) => { + let mut next = self.inner.clone(); + next.transition_from_program_deployment_transaction(tx) + .unwrap_or_else(|err| { + panic!( + "local replay rejected program deployment committed by sequencer at \ + block {}: {err}", + context.block_id + ) + }); + self.inner = next; + self.assert_affected_accounts_match(&rpc_tx); + Ok(()) + } + MirrorOutcome::NotCommitted(_) => { + let mut expected = self.inner.clone(); + let result = expected.transition_from_program_deployment_transaction(tx); + if result.is_ok() { + panic!("local replay accepted program deployment dropped by sequencer"); + } + result + } + } + } + + pub fn force_insert_account(&mut self, account_id: AccountId, account: Account) { + self.inner.force_insert_account(account_id, account); + self.dirty = true; + } + + #[must_use] + pub fn get_account_by_id(&self, account_id: AccountId) -> Account { + let account = self.inner.get_account_by_id(account_id); + if !self.dirty { + if let Some(sequencer) = &self.sequencer { + let sequencer_account = sequencer + .get_account_by_id(account_id) + .unwrap_or_else(|err| panic!("local sequencer getAccount failed: {err}")); + assert_eq!( + sequencer_account, account, + "local sequencer account state diverged for {account_id}" + ); + } + } + account + } + + #[must_use] + pub fn get_proof_for_commitment(&self, commitment: &Commitment) -> Option { + let proof = self.inner.get_proof_for_commitment(commitment); + if !self.dirty { + if let Some(sequencer) = &self.sequencer { + let sequencer_proof = sequencer + .get_proof_for_commitment(commitment) + .unwrap_or_else(|err| { + panic!("local sequencer getProofForCommitment failed: {err}") + }); + assert_eq!( + sequencer_proof, proof, + "local sequencer commitment proof diverged" + ); + } + } + proof + } + + fn mirror_transaction(&mut self, tx: &RpcTransaction) -> MirrorOutcome { + let tx_hash = hex_encode(&tx.hash()); + let outcome = self + .ensure_sequencer() + .submit_and_wait(tx, COMMIT_TIMEOUT) + .unwrap_or_else(|err| panic!("local sequencer failed to submit {tx_hash}: {err}")); + + match outcome { + TransactionOutcome::Committed { block, .. } => { + MirrorOutcome::Committed(ObservedBlock { + block_id: block.block_id, + timestamp: block.timestamp, + }) + } + TransactionOutcome::Rejected { + phase: RejectionPhase::Stateless, + .. + } => MirrorOutcome::NotCommitted(RejectionContext::precheck()), + TransactionOutcome::Rejected { + phase: RejectionPhase::Stateful, + observed_after_block_id, + .. + } => { + let validation_context = observed_after_block_id + .and_then(|block_id| self.sequencer.as_ref()?.block_context(block_id).ok()); + MirrorOutcome::NotCommitted(RejectionContext { validation_context }) + } + TransactionOutcome::Timeout { + last_observed_block_id, + .. + } => { + panic!( + "local sequencer timed out waiting for {tx_hash}; last observed block \ + {last_observed_block_id}" + ) + } + TransactionOutcome::TransportError { operation, message } => { + panic!( + "local sequencer transport error during {operation} for {tx_hash}: {message}" + ) + } + TransactionOutcome::WireMismatch { .. } => { + panic!("local sequencer echoed different transaction bytes for {tx_hash}") + } + } + } + + fn ensure_sequencer(&mut self) -> &mut LocalSequencer { + if self.sequencer.is_none() || self.dirty { + self.sequencer = Some( + LocalSequencer::spawn(&self.inner) + .unwrap_or_else(|err| panic!("failed to start local sequencer: {err}")), + ); + self.dirty = false; + } + + match &mut self.sequencer { + Some(sequencer) => sequencer, + None => unreachable!("local sequencer should be initialized"), + } + } + + fn assert_affected_accounts_match(&self, tx: &RpcTransaction) { + let Some(sequencer) = &self.sequencer else { + return; + }; + + let account_ids = tx.affected_public_account_ids(); + let sequencer_accounts = sequencer + .get_accounts_by_id(&account_ids) + .unwrap_or_else(|err| panic!("local sequencer batch getAccount failed: {err}")); + for (account_id, sequencer_account) in account_ids.into_iter().zip(sequencer_accounts) { + let account = self.inner.get_account_by_id(account_id); + assert_eq!( + sequencer_account, account, + "local sequencer account state diverged for {account_id}" + ); + } + } +} + +enum MirrorOutcome { + Committed(ObservedBlock), + NotCommitted(RejectionContext), +} + +struct RejectionContext { + validation_context: Option, +} + +impl RejectionContext { + const fn precheck() -> Self { + Self { + validation_context: None, + } + } +} + +#[derive(Clone, Copy)] +struct ObservedBlock { + block_id: u64, + timestamp: Timestamp, +} + +fn ensure_risc0_dev_mode() -> DynResult<()> { + if let Some(value) = env::var_os("RISC0_DEV_MODE") { + let value = value.to_string_lossy(); + if matches!(value.trim(), "0" | "false") { + return Err(io::Error::other(format!( + "RISC0_DEV_MODE={value} disables dev mode, but the local sequencer harness requires it; unset RISC0_DEV_MODE or set it to 1" + )) + .into()); + } + } + Ok(()) +} + +fn ensure_release_guest_builds() -> DynResult<()> { + if let Some(value) = env::var_os(RISC0_BUILD_DEBUG_ENV) { + let value = value.to_string_lossy(); + if value.trim() == "1" { + return Err(io::Error::other(format!( + "{RISC0_BUILD_DEBUG_ENV}=1 enables debug-profile guest ELFs, but local sequencer tests require release-profile guest ELFs; unset {RISC0_BUILD_DEBUG_ENV} or set it to 0" + )) + .into()); + } + } + Ok(()) +} + +struct LocalSequencer { + // Fields drop in declaration order; shut the node down before seed cleanup. + _node: TestNode, + client: TestNodeClient, + _seed_dir: SeedDirGuard, +} + +impl LocalSequencer { + fn spawn(state: &nssa::V03State) -> DynResult { + ensure_risc0_dev_mode()?; + ensure_release_guest_builds()?; + let seed_dir = SeedDirGuard::from_state(state)?; + let config = TestNodeConfig { + state: Some(seed_dir.path().to_path_buf()), + timeout_sec: HEALTH_TIMEOUT.as_secs(), + ..Default::default() + }; + let node = TestNode::start(scaffold_project(), &config) + .map_err(|err| io::Error::other(format!("scaffold test-node start failed: {err}")))?; + Ok(Self { + client: node.client(), + _node: node, + _seed_dir: seed_dir, + }) + } + + fn submit_and_wait( + &self, + tx: &RpcTransaction, + timeout: Duration, + ) -> DynResult { + let bytes = TransactionBytes::borsh(borsh::to_vec(tx)?); + Ok(self.client.submit_and_wait( + &bytes, + &WaitOptions { + timeout, + rejection_blocks: 1, + poll_interval: POLL_INTERVAL, + ..Default::default() + }, + )) + } + + fn block_context(&self, block_id: u64) -> DynResult { + let block = self.client.block_info(block_id)?.ok_or_else(|| { + io::Error::new( + io::ErrorKind::NotFound, + format!("sequencer block {block_id} was not found"), + ) + })?; + Ok(ObservedBlock { + block_id: block.block_id, + timestamp: block.timestamp, + }) + } + + fn get_account_by_id(&self, account_id: AccountId) -> DynResult { + let read = self + .client + .account(&account_id.to_string(), ReadAt::Latest)?; + account_from_value(account_id, read.value) + } + + fn get_accounts_by_id(&self, account_ids: &[AccountId]) -> DynResult> { + let ids = account_ids + .iter() + .map(ToString::to_string) + .collect::>(); + let read = self.client.accounts(&ids, ReadAt::Latest)?; + read.accounts + .into_iter() + .zip(account_ids) + .map(|(entry, account_id)| account_from_value(*account_id, entry.value)) + .collect() + } + + fn get_proof_for_commitment( + &self, + commitment: &Commitment, + ) -> DynResult> { + let commitment_hex = hex_encode(&commitment.to_byte_array()); + let read = self.client.proof(&commitment_hex, ReadAt::Latest)?; + read.proof.map(proof_from_value).transpose() + } +} + +struct SeedDirGuard { + path: PathBuf, +} + +impl SeedDirGuard { + fn from_state(state: &nssa::V03State) -> DynResult { + let path = local_sequencer_seed_dir()?; + if path.exists() { + fs::remove_dir_all(&path)?; + } + fs::create_dir_all(&path)?; + seed_sequencer_state(&path, state)?; + Ok(Self { path }) + } + + fn path(&self) -> &Path { + &self.path + } +} + +impl Drop for SeedDirGuard { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.path); + } +} + +#[derive(BorshSerialize)] +struct NssaStateCellRef<'state>(&'state nssa::V03State); + +fn seed_sequencer_state(seed_dir: &Path, state: &nssa::V03State) -> DynResult<()> { + // Scaffold snapshot seeding cannot represent full public account data at + // this LEZ pin, while these fixtures use force-inserted public accounts. + let mut cf_opts = Options::default(); + cf_opts.set_max_write_buffer_number(16); + let cfb = ColumnFamilyDescriptor::new(CF_BLOCK_NAME, cf_opts.clone()); + let cfmeta = ColumnFamilyDescriptor::new(CF_META_NAME, cf_opts.clone()); + let cfstate = ColumnFamilyDescriptor::new(CF_NSSA_STATE_NAME, cf_opts); + + let mut db_opts = Options::default(); + db_opts.create_missing_column_families(true); + db_opts.create_if_missing(true); + let db = DBWithThreadMode::::open_cf_descriptors( + &db_opts, + seed_dir.join("rocksdb"), + vec![cfb, cfmeta, cfstate], + )?; + let state_column = db + .cf_handle(CF_NSSA_STATE_NAME) + .ok_or_else(|| io::Error::other("state column family not created"))?; + db.put_cf( + &state_column, + borsh::to_vec(&DB_NSSA_STATE_KEY)?, + borsh::to_vec(&NssaStateCellRef(state))?, + )?; + Ok(()) +} + +fn scaffold_project() -> &'static Project { + static PROJECT: OnceLock = OnceLock::new(); + + PROJECT.get_or_init(|| { + let root = repo_root(); + let project = Project::open(&root).unwrap_or_else(|err| { + panic!( + "failed to open scaffold project at {}: {err}", + root.display() + ) + }); + let circuits_version = env::var(TEST_NODE_CIRCUITS_VERSION_ENV) + .unwrap_or_else(|_| DEFAULT_CIRCUITS_VERSION.to_owned()); + logos_scaffold::api::testnode::prepare_test_node( + &project, + &PinOverrides { + circuits_version: Some(circuits_version), + ..Default::default() + }, + None, + ) + .unwrap_or_else(|err| panic!("failed to prepare scaffold test node: {err}")); + project + }) +} + +fn repo_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .expect("integration_tests crate lives under programs/integration_tests") + .to_path_buf() +} + +fn local_sequencer_seed_dir() -> DynResult { + let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos(); + Ok(repo_root().join(format!( + "target/local-sequencer/seeds/{}-{timestamp}", + std::process::id() + ))) +} + +fn account_from_value(account_id: AccountId, value: AccountValue) -> DynResult { + match value { + AccountValue::Missing => Ok(Account::default()), + AccountValue::Present { encoded, .. } => { + let bytes = BASE64_STANDARD.decode(encoded)?; + Ok(borsh::from_slice(&bytes)?) + } + AccountValue::DecodeError { message, .. } => Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("sequencer account {account_id} could not be decoded: {message}"), + ) + .into()), + } +} + +fn proof_from_value(proof: ProofValue) -> DynResult { + let leaf_index = usize::try_from(proof.leaf_index).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("proof leaf index {} does not fit usize", proof.leaf_index), + ) + })?; + let path = proof + .path + .iter() + .map(|node| parse_hex_32(node)) + .collect::>>()?; + Ok((leaf_index, path)) +} + +fn parse_hex_32(value: &str) -> DynResult<[u8; 32]> { + let bytes = value.as_bytes(); + if bytes.len() != 64 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("expected 64 hex chars, got {}", bytes.len()), + ) + .into()); + } + + let mut out = [0_u8; 32]; + for (index, chunk) in bytes.chunks_exact(2).enumerate() { + let slot = out + .get_mut(index) + .ok_or_else(|| io::Error::other("hex output index out of bounds"))?; + let pair = std::str::from_utf8(chunk)?; + *slot = u8::from_str_radix(pair, 16)?; + } + Ok(out) +} + +#[derive(BorshSerialize)] +enum RpcTransaction { + Public(Box), + PrivacyPreserving(Box), + ProgramDeployment(Box), +} + +impl RpcTransaction { + fn hash(&self) -> [u8; 32] { + match self { + Self::Public(tx) => tx.hash(), + Self::PrivacyPreserving(tx) => tx.hash(), + Self::ProgramDeployment(tx) => tx.hash(), + } + } + + fn affected_public_account_ids(&self) -> Vec { + match self { + Self::Public(tx) => tx.affected_public_account_ids(), + Self::PrivacyPreserving(tx) => tx.affected_public_account_ids(), + Self::ProgramDeployment(tx) => tx.affected_public_account_ids(), + } + } +} + +fn hex_encode(bytes: &[u8]) -> String { + let mut output = String::with_capacity(bytes.len().saturating_mul(2)); + for byte in bytes { + write!(&mut output, "{byte:02x}").expect("writing to String cannot fail"); + } + output +} diff --git a/programs/integration_tests/tests/amm.rs b/programs/integration_tests/tests/amm.rs index af711aa..dacf03f 100644 --- a/programs/integration_tests/tests/amm.rs +++ b/programs/integration_tests/tests/amm.rs @@ -8,10 +8,11 @@ use amm_core::{ MINIMUM_LIQUIDITY, }; use clock_core::{ClockAccountData, CLOCK_01_PROGRAM_ACCOUNT_ID}; +use integration_tests::TestState as V03State; use nssa::{ error::LeeError, program_deployment_transaction::{self, ProgramDeploymentTransaction}, - public_transaction, PrivateKey, PublicKey, PublicTransaction, V03State, + public_transaction, PrivateKey, PublicKey, PublicTransaction, }; use nssa_core::account::{Account, AccountId, Data, Nonce}; use token_core::{TokenDefinition, TokenHolding}; diff --git a/programs/integration_tests/tests/ata.rs b/programs/integration_tests/tests/ata.rs index 6e5d016..253a8a9 100644 --- a/programs/integration_tests/tests/ata.rs +++ b/programs/integration_tests/tests/ata.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use ata_core::{compute_ata_seed, get_associated_token_account_id}; +use integration_tests::TestState as V03State; use nssa::{ execute_and_prove, privacy_preserving_transaction::{ @@ -8,7 +9,7 @@ use nssa::{ }, program::Program, program_deployment_transaction::{self, ProgramDeploymentTransaction}, - public_transaction, PrivateKey, PublicKey, PublicTransaction, SharedSecretKey, V03State, + public_transaction, PrivateKey, PublicKey, PublicTransaction, SharedSecretKey, }; use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Data, Nonce}, diff --git a/programs/integration_tests/tests/stablecoin.rs b/programs/integration_tests/tests/stablecoin.rs index d72ad67..bec0e32 100644 --- a/programs/integration_tests/tests/stablecoin.rs +++ b/programs/integration_tests/tests/stablecoin.rs @@ -1,6 +1,7 @@ +use integration_tests::TestState as V03State; use nssa::{ program_deployment_transaction::{self, ProgramDeploymentTransaction}, - public_transaction, PrivateKey, PublicKey, PublicTransaction, V03State, + public_transaction, PrivateKey, PublicKey, PublicTransaction, }; use nssa_core::account::{Account, AccountId, Data, Nonce}; use stablecoin_core::{compute_position_pda, compute_position_vault_pda, Position}; diff --git a/programs/integration_tests/tests/token.rs b/programs/integration_tests/tests/token.rs index bb276d2..ca454ed 100644 --- a/programs/integration_tests/tests/token.rs +++ b/programs/integration_tests/tests/token.rs @@ -1,9 +1,10 @@ +use integration_tests::TestState as V03State; use nssa::{ execute_and_prove, privacy_preserving_transaction::{Message, PrivacyPreservingTransaction, WitnessSet}, program::Program, program_deployment_transaction::{self, ProgramDeploymentTransaction}, - public_transaction, PrivateKey, PublicKey, PublicTransaction, SharedSecretKey, V03State, + public_transaction, PrivateKey, PublicKey, PublicTransaction, SharedSecretKey, }; use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Data, Nonce}, diff --git a/scaffold.toml b/scaffold.toml new file mode 100644 index 0000000..7d33239 --- /dev/null +++ b/scaffold.toml @@ -0,0 +1,25 @@ +[scaffold] +version = "0.2.0" + +[repos.lez] +source = "https://github.com/logos-blockchain/logos-execution-zone.git" +pin = "cf3639d8252040d13b3d4e933feb19b42c76e14a" + +[repos.spel] +source = "https://github.com/logos-co/spel.git" +pin = "84f50d4aa473a70b72a16a7fb468c5618277cdd7" + +[wallet] +home_dir = ".scaffold/wallet" + +[framework] +kind = "default" +version = "0.1.0" + +[framework.idl] +spec = "lssa-idl/0.1.0" +path = "artifacts" + +[localnet] +port = 3040 +risc0_dev_mode = true