diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21c73d29..a8efedd9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,7 +83,7 @@ jobs: RISC0_SKIP_BUILD: "1" run: cargo clippy -p "*programs" -- -D warnings - unit-tests: + tests: runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -99,12 +99,13 @@ jobs: run: rustup install - name: Install nextest - run: cargo install cargo-nextest + run: cargo install --locked cargo-nextest - - name: Run unit tests + - name: Run tests env: RISC0_DEV_MODE: "1" - run: cargo nextest run --no-fail-fast + RUST_LOG: "info" + run: cargo nextest run --no-fail-fast -- --skip tps_test valid-proof-test: runs-on: ubuntu-latest @@ -123,31 +124,8 @@ jobs: - name: Test valid proof env: - NSSA_WALLET_HOME_DIR: ./integration_tests/configs/debug/wallet RUST_LOG: "info" - run: cargo run --bin integration_tests -- ./integration_tests/configs/debug/ test_success_private_transfer_to_another_owned_account - - integration-tests: - runs-on: ubuntu-latest - timeout-minutes: 120 - steps: - - uses: actions/checkout@v5 - with: - ref: ${{ github.head_ref }} - - - uses: ./.github/actions/install-system-deps - - - uses: ./.github/actions/install-risc0 - - - name: Install active toolchain - run: rustup install - - - name: Run integration tests - env: - NSSA_WALLET_HOME_DIR: ./integration_tests/configs/debug/wallet - RUST_LOG: "info" - RISC0_DEV_MODE: "1" - run: cargo run --bin integration_tests -- ./integration_tests/configs/debug/ all + run: cargo test -p integration_tests -- --exact private::private_transfer_to_owned_account artifacts: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index eeb8f5ba..16baf5a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,7 +69,7 @@ dependencies = [ "actix-rt", "actix-service", "actix-utils", - "base64 0.22.1", + "base64", "bitflags 2.10.0", "bytes", "bytestring", @@ -77,7 +77,7 @@ dependencies = [ "encoding_rs", "foldhash", "futures-core", - "h2", + "h2 0.3.27", "http 0.2.12", "httparse", "httpdate", @@ -236,7 +236,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -367,16 +367,54 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "archery" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d" +dependencies = [ + "triomphe", +] + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + [[package]] name = "ark-bn254" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" dependencies = [ - "ark-ec", - "ark-ff", + "ark-ec 0.5.0", + "ark-ff 0.5.0", "ark-r1cs-std", - "ark-std", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-crypto-primitives" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3a13b34da09176a8baba701233fdffbaa7c1b1192ce031a3da4e55ce1f1a56" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-relations 0.4.0", + "ark-serialize 0.4.2", + "ark-snark 0.4.0", + "ark-std 0.4.0", + "blake2", + "derivative", + "digest", + "sha2", ] [[package]] @@ -387,12 +425,12 @@ checksum = "1e0c292754729c8a190e50414fd1a37093c786c709899f29c9f7daccecfa855e" dependencies = [ "ahash 0.8.12", "ark-crypto-primitives-macros", - "ark-ec", - "ark-ff", - "ark-relations", - "ark-serialize", - "ark-snark", - "ark-std", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-relations 0.5.1", + "ark-serialize 0.5.0", + "ark-snark 0.5.1", + "ark-std 0.5.0", "blake2", "derivative", "digest", @@ -412,6 +450,23 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ec" version = "0.5.0" @@ -419,10 +474,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" dependencies = [ "ahash 0.8.12", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", "educe", "fnv", "hashbrown 0.15.5", @@ -433,16 +488,36 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", "arrayvec", "digest", "educe", @@ -453,6 +528,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-asm" version = "0.5.0" @@ -463,6 +548,19 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-macros" version = "0.5.0" @@ -476,19 +574,47 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "ark-groth16" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20ceafa83848c3e390f1cbf124bc3193b3e639b3f02009e0e290809a501b95fc" +dependencies = [ + "ark-crypto-primitives 0.4.0", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-relations 0.4.0", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + [[package]] name = "ark-groth16" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88f1d0f3a534bb54188b8dcc104307db6c56cdae574ddc3212aec0625740fc7e" dependencies = [ - "ark-crypto-primitives", - "ark-ec", - "ark-ff", - "ark-poly", - "ark-relations", - "ark-serialize", - "ark-std", + "ark-crypto-primitives 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-relations 0.5.1", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", ] [[package]] @@ -498,9 +624,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" dependencies = [ "ahash 0.8.12", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", "educe", "fnv", "hashbrown 0.15.5", @@ -512,10 +638,10 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" dependencies = [ - "ark-ec", - "ark-ff", - "ark-relations", - "ark-std", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-relations 0.5.1", + "ark-std 0.5.0", "educe", "num-bigint", "num-integer", @@ -523,31 +649,66 @@ dependencies = [ "tracing", ] +[[package]] +name = "ark-relations" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00796b6efc05a3f48225e59cb6a2cda78881e7c390872d5786aaf112f31fb4f0" +dependencies = [ + "ark-ff 0.4.2", + "ark-std 0.4.0", + "tracing", + "tracing-subscriber", +] + [[package]] name = "ark-relations" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" dependencies = [ - "ark-ff", - "ark-std", + "ark-ff 0.5.0", + "ark-std 0.5.0", "tracing", "tracing-subscriber", ] +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive 0.4.2", + "ark-std 0.4.0", + "digest", + "num-bigint", +] + [[package]] name = "ark-serialize" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" dependencies = [ - "ark-serialize-derive", - "ark-std", + "ark-serialize-derive 0.5.0", + "ark-std 0.5.0", "arrayvec", "digest", "num-bigint", ] +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-serialize-derive" version = "0.5.0" @@ -559,16 +720,38 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "ark-snark" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d3cc6833a335bb8a600241889ead68ee89a3cf8448081fb7694c0fe503da63" +dependencies = [ + "ark-ff 0.4.2", + "ark-relations 0.4.0", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + [[package]] name = "ark-snark" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d368e2848c2d4c129ce7679a7d0d2d612b6a274d3ea6a13bad4445d61b381b88" dependencies = [ - "ark-ff", - "ark-relations", - "ark-serialize", - "ark-std", + "ark-ff 0.5.0", + "ark-relations 0.5.1", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", ] [[package]] @@ -587,6 +770,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -615,6 +804,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -627,24 +827,89 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.4.0", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + [[package]] name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + [[package]] name = "base58" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -657,6 +922,16 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +[[package]] +name = "bedrock_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "logos-blockchain-common-http-client", + "logos-blockchain-core", + "reqwest", +] + [[package]] name = "bincode" version = "1.3.3" @@ -750,7 +1025,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -761,9 +1036,9 @@ checksum = "21055e2f49cbbdbfe9f8f96d597c5527b0c6ab7933341fdc2f147180e48a988e" dependencies = [ "duplicate", "maybe-async", - "reqwest 0.12.26", + "reqwest", "serde", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -789,6 +1064,15 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -878,7 +1162,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -899,7 +1183,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -914,6 +1198,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "cfg_eval" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "chacha20" version = "0.9.1" @@ -1006,7 +1301,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -1020,17 +1315,17 @@ name = "common" version = "0.1.0" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "borsh", "hex", "log", "nssa", "nssa_core", - "reqwest 0.11.27", + "reqwest", "serde", "serde_json", "sha2", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -1046,18 +1341,45 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "const-hex" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +dependencies = [ + "cfg-if", + "cpufeatures", + "proptest", + "serde_core", +] + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + [[package]] name = "convert_case" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "convert_case" version = "0.10.0" @@ -1094,6 +1416,15 @@ dependencies = [ "libc", ] +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1118,13 +1449,19 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", "subtle", "zeroize", @@ -1136,7 +1473,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", "typenum", ] @@ -1150,6 +1487,34 @@ dependencies = [ "cipher", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "darling" version = "0.20.11" @@ -1220,6 +1585,45 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "data-encoding-macro" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" +dependencies = [ + "data-encoding", + "syn 2.0.111", +] + [[package]] name = "der" version = "0.7.10" @@ -1407,6 +1811,31 @@ dependencies = [ "spki", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "serde", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "educe" version = "0.6.0" @@ -1441,7 +1870,7 @@ dependencies = [ "crypto-bigint", "digest", "ff", - "generic-array", + "generic-array 0.14.7", "group", "pem-rfc7468", "pkcs8", @@ -1561,6 +1990,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.5" @@ -1630,6 +2065,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "forwarded-header-value" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" +dependencies = [ + "nonempty", + "thiserror 1.0.69", +] + [[package]] name = "futures" version = "0.3.31" @@ -1701,6 +2146,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -1730,6 +2181,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "generic-array" +version = "1.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf57c49a95fd1fe24b90b3033bee6dc7e8f1288d51494cb44e627c295e38542" +dependencies = [ + "rustversion", + "serde_core", + "typenum", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -1773,6 +2235,26 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "governor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot", + "portable-atomic", + "quanta", + "rand 0.8.5", + "smallvec", + "spinning_top", +] + [[package]] name = "group" version = "0.13.0" @@ -1803,12 +2285,46 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap 2.12.1", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.12", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1873,6 +2389,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1909,17 +2434,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -1939,7 +2453,7 @@ dependencies = [ "bytes", "futures-core", "http 1.4.0", - "http-body 1.0.1", + "http-body", "pin-project-lite", ] @@ -1961,30 +2475,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.8.1" @@ -1995,9 +2485,11 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2 0.4.13", "http 1.4.0", - "http-body 1.0.1", + "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -2013,7 +2505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.4.0", - "hyper 1.8.1", + "hyper", "hyper-util", "rustls", "rustls-pki-types", @@ -2025,15 +2517,18 @@ dependencies = [ [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", - "hyper 0.14.32", + "http-body-util", + "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", ] [[package]] @@ -2042,22 +2537,24 @@ version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-core", "futures-util", "http 1.4.0", - "http-body 1.0.1", - "hyper 1.8.1", + "http-body", + "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.5.10", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2241,27 +2738,25 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] name = "integration_tests" version = "0.1.0" dependencies = [ - "actix", "actix-web", "anyhow", - "base64 0.22.1", + "base64", "borsh", - "clap", "common", "env_logger", + "futures", "hex", "key_protocol", "log", "nssa", "nssa_core", - "proc_macro_test_attribute", "sequencer_core", "sequencer_runner", "tempfile", @@ -2302,6 +2797,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -2326,6 +2830,31 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jf-crhf" +version = "0.1.1" +source = "git+https://github.com/EspressoSystems/jellyfish?tag=jf-crhf-v0.1.1#8f3dce0bc2bd161b4648f6ac029dcc1a23aaf4c5" +dependencies = [ + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "jf-poseidon2" +version = "0.1.0" +source = "git+https://github.com/EspressoSystems/jellyfish.git?rev=dc166cf0f803c3e5067f9dfcc21e3dade986a447#dc166cf0f803c3e5067f9dfcc21e3dade986a447" +dependencies = [ + "ark-bn254 0.4.0", + "ark-ff 0.4.2", + "ark-std 0.4.0", + "displaydoc", + "hex", + "jf-crhf", + "lazy_static", + "nimue", + "zeroize", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -2388,7 +2917,7 @@ dependencies = [ "rand 0.8.5", "serde", "sha2", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -2451,6 +2980,20 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libp2p-identity" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3" +dependencies = [ + "bs58", + "hkdf", + "multihash", + "sha2", + "thiserror 2.0.17", + "tracing", +] + [[package]] name = "libredox" version = "0.1.11" @@ -2485,6 +3028,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "light-poseidon" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a1ccadd0bb5a32c196da536fd72c59183de24a055f6bf0513bf845fefab862" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "num-bigint", + "thiserror 1.0.69", +] + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -2529,6 +3084,483 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "logos-blockchain-blend-crypto" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "blake2", + "logos-blockchain-groth16", + "logos-blockchain-poq", + "logos-blockchain-poseidon2", + "logos-blockchain-utils", + "rs-merkle-tree", + "thiserror 1.0.69", +] + +[[package]] +name = "logos-blockchain-blend-message" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "blake2", + "derivative", + "itertools 0.14.0", + "logos-blockchain-blend-crypto", + "logos-blockchain-blend-proofs", + "logos-blockchain-core", + "logos-blockchain-groth16", + "logos-blockchain-key-management-system-keys", + "logos-blockchain-utils", + "serde", + "serde-big-array", + "serde_with", + "thiserror 1.0.69", + "tracing", + "zeroize", +] + +[[package]] +name = "logos-blockchain-blend-proofs" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "ed25519-dalek", + "generic-array 1.3.5", + "logos-blockchain-blend-crypto", + "logos-blockchain-groth16", + "logos-blockchain-poq", + "num-bigint", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "logos-blockchain-chain-broadcast-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "derivative", + "futures", + "logos-blockchain-core", + "overwatch", + "serde", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "logos-blockchain-chain-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "bytes", + "futures", + "logos-blockchain-chain-broadcast-service", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-engine", + "logos-blockchain-cryptarchia-sync", + "logos-blockchain-groth16", + "logos-blockchain-ledger", + "logos-blockchain-network-service", + "logos-blockchain-services-utils", + "logos-blockchain-storage-service", + "logos-blockchain-time-service", + "logos-blockchain-utils", + "num-bigint", + "overwatch", + "serde", + "serde_with", + "strum", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-futures", +] + +[[package]] +name = "logos-blockchain-circuits-prover" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "logos-blockchain-circuits-utils", + "tempfile", +] + +[[package]] +name = "logos-blockchain-circuits-utils" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "dirs", +] + +[[package]] +name = "logos-blockchain-common-http-client" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "futures", + "logos-blockchain-chain-broadcast-service", + "logos-blockchain-chain-service", + "logos-blockchain-core", + "logos-blockchain-http-api-common", + "reqwest", + "serde", + "serde_json", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "logos-blockchain-core" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "ark-ff 0.4.2", + "bincode", + "blake2", + "bytes", + "const-hex", + "futures", + "generic-array 1.3.5", + "hex", + "logos-blockchain-blend-proofs", + "logos-blockchain-cryptarchia-engine", + "logos-blockchain-groth16", + "logos-blockchain-key-management-system-keys", + "logos-blockchain-pol", + "logos-blockchain-poseidon2", + "logos-blockchain-utils", + "multiaddr", + "nom 8.0.0", + "num-bigint", + "serde", + "strum", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "logos-blockchain-cryptarchia-engine" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "cfg_eval", + "logos-blockchain-utils", + "serde", + "serde_with", + "thiserror 1.0.69", + "time", + "tokio", + "tracing", +] + +[[package]] +name = "logos-blockchain-cryptarchia-sync" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "bytes", + "futures", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-engine", + "rand 0.8.5", + "serde", + "serde_with", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "logos-blockchain-groth16" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "ark-bn254 0.4.0", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-groth16 0.4.0", + "ark-serialize 0.4.2", + "generic-array 1.3.5", + "hex", + "num-bigint", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "logos-blockchain-http-api-common" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "axum", + "governor", + "logos-blockchain-core", + "logos-blockchain-key-management-system-keys", + "serde", + "serde_json", + "serde_with", + "tower_governor", +] + +[[package]] +name = "logos-blockchain-key-management-system-keys" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "bytes", + "ed25519-dalek", + "generic-array 1.3.5", + "logos-blockchain-blend-proofs", + "logos-blockchain-groth16", + "logos-blockchain-key-management-system-macros", + "logos-blockchain-poseidon2", + "logos-blockchain-utils", + "logos-blockchain-zksign", + "num-bigint", + "rand_core 0.6.4", + "serde", + "subtle", + "thiserror 2.0.17", + "tokio", + "tracing", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "logos-blockchain-key-management-system-macros" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "logos-blockchain-key-management-system-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "log", + "logos-blockchain-key-management-system-keys", + "overwatch", + "serde", + "thiserror 2.0.17", + "tokio", + "tracing", +] + +[[package]] +name = "logos-blockchain-ledger" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "derivative", + "logos-blockchain-blend-crypto", + "logos-blockchain-blend-message", + "logos-blockchain-blend-proofs", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-engine", + "logos-blockchain-groth16", + "logos-blockchain-key-management-system-keys", + "logos-blockchain-mmr", + "logos-blockchain-utils", + "logos-blockchain-utxotree", + "num-bigint", + "rand 0.8.5", + "rpds", + "serde", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "logos-blockchain-mmr" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "ark-ff 0.4.2", + "logos-blockchain-groth16", + "logos-blockchain-poseidon2", + "rpds", + "serde", +] + +[[package]] +name = "logos-blockchain-network-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "futures", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-sync", + "overwatch", + "serde", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "logos-blockchain-pol" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-utils", + "logos-blockchain-groth16", + "logos-blockchain-witness-generator", + "num-bigint", + "num-traits", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "logos-blockchain-poq" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-utils", + "logos-blockchain-groth16", + "logos-blockchain-pol", + "logos-blockchain-witness-generator", + "num-bigint", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "logos-blockchain-poseidon2" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "ark-bn254 0.4.0", + "ark-ff 0.4.2", + "jf-poseidon2", + "num-bigint", +] + +[[package]] +name = "logos-blockchain-services-utils" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "futures", + "log", + "overwatch", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "logos-blockchain-storage-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "bytes", + "futures", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-engine", + "overwatch", + "serde", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "logos-blockchain-time-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "futures", + "log", + "logos-blockchain-cryptarchia-engine", + "overwatch", + "sntpc", + "thiserror 2.0.17", + "time", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "logos-blockchain-utils" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "blake2", + "cipher", + "const-hex", + "humantime", + "overwatch", + "rand 0.8.5", + "serde", + "serde_with", + "time", +] + +[[package]] +name = "logos-blockchain-utxotree" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "ark-ff 0.4.2", + "logos-blockchain-core", + "logos-blockchain-groth16", + "logos-blockchain-poseidon2", + "num-bigint", + "rpds", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "logos-blockchain-witness-generator" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "tempfile", +] + +[[package]] +name = "logos-blockchain-zksign" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-utils", + "logos-blockchain-groth16", + "logos-blockchain-poseidon2", + "logos-blockchain-witness-generator", + "num-bigint", + "serde", + "serde_json", + "thiserror 2.0.17", +] + [[package]] name = "lru-slab" version = "0.1.2" @@ -2544,6 +3576,23 @@ dependencies = [ "libc", ] +[[package]] +name = "match-lookup" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1265724d8cb29dbbc2b0f06fffb8bf1a8c0cf73a78eede9ba73a4a66c52a981e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "maybe-async" version = "0.2.10" @@ -2619,6 +3668,46 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multiaddr" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "libp2p-identity", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint", +] + +[[package]] +name = "multibase" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +dependencies = [ + "base-x", + "base256emoji", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +dependencies = [ + "core2", + "unsigned-varint", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -2636,6 +3725,30 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nimue" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0dc7d3b2b7bd112c0cecf7d6f4f16a174ee7a98e27615b1d08256d0176588f2" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "digest", + "generic-array 0.14.7", + "hex", + "keccak", + "log", + "rand 0.8.5", + "zeroize", +] + +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "no_std_strings" version = "0.1.3" @@ -2652,6 +3765,27 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "nssa" version = "0.1.0" @@ -2670,8 +3804,9 @@ dependencies = [ "secp256k1", "serde", "sha2", + "test-case", "test_program_methods", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -2687,7 +3822,7 @@ dependencies = [ "risc0-zkvm", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -2844,12 +3979,50 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "optfield" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969ccca8ffc4fb105bd131a228107d5c9dd89d9d627edf3295cbe979156f9712" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "overwatch" +version = "0.1.0" +source = "git+https://github.com/logos-co/Overwatch?rev=f5a9902#f5a99022f389d65adbd55e51f1e3f9eead62432a" +dependencies = [ + "async-trait", + "futures", + "overwatch-derive", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "overwatch-derive" +version = "0.1.0" +source = "git+https://github.com/logos-co/Overwatch?rev=f5a9902#f5a99022f389d65adbd55e51f1e3f9eead62432a" +dependencies = [ + "convert_case 0.8.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -2894,6 +4067,26 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2996,6 +4189,28 @@ dependencies = [ "toml_edit 0.23.10+spec-1.0.0", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -3017,10 +4232,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc_macro_test_attribute" -version = "0.1.0" - [[package]] name = "program_deployment" version = "0.1.0" @@ -3085,6 +4296,21 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quinn" version = "0.11.9" @@ -3098,8 +4324,8 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.1", - "thiserror", + "socket2 0.5.10", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -3120,7 +4346,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -3135,7 +4361,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.5.10", "tracing", "windows-sys 0.60.2", ] @@ -3223,6 +4449,15 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -3240,7 +4475,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -3298,65 +4533,30 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - [[package]] name = "reqwest" version = "0.12.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" dependencies = [ - "base64 0.22.1", + "base64", "bytes", + "encoding_rs", "futures-channel", "futures-core", "futures-util", + "h2 0.4.13", "http 1.4.0", - "http-body 1.0.1", + "http-body", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "mime", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -3365,11 +4565,12 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-util", - "tower", + "tower 0.5.2", "tower-http", "tower-service", "url", @@ -3516,11 +4717,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ff13f9b427254c5264e01aaa32e33f355525299b6829449295905778f3b1e8" dependencies = [ "anyhow", - "ark-bn254", - "ark-ec", - "ark-ff", - "ark-groth16", - "ark-serialize", + "ark-bn254 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-groth16 0.5.0", + "ark-serialize 0.5.0", "bytemuck", "hex", "num-bigint", @@ -3628,6 +4829,16 @@ dependencies = [ "librocksdb-sys", ] +[[package]] +name = "rpds" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e75f485e819d4d3015e6c0d55d02a4fd3db47c1993d9e603e0361fba2bffb34" +dependencies = [ + "archery", + "serde", +] + [[package]] name = "rrs-lib" version = "0.1.0" @@ -3638,6 +4849,25 @@ dependencies = [ "paste", ] +[[package]] +name = "rs-merkle-tree" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a3ef170810c387d31b64c0b59734abb0839dac2a8d137909e271bfdec9b1e0" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "byteorder", + "futures", + "light-poseidon", + "quote", + "rand 0.9.2", + "syn 1.0.109", + "thiserror 2.0.17", + "tiny-keccak", + "tokio", +] + [[package]] name = "rsa" version = "0.9.9" @@ -3722,15 +4952,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pki-types" version = "1.13.2" @@ -3778,7 +4999,7 @@ dependencies = [ "sha2", "strum", "tempfile", - "thiserror", + "thiserror 2.0.17", "toml", "yaml-rust2", ] @@ -3830,7 +5051,7 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", - "generic-array", + "generic-array 0.14.7", "pkcs8", "serdect", "subtle", @@ -3896,14 +5117,21 @@ version = "0.1.0" dependencies = [ "anyhow", "base58", + "bedrock_client", + "borsh", "chrono", "common", "futures", "log", + "logos-blockchain-core", + "logos-blockchain-key-management-system-service", "mempool", "nssa", "nssa_core", + "rand 0.8.5", + "reqwest", "serde", + "serde_json", "storage", "tempfile", "tokio", @@ -3917,7 +5145,7 @@ dependencies = [ "actix-web", "anyhow", "base58", - "base64 0.22.1", + "base64", "borsh", "common", "futures", @@ -3946,7 +5174,6 @@ dependencies = [ "log", "sequencer_core", "sequencer_rpc", - "serde_json", "tokio", ] @@ -3960,6 +5187,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -3993,6 +5229,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -4020,7 +5267,7 @@ version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ - "base64 0.22.1", + "base64", "chrono", "hex", "indexmap 1.9.3", @@ -4114,6 +5361,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "sntpc" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f778a0f82b3cf5d75f858eceee38e84d5292f1d03415e88cc4ec45ca6ba8a2" +dependencies = [ + "cfg-if", + "tokio", +] + [[package]] name = "socket2" version = "0.4.10" @@ -4150,6 +5407,15 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.7.3" @@ -4176,6 +5442,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "storage" version = "0.1.0" @@ -4183,7 +5455,7 @@ dependencies = [ "borsh", "common", "rocksdb", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -4241,12 +5513,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -4310,6 +5576,39 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", + "test-case-core", +] + [[package]] name = "test_program_methods" version = "0.1.0" @@ -4325,13 +5624,33 @@ dependencies = [ "risc0-zkvm", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -4376,6 +5695,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -4449,6 +5777,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + [[package]] name = "tokio-util" version = "0.7.17" @@ -4533,6 +5873,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.2" @@ -4542,10 +5893,11 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -4558,10 +5910,10 @@ dependencies = [ "bytes", "futures-util", "http 1.4.0", - "http-body 1.0.1", + "http-body", "iri-string", "pin-project-lite", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", ] @@ -4578,6 +5930,22 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +[[package]] +name = "tower_governor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3790eac6ad3fb8d9d96c2b040ae06e2517aa24b067545d1078b96ae72f7bb9a7" +dependencies = [ + "axum", + "forwarded-header-value", + "governor", + "http 1.4.0", + "pin-project", + "thiserror 1.0.69", + "tower 0.4.13", + "tracing", +] + [[package]] name = "tracing" version = "0.1.43" @@ -4611,6 +5979,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-subscriber" version = "0.2.25" @@ -4620,6 +5998,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "triomphe" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" + [[package]] name = "try-lock" version = "0.2.5" @@ -4687,6 +6071,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + [[package]] name = "untrusted" version = "0.9.0" @@ -4742,7 +6132,7 @@ dependencies = [ "anyhow", "async-stream", "base58", - "base64 0.22.1", + "base64", "borsh", "bytemuck", "clap", @@ -4756,6 +6146,7 @@ dependencies = [ "log", "nssa", "nssa_core", + "optfield", "rand 0.8.5", "risc0-zkvm", "serde", @@ -4960,6 +6351,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -4978,15 +6380,6 @@ 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" @@ -5014,21 +6407,6 @@ 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" @@ -5062,12 +6440,6 @@ 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" @@ -5080,12 +6452,6 @@ 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" @@ -5098,12 +6464,6 @@ 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" @@ -5128,12 +6488,6 @@ 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" @@ -5146,12 +6500,6 @@ 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" @@ -5164,12 +6512,6 @@ 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" @@ -5182,12 +6524,6 @@ 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" @@ -5209,16 +6545,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wit-bindgen" version = "0.46.0" @@ -5231,6 +6557,18 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "yaml-rust2" version = "0.10.4" diff --git a/Cargo.toml b/Cargo.toml index 14856d09..f14f2559 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,10 @@ members = [ "program_methods/guest", "test_program_methods", "test_program_methods/guest", - "integration_tests/proc_macro_test_attribute", "examples/program_deployment", "examples/program_deployment/methods", "examples/program_deployment/methods/guest", + "bedrock_client", ] [workspace.dependencies] @@ -34,6 +34,7 @@ sequencer_rpc = { path = "sequencer_rpc" } sequencer_runner = { path = "sequencer_runner" } wallet = { path = "wallet" } test_program_methods = { path = "test_program_methods" } +bedrock_client = { path = "bedrock_client" } tokio = { version = "1.28.2", features = [ "net", @@ -76,6 +77,11 @@ chrono = "0.4.41" borsh = "1.5.7" base58 = "0.2.0" itertools = "0.14.0" +url = "2.5.4" + +logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } +logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } +logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } rocksdb = { version = "0.24.0", default-features = false, features = [ "snappy", @@ -94,4 +100,4 @@ actix-web = { version = "=4.1.0", default-features = false, features = [ "macros", ] } clap = { version = "4.5.42", features = ["derive", "env"] } -reqwest = { version = "0.11.16", features = ["json"] } +reqwest = { version = "0.12", features = ["json", "rustls-tls", "stream"] } diff --git a/README.md b/README.md index cc20d220..f7885be0 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,8 @@ cargo install --path wallet --force Run `wallet help` to check everything went well. +Some completion scripts exists, see the [completions](./completions/README.md) folder. + ## Tutorial This tutorial walks you through creating accounts and executing NSSA programs in both public and private contexts. diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin index 354e6556..1a2fabca 100644 Binary files a/artifacts/program_methods/amm.bin and b/artifacts/program_methods/amm.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/program_methods/authenticated_transfer.bin index 1b72c464..73d5fec1 100644 Binary files a/artifacts/program_methods/authenticated_transfer.bin and b/artifacts/program_methods/authenticated_transfer.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/program_methods/pinata.bin index 91d2b934..278e88f4 100644 Binary files a/artifacts/program_methods/pinata.bin and b/artifacts/program_methods/pinata.bin differ diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin index 8da8b894..4a4c8bb6 100644 Binary files a/artifacts/program_methods/pinata_token.bin and b/artifacts/program_methods/pinata_token.bin differ diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin index 404f32b1..d8f5915e 100644 Binary files a/artifacts/program_methods/privacy_preserving_circuit.bin and b/artifacts/program_methods/privacy_preserving_circuit.bin differ diff --git a/artifacts/program_methods/token.bin b/artifacts/program_methods/token.bin index 22693413..5f6d3781 100644 Binary files a/artifacts/program_methods/token.bin and b/artifacts/program_methods/token.bin differ diff --git a/artifacts/test_program_methods/burner.bin b/artifacts/test_program_methods/burner.bin index 6bbc5071..96e339c2 100644 Binary files a/artifacts/test_program_methods/burner.bin and b/artifacts/test_program_methods/burner.bin differ diff --git a/artifacts/test_program_methods/chain_caller.bin b/artifacts/test_program_methods/chain_caller.bin index 56b017e7..731d2dc7 100644 Binary files a/artifacts/test_program_methods/chain_caller.bin and b/artifacts/test_program_methods/chain_caller.bin differ diff --git a/artifacts/test_program_methods/changer_claimer.bin b/artifacts/test_program_methods/changer_claimer.bin new file mode 100644 index 00000000..692d152b Binary files /dev/null and b/artifacts/test_program_methods/changer_claimer.bin differ diff --git a/artifacts/test_program_methods/claimer.bin b/artifacts/test_program_methods/claimer.bin index 0f42aaed..da0a9bef 100644 Binary files a/artifacts/test_program_methods/claimer.bin and b/artifacts/test_program_methods/claimer.bin differ diff --git a/artifacts/test_program_methods/data_changer.bin b/artifacts/test_program_methods/data_changer.bin index 99089d33..86fde894 100644 Binary files a/artifacts/test_program_methods/data_changer.bin and b/artifacts/test_program_methods/data_changer.bin differ diff --git a/artifacts/test_program_methods/extra_output.bin b/artifacts/test_program_methods/extra_output.bin index ffb622f5..1ae1cd98 100644 Binary files a/artifacts/test_program_methods/extra_output.bin and b/artifacts/test_program_methods/extra_output.bin differ diff --git a/artifacts/test_program_methods/malicious_authorization_changer.bin b/artifacts/test_program_methods/malicious_authorization_changer.bin new file mode 100644 index 00000000..8f80ab58 Binary files /dev/null and b/artifacts/test_program_methods/malicious_authorization_changer.bin differ diff --git a/artifacts/test_program_methods/minter.bin b/artifacts/test_program_methods/minter.bin index 08a57ea4..50199403 100644 Binary files a/artifacts/test_program_methods/minter.bin and b/artifacts/test_program_methods/minter.bin differ diff --git a/artifacts/test_program_methods/missing_output.bin b/artifacts/test_program_methods/missing_output.bin index 891acd58..4994ae3f 100644 Binary files a/artifacts/test_program_methods/missing_output.bin and b/artifacts/test_program_methods/missing_output.bin differ diff --git a/artifacts/test_program_methods/modified_transfer.bin b/artifacts/test_program_methods/modified_transfer.bin index 17793836..796de2d3 100644 Binary files a/artifacts/test_program_methods/modified_transfer.bin and b/artifacts/test_program_methods/modified_transfer.bin differ diff --git a/artifacts/test_program_methods/nonce_changer.bin b/artifacts/test_program_methods/nonce_changer.bin index 9dcb36a9..017de8b3 100644 Binary files a/artifacts/test_program_methods/nonce_changer.bin and b/artifacts/test_program_methods/nonce_changer.bin differ diff --git a/artifacts/test_program_methods/noop.bin b/artifacts/test_program_methods/noop.bin index fba84578..23195ef2 100644 Binary files a/artifacts/test_program_methods/noop.bin and b/artifacts/test_program_methods/noop.bin differ diff --git a/artifacts/test_program_methods/program_owner_changer.bin b/artifacts/test_program_methods/program_owner_changer.bin index 6daa6a11..db1f87fa 100644 Binary files a/artifacts/test_program_methods/program_owner_changer.bin and b/artifacts/test_program_methods/program_owner_changer.bin differ diff --git a/artifacts/test_program_methods/simple_balance_transfer.bin b/artifacts/test_program_methods/simple_balance_transfer.bin index 4fa42b26..17c95475 100644 Binary files a/artifacts/test_program_methods/simple_balance_transfer.bin and b/artifacts/test_program_methods/simple_balance_transfer.bin differ diff --git a/bedrock_client/Cargo.toml b/bedrock_client/Cargo.toml new file mode 100644 index 00000000..50a54815 --- /dev/null +++ b/bedrock_client/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "bedrock_client" +version = "0.1.0" +edition = "2024" + +[dependencies] +reqwest.workspace = true +anyhow.workspace = true +logos-blockchain-common-http-client.workspace = true +logos-blockchain-core.workspace = true diff --git a/bedrock_client/src/lib.rs b/bedrock_client/src/lib.rs new file mode 100644 index 00000000..530fdfc2 --- /dev/null +++ b/bedrock_client/src/lib.rs @@ -0,0 +1,32 @@ +use anyhow::Result; +pub use logos_blockchain_common_http_client::{BasicAuthCredentials, CommonHttpClient, Error}; +use logos_blockchain_core::mantle::SignedMantleTx; +use reqwest::{Client, Url}; + +// Simple wrapper +// maybe extend in the future for our purposes +pub struct BedrockClient { + http_client: CommonHttpClient, + node_url: Url, +} + +impl BedrockClient { + pub fn new(auth: Option, node_url: Url) -> Result { + let client = Client::builder() + //Add more fields if needed + .timeout(std::time::Duration::from_secs(60)) + .build()?; + + let http_client = CommonHttpClient::new_with_client(client, auth); + Ok(Self { + http_client, + node_url, + }) + } + + pub async fn post_transaction(&self, tx: SignedMantleTx) -> Result<(), Error> { + self.http_client + .post_transaction(self.node_url.clone(), tx) + .await + } +} diff --git a/ci_scripts/deploy.sh b/ci_scripts/deploy.sh index 7615df03..e84cac72 100644 --- a/ci_scripts/deploy.sh +++ b/ci_scripts/deploy.sh @@ -52,7 +52,7 @@ if [ -d ".git" ]; then git reset --hard origin/main else echo "Cloning repository..." - git clone https://github.com/vacp2p/nescience-testnet.git . + git clone https://github.com/logos-blockchain/lssa.git . git checkout main fi diff --git a/common/src/block.rs b/common/src/block.rs index baba1e42..84b7a419 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -23,7 +23,7 @@ pub type BlockHash = [u8; 32]; pub type BlockId = u64; pub type TimeStamp = u64; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] pub struct BlockHeader { pub block_id: BlockId, pub prev_block_hash: BlockHash, @@ -32,18 +32,26 @@ pub struct BlockHeader { pub signature: nssa::Signature, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] pub struct BlockBody { pub transactions: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub enum BedrockStatus { + Pending, + Safe, + Finalized, +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct Block { pub header: BlockHeader, pub body: BlockBody, + pub bedrock_status: BedrockStatus, } -#[derive(Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct HashableBlockData { pub block_id: BlockId, pub prev_block_hash: BlockHash, @@ -52,7 +60,7 @@ pub struct HashableBlockData { } impl HashableBlockData { - pub fn into_block(self, signing_key: &nssa::PrivateKey) -> Block { + pub fn into_pending_block(self, signing_key: &nssa::PrivateKey) -> Block { let data_bytes = borsh::to_vec(&self).unwrap(); let signature = nssa::Signature::new(signing_key, &data_bytes); let hash = OwnHasher::hash(&data_bytes); @@ -67,6 +75,7 @@ impl HashableBlockData { body: BlockBody { transactions: self.transactions, }, + bedrock_status: BedrockStatus::Pending, } } } diff --git a/common/src/sequencer_client.rs b/common/src/sequencer_client.rs index 622c0c11..0cb03f6f 100644 --- a/common/src/sequencer_client.rs +++ b/common/src/sequencer_client.rs @@ -44,8 +44,10 @@ impl SequencerClient { ) -> Result { Ok(Self { client: Client::builder() - //Add more fiedls if needed + // Add more fields if needed .timeout(std::time::Duration::from_secs(60)) + // Should be kept in sync with server keep-alive settings + .pool_idle_timeout(std::time::Duration::from_secs(5)) .build()?, sequencer_addr, basic_auth, @@ -60,6 +62,10 @@ impl SequencerClient { let request = rpc_primitives::message::Request::from_payload_version_2_0(method.to_string(), payload); + log::debug!( + "Calling method {method} with payload {request:?} to sequencer at {}", + self.sequencer_addr + ); let mut call_builder = self.client.post(&self.sequencer_addr); if let Some((username, password)) = &self.basic_auth { diff --git a/common/src/test_utils.rs b/common/src/test_utils.rs index 7e3c0ba8..1125b86e 100644 --- a/common/src/test_utils.rs +++ b/common/src/test_utils.rs @@ -30,7 +30,7 @@ pub fn produce_dummy_block( transactions, }; - block_data.into_block(&sequencer_sign_key_for_testing()) + block_data.into_pending_block(&sequencer_sign_key_for_testing()) } pub fn produce_dummy_empty_transaction() -> EncodedTransaction { diff --git a/completions/README.md b/completions/README.md new file mode 100644 index 00000000..4695c9e6 --- /dev/null +++ b/completions/README.md @@ -0,0 +1,135 @@ +# Wallet CLI Completion + +Completion scripts for the LSSA `wallet` command. + +## ZSH + +Works with both vanilla zsh and oh-my-zsh. + +### Features + +- Full completion for all wallet subcommands +- Contextual option completion for each command +- Dynamic account ID completion via `wallet account list` +- Descriptions for all commands and options + +Note that only accounts created by the user auto-complete. +Preconfigured accounts and accounts only with `/` (no number) are not completed. + +e.g.: + +``` +▶ wallet account list +Preconfigured Public/Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw, +Preconfigured Public/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy, +Preconfigured Private/3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw, +Preconfigured Private/AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX, +/ Public/8DstRgMQrB2N9a7ymv98RDDbt8nctrP9ZzaNRSpKDZSu, +/0 Public/2gJJjtG9UivBGEhA1Jz6waZQx1cwfYupC5yvKEweHaeH, +/ Private/Bcv15B36bs1VqvQAdY6ZGFM1KioByNQQsB92KTNAx6u2 +``` + +Only `Public/2gJJjtG9UivBGEhA1Jz6waZQx1cwfYupC5yvKEweHaeH` is used for completion. + +### Supported Commands + +| Command | Description | +|------------------------|-------------------------------------------------------------| +| `wallet auth-transfer` | Authenticated transfer (init, send) | +| `wallet chain-info` | Chain info queries (current-block-id, block, transaction) | +| `wallet account` | Account management (get, list, new, sync-private) | +| `wallet pinata` | Piñata faucet (claim) | +| `wallet token` | Token operations (new, send) | +| `wallet amm` | AMM operations (new, swap, add-liquidity, remove-liquidity) | +| `wallet check-health` | Health check | + +### Installation + +#### Vanilla Zsh + +1. Create a completions directory: + + ```sh + mkdir -p ~/.zsh/completions + ``` + +2. Copy the completion file: + + ```sh + cp ./zsh/_wallet ~/.zsh/completions/ + ``` + +3. Add to your `~/.zshrc` (before any `compinit` call, or add these lines if you don't have one): + + ```sh + fpath=(~/.zsh/completions $fpath) + autoload -Uz compinit && compinit + ``` + +4. Reload your shell: + + ```sh + exec zsh + ``` + +#### Oh-My-Zsh + +1. Create the plugin directory and copy the file: + + ```sh + mkdir -p ~/.oh-my-zsh/custom/plugins/wallet + cp _wallet ~/.oh-my-zsh/custom/plugins/wallet/ + ``` + +2. Add `wallet` to your plugins array in `~/.zshrc`: + + ```sh + plugins=(... wallet) + ``` + +3. Reload your shell: + + ```sh + exec zsh + ``` + +### Requirements + +The completion script calls `wallet account list` to dynamically fetch account IDs. Ensure the `wallet` command is in your `$PATH`. + +### Usage + +```sh +# Main commands +wallet + +# Account subcommands +wallet account + +# Options for auth-transfer send +wallet auth-transfer send -- + +# Account types when creating +wallet account new +# Shows: public private + +# Account IDs (fetched dynamically) +wallet account get --account-id +# Shows: Public/... Private/... +``` + +## Troubleshooting + +### Completions not appearing + +1. Check that `compinit` is called in your `.zshrc` +2. Rebuild the completion cache: + + ```sh + rm -f ~/.zcompdump* + exec zsh + ``` + +### Account IDs not completing + +Ensure `wallet account list` works from your command line. diff --git a/completions/zsh/_wallet b/completions/zsh/_wallet new file mode 100644 index 00000000..d2831ef0 --- /dev/null +++ b/completions/zsh/_wallet @@ -0,0 +1,435 @@ +#compdef wallet + +# Zsh completion script for the wallet CLI +# See instructions in ../README.md + +_wallet() { + local -a commands + local -a subcommands + local curcontext="$curcontext" state line + typeset -A opt_args + + _arguments -C \ + '(-c --continuous-run)'{-c,--continuous-run}'[Continuous run flag]' \ + '--auth[Basic authentication in the format user or user\:password]:auth:' \ + '1: :->command' \ + '*:: :->args' + + case $state in + command) + commands=( + 'auth-transfer:Authenticated transfer subcommand' + 'chain-info:Generic chain info subcommand' + 'account:Account view and sync subcommand' + 'pinata:Pinata program interaction subcommand' + 'token:Token program interaction subcommand' + 'amm:AMM program interaction subcommand' + 'check-health:Check the wallet can connect to the node and builtin local programs match the remote versions' + 'config:Command to setup config, get and set config fields' + 'restore-keys:Restoring keys from given password at given depth' + 'deploy-program:Deploy a program' + 'help:Print help message or the help of the given subcommand(s)' + ) + _describe -t commands 'wallet commands' commands + ;; + args) + case $line[1] in + auth-transfer) + _wallet_auth_transfer + ;; + chain-info) + _wallet_chain_info + ;; + account) + _wallet_account + ;; + pinata) + _wallet_pinata + ;; + token) + _wallet_token + ;; + amm) + _wallet_amm + ;; + config) + _wallet_config + ;; + restore-keys) + _wallet_restore_keys + ;; + deploy-program) + _wallet_deploy_program + ;; + help) + _wallet_help + ;; + esac + ;; + esac +} + +# auth-transfer subcommand +_wallet_auth_transfer() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'init:Initialize account under authenticated transfer program' + 'send:Send native tokens from one account to another with variable privacy' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'auth-transfer subcommands' subcommands + ;; + args) + case $line[1] in + init) + _arguments \ + '--account-id[Account ID to initialize]:account_id:_wallet_account_ids' + ;; + send) + _arguments \ + '--from[Source account ID]:from_account:_wallet_account_ids' \ + '--to[Destination account ID (for owned accounts)]:to_account:_wallet_account_ids' \ + '--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \ + '--to-ipk[Destination viewing public key (for foreign private accounts)]:ipk:' \ + '--amount[Amount of native tokens to send]:amount:' + ;; + esac + ;; + esac +} + +# chain-info subcommand +_wallet_chain_info() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'current-block-id:Get current block id from sequencer' + 'block:Get block at id from sequencer' + 'transaction:Get transaction at hash from sequencer' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'chain-info subcommands' subcommands + ;; + args) + case $line[1] in + block) + _arguments \ + '--id[Block ID to retrieve]:block_id:' + ;; + transaction) + _arguments \ + '--hash[Transaction hash to retrieve]:tx_hash:' + ;; + esac + ;; + esac +} + +# account subcommand +_wallet_account() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'get:Get account data' + 'list:List all accounts' + 'ls:List all accounts (alias for list)' + 'new:Produce new public or private account' + 'sync-private:Sync private accounts' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'account subcommands' subcommands + ;; + args) + case $line[1] in + get) + _arguments \ + '(-r --raw)'{-r,--raw}'[Get raw account data]' \ + '(-k --keys)'{-k,--keys}'[Display keys (pk for public accounts, npk/ipk for private accounts)]' \ + '(-a --account-id)'{-a,--account-id}'[Account ID to query]:account_id:_wallet_account_ids' + ;; + list|ls) + _arguments \ + '(-l --long)'{-l,--long}'[Display detailed account information]' + ;; + new) + _arguments -C \ + '1: :->account_type' \ + '*:: :->new_args' + case $state in + account_type) + compadd public private + ;; + new_args) + _arguments \ + '--cci[Chain index of a parent node]:chain_index:' + ;; + esac + ;; + esac + ;; + esac +} + +# pinata subcommand +_wallet_pinata() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'claim:Claim tokens from the Piñata faucet' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'pinata subcommands' subcommands + ;; + args) + case $line[1] in + claim) + _arguments \ + '--to[Destination account ID to receive claimed tokens]:to_account:_wallet_account_ids' + ;; + esac + ;; + esac +} + +# token subcommand +_wallet_token() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'new:Produce a new token' + 'send:Send tokens from one account to another with variable privacy' + 'burn:Burn tokens on holder, modify definition' + 'mint:Mint tokens on holder, modify definition' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'token subcommands' subcommands + ;; + args) + case $line[1] in + new) + _arguments \ + '--name[Token name]:name:' \ + '--total-supply[Total supply of tokens to mint]:total_supply:' \ + '--definition-account-id[Account ID for token definition]:definition_account:_wallet_account_ids' \ + '--supply-account-id[Account ID to receive initial supply]:supply_account:_wallet_account_ids' + ;; + send) + _arguments \ + '--from[Source holding account ID]:from_account:_wallet_account_ids' \ + '--to[Destination holding account ID (for owned accounts)]:to_account:_wallet_account_ids' \ + '--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \ + '--to-ipk[Destination viewing public key (for foreign private accounts)]:ipk:' \ + '--amount[Amount of tokens to send]:amount:' + ;; + burn) + _arguments \ + '--definition[Definition account ID]:definition_account:_wallet_account_ids' \ + '--holder[Holder account ID]:holder_account:_wallet_account_ids' \ + '--amount[Amount of tokens to burn]:amount:' + ;; + mint) + _arguments \ + '--definition[Definition account ID]:definition_account:_wallet_account_ids' \ + '--holder[Holder account ID (for owned accounts)]:holder_account:_wallet_account_ids' \ + '--holder-npk[Holder nullifier public key (for foreign private accounts)]:npk:' \ + '--holder-ipk[Holder viewing public key (for foreign private accounts)]:ipk:' \ + '--amount[Amount of tokens to mint]:amount:' + ;; + esac + ;; + esac +} + +# amm subcommand +_wallet_amm() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'new:Create a new liquidity pool' + 'swap:Swap tokens using the AMM' + 'add-liquidity:Add liquidity to an existing pool' + 'remove-liquidity:Remove liquidity from a pool' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'amm subcommands' subcommands + ;; + args) + case $line[1] in + new) + _arguments \ + '--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \ + '--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \ + '--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \ + '--balance-a[Amount of token A to deposit]:balance_a:' \ + '--balance-b[Amount of token B to deposit]:balance_b:' + ;; + swap) + _arguments \ + '--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \ + '--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \ + '--amount-in[Amount of tokens to swap]:amount_in:' \ + '--min-amount-out[Minimum tokens expected in return]:min_amount_out:' \ + '--token-definition[Definition ID of the token being provided]:token_def:' + ;; + add-liquidity) + _arguments \ + '--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \ + '--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \ + '--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \ + '--max-amount-a[Maximum amount of token A to deposit]:max_amount_a:' \ + '--max-amount-b[Maximum amount of token B to deposit]:max_amount_b:' \ + '--min-amount-lp[Minimum LP tokens to receive]:min_amount_lp:' + ;; + remove-liquidity) + _arguments \ + '--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \ + '--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \ + '--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \ + '--balance-lp[Amount of LP tokens to burn]:balance_lp:' \ + '--min-amount-a[Minimum token A to receive]:min_amount_a:' \ + '--min-amount-b[Minimum token B to receive]:min_amount_b:' + ;; + esac + ;; + esac +} + +# config subcommand +_wallet_config() { + local -a subcommands + local -a config_keys + + config_keys=( + 'all' + 'override_rust_log' + 'sequencer_addr' + 'seq_poll_timeout_millis' + 'seq_tx_poll_max_blocks' + 'seq_poll_max_retries' + 'seq_block_poll_max_amount' + 'initial_accounts' + 'basic_auth' + ) + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'get:Getter of config fields' + 'set:Setter of config fields' + 'description:Prints description of corresponding field' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'config subcommands' subcommands + ;; + args) + case $line[1] in + get|description) + compadd -a config_keys + ;; + set) + _arguments \ + '1:key:compadd -a config_keys' \ + '2:value:' + ;; + esac + ;; + esac +} + +# restore-keys subcommand +_wallet_restore_keys() { + _arguments \ + '(-d --depth)'{-d,--depth}'[How deep in tree accounts may be]:depth:' +} + +# deploy-program subcommand +_wallet_deploy_program() { + _arguments \ + '1:binary filepath:_files' +} + +# help subcommand +_wallet_help() { + local -a commands + commands=( + 'auth-transfer:Authenticated transfer subcommand' + 'chain-info:Generic chain info subcommand' + 'account:Account view and sync subcommand' + 'pinata:Pinata program interaction subcommand' + 'token:Token program interaction subcommand' + 'amm:AMM program interaction subcommand' + 'check-health:Check the wallet can connect to the node' + 'config:Command to setup config, get and set config fields' + 'restore-keys:Restoring keys from given password at given depth' + 'deploy-program:Deploy a program' + ) + _describe -t commands 'wallet commands' commands +} + +# Helper function to complete account IDs +# Uses `wallet account list` to get available accounts +# Only includes accounts with /N prefix (where N is a number) +_wallet_account_ids() { + local -a accounts + local line + + # Try to get accounts from wallet account list command + # Filter to lines starting with /N (numbered accounts) and extract the account ID + if command -v wallet &>/dev/null; then + while IFS= read -r line; do + # Remove trailing comma if present and add to array + [[ -n "$line" ]] && accounts+=("${line%,}") + done < <(wallet account list 2>/dev/null | grep '^/[0-9]' | awk '{print $2}') + fi + + # Provide type prefixes as fallback if command fails or returns nothing + if (( ${#accounts} == 0 )); then + compadd -S '' -- 'Public/' 'Private/' + return + fi + + _multi_parts / accounts +} + +_wallet "$@" diff --git a/examples/program_deployment/src/bin/run_hello_world.rs b/examples/program_deployment/src/bin/run_hello_world.rs index a7dc0fca..3c0c9034 100644 --- a/examples/program_deployment/src/bin/run_hello_world.rs +++ b/examples/program_deployment/src/bin/run_hello_world.rs @@ -3,7 +3,7 @@ use nssa::{ program::Program, public_transaction::{Message, WitnessSet}, }; -use wallet::{WalletCore, helperfunctions::fetch_config}; +use wallet::WalletCore; // Before running this example, compile the `hello_world.rs` guest program with: // @@ -24,11 +24,8 @@ use wallet::{WalletCore, helperfunctions::fetch_config}; #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary diff --git a/examples/program_deployment/src/bin/run_hello_world_private.rs b/examples/program_deployment/src/bin/run_hello_world_private.rs index dcbe59a5..27ac2079 100644 --- a/examples/program_deployment/src/bin/run_hello_world_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_private.rs @@ -1,5 +1,5 @@ use nssa::{AccountId, program::Program}; -use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config}; +use wallet::{PrivacyPreservingAccount, WalletCore}; // Before running this example, compile the `hello_world.rs` guest program with: // @@ -22,11 +22,8 @@ use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary @@ -53,7 +50,7 @@ async fn main() { wallet_core .send_privacy_preserving_tx( accounts, - &Program::serialize_instruction(greeting).unwrap(), + Program::serialize_instruction(greeting).unwrap(), &program.into(), ) .await diff --git a/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs b/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs index d7c91f89..56d28084 100644 --- a/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs +++ b/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs @@ -3,7 +3,7 @@ use nssa::{ program::Program, public_transaction::{Message, WitnessSet}, }; -use wallet::{WalletCore, helperfunctions::fetch_config}; +use wallet::WalletCore; // Before running this example, compile the `simple_tail_call.rs` guest program with: // @@ -24,11 +24,8 @@ use wallet::{WalletCore, helperfunctions::fetch_config}; #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary diff --git a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs index 5a014f24..9b3619cb 100644 --- a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs @@ -4,7 +4,7 @@ use nssa::{ AccountId, ProgramId, privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program, }; -use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config}; +use wallet::{PrivacyPreservingAccount, WalletCore}; // Before running this example, compile the `simple_tail_call.rs` guest program with: // @@ -25,11 +25,8 @@ use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the simple_tail_call program binary @@ -61,7 +58,7 @@ async fn main() { wallet_core .send_privacy_preserving_tx( accounts, - &Program::serialize_instruction(instruction).unwrap(), + Program::serialize_instruction(instruction).unwrap(), &program_with_dependencies, ) .await diff --git a/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs b/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs index 21740ae9..09fd621e 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs @@ -3,7 +3,7 @@ use nssa::{ program::Program, public_transaction::{Message, WitnessSet}, }; -use wallet::{WalletCore, helperfunctions::fetch_config}; +use wallet::WalletCore; // Before running this example, compile the `hello_world_with_authorization.rs` guest program with: // @@ -26,11 +26,8 @@ use wallet::{WalletCore, helperfunctions::fetch_config}; #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary @@ -50,7 +47,7 @@ async fn main() { // Load signing keys to provide authorization let signing_key = wallet_core - .storage + .storage() .user_data .get_pub_account_signing_key(&account_id) .expect("Input account should be a self owned public account"); diff --git a/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs b/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs index 6c673d8b..43839ba9 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs @@ -4,7 +4,7 @@ use nssa::{ public_transaction::{Message, WitnessSet}, }; use nssa_core::program::PdaSeed; -use wallet::{WalletCore, helperfunctions::fetch_config}; +use wallet::WalletCore; // Before running this example, compile the `simple_tail_call.rs` guest program with: // @@ -27,11 +27,8 @@ const PDA_SEED: PdaSeed = PdaSeed::new([37; 32]); #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary diff --git a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs index 4307315a..fc116241 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs @@ -1,6 +1,6 @@ use clap::{Parser, Subcommand}; use nssa::{PublicTransaction, program::Program, public_transaction}; -use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config}; +use wallet::{PrivacyPreservingAccount, WalletCore}; // Before running this example, compile the `hello_world_with_move_function.rs` guest program with: // @@ -62,11 +62,8 @@ async fn main() { let bytecode: Vec = std::fs::read(cli.program_path).unwrap(); let program = Program::new(bytecode).unwrap(); - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); match cli.command { Command::WritePublic { @@ -104,7 +101,7 @@ async fn main() { wallet_core .send_privacy_preserving_tx( accounts, - &Program::serialize_instruction(instruction).unwrap(), + Program::serialize_instruction(instruction).unwrap(), &program.into(), ) .await @@ -145,7 +142,7 @@ async fn main() { wallet_core .send_privacy_preserving_tx( accounts, - &Program::serialize_instruction(instruction).unwrap(), + Program::serialize_instruction(instruction).unwrap(), &program.into(), ) .await diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 52c6e385..b888c177 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -11,16 +11,14 @@ sequencer_runner.workspace = true wallet.workspace = true common.workspace = true key_protocol.workspace = true -proc_macro_test_attribute = { path = "./proc_macro_test_attribute" } -clap = { workspace = true, features = ["derive", "env"] } anyhow.workspace = true env_logger.workspace = true log.workspace = true -actix.workspace = true actix-web.workspace = true base64.workspace = true -tokio.workspace = true +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } hex.workspace = true tempfile.workspace = true borsh.workspace = true +futures.workspace = true diff --git a/integration_tests/configs/debug/sequencer/sequencer_config.json b/integration_tests/configs/sequencer/sequencer_config.json similarity index 98% rename from integration_tests/configs/debug/sequencer/sequencer_config.json rename to integration_tests/configs/sequencer/sequencer_config.json index db1c7f20..575d3de3 100644 --- a/integration_tests/configs/debug/sequencer/sequencer_config.json +++ b/integration_tests/configs/sequencer/sequencer_config.json @@ -1,12 +1,12 @@ { - "home": "./sequencer", + "home": "", "override_rust_log": null, "genesis_id": 1, "is_genesis_random": true, "max_num_tx_in_block": 20, "mempool_max_size": 10000, "block_create_timeout_millis": 10000, - "port": 3040, + "port": 0, "initial_accounts": [ { "account_id": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy", @@ -155,4 +155,4 @@ 37, 37 ] -} \ No newline at end of file +} diff --git a/integration_tests/configs/debug/wallet/wallet_config.json b/integration_tests/configs/wallet/wallet_config.json similarity index 99% rename from integration_tests/configs/debug/wallet/wallet_config.json rename to integration_tests/configs/wallet/wallet_config.json index ad7b2792..2abab83e 100644 --- a/integration_tests/configs/debug/wallet/wallet_config.json +++ b/integration_tests/configs/wallet/wallet_config.json @@ -1,10 +1,11 @@ { "override_rust_log": null, - "sequencer_addr": "http://127.0.0.1:3040", + "sequencer_addr": "", "seq_poll_timeout_millis": 12000, "seq_tx_poll_max_blocks": 5, "seq_poll_max_retries": 5, "seq_block_poll_max_amount": 100, + "basic_auth": null, "initial_accounts": [ { "Public": { @@ -542,6 +543,5 @@ } } } - ], - "basic_auth": null + ] } \ No newline at end of file diff --git a/integration_tests/proc_macro_test_attribute/Cargo.toml b/integration_tests/proc_macro_test_attribute/Cargo.toml deleted file mode 100644 index a50c3f37..00000000 --- a/integration_tests/proc_macro_test_attribute/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "proc_macro_test_attribute" -version = "0.1.0" -edition = "2024" - -[dependencies] - -[lib] -proc-macro = true diff --git a/integration_tests/proc_macro_test_attribute/src/lib.rs b/integration_tests/proc_macro_test_attribute/src/lib.rs deleted file mode 100644 index 29852a0b..00000000 --- a/integration_tests/proc_macro_test_attribute/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -extern crate proc_macro; - -use proc_macro::*; - -#[proc_macro_attribute] -pub fn nssa_integration_test(_attr: TokenStream, item: TokenStream) -> TokenStream { - let input = item.to_string(); - - let fn_keyword = "fn "; - let fn_keyword_alternative = "fn\n"; - - let mut start_opt = None; - let mut fn_name = String::new(); - - if let Some(start) = input.find(fn_keyword) { - start_opt = Some(start); - } else if let Some(start) = input.find(fn_keyword_alternative) { - start_opt = Some(start); - } - - if let Some(start) = start_opt { - let rest = &input[start + fn_keyword.len()..]; - if let Some(end) = rest.find(|c: char| c == '(' || c.is_whitespace()) { - let name = &rest[..end]; - fn_name = name.to_string(); - } - } else { - println!("ERROR: keyword fn not found"); - } - - let extension = format!( - r#" - {input} - - function_map.insert("{fn_name}".to_string(), |home_dir: PathBuf| Box::pin(async {{ - let res = pre_test(home_dir).await.unwrap(); - - info!("Waiting for first block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - {fn_name}().await; - - post_test(res).await; - }})); - "# - ); - - extension.parse().unwrap() -} diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 401238fa..12d718ec 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -1,38 +1,25 @@ -use std::path::PathBuf; +//! This library contains common code for integration tests. + +use std::{net::SocketAddr, path::PathBuf, sync::LazyLock}; use actix_web::dev::ServerHandle; -use anyhow::Result; +use anyhow::{Context as _, Result}; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; -use clap::Parser; use common::{ sequencer_client::SequencerClient, transaction::{EncodedTransaction, NSSATransaction}, }; -use log::{info, warn}; +use futures::FutureExt as _; +use log::debug; use nssa::PrivacyPreservingTransaction; use nssa_core::Commitment; use sequencer_core::config::SequencerConfig; -use sequencer_runner::startup_sequencer; use tempfile::TempDir; use tokio::task::JoinHandle; +use wallet::{WalletCore, config::WalletConfigOverrides}; -use crate::test_suite_map::{prepare_function_map, tps_test}; - -#[macro_use] -extern crate proc_macro_test_attribute; - -pub mod test_suite_map; - -mod tps_test_utils; - -#[derive(Parser, Debug)] -#[clap(version)] -struct Args { - /// Path to configs - home_dir: PathBuf, - /// Test name - test_name: String, -} +// TODO: Remove this and control time from tests +pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; pub const ACC_SENDER: &str = "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy"; pub const ACC_RECEIVER: &str = "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw"; @@ -40,104 +27,181 @@ pub const ACC_RECEIVER: &str = "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw"; pub const ACC_SENDER_PRIVATE: &str = "3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw"; pub const ACC_RECEIVER_PRIVATE: &str = "AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX"; -pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; - pub const NSSA_PROGRAM_FOR_TEST_DATA_CHANGER: &str = "data_changer.bin"; -fn make_public_account_input_from_str(account_id: &str) -> String { +static LOGGER: LazyLock<()> = LazyLock::new(env_logger::init); + +/// Test context which sets up a sequencer and a wallet for integration tests. +/// +/// It's memory and logically safe to create multiple instances of this struct in parallel tests, +/// as each instance uses its own temporary directories for sequencer and wallet data. +pub struct TestContext { + sequencer_server_handle: ServerHandle, + sequencer_loop_handle: JoinHandle>, + sequencer_client: SequencerClient, + wallet: WalletCore, + _temp_sequencer_dir: TempDir, + _temp_wallet_dir: TempDir, +} + +impl TestContext { + /// Create new test context. + pub async fn new() -> Result { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + + let sequencer_config_path = + PathBuf::from(manifest_dir).join("configs/sequencer/sequencer_config.json"); + + let sequencer_config = SequencerConfig::from_path(&sequencer_config_path) + .context("Failed to create sequencer config from file")?; + + Self::new_with_sequencer_config(sequencer_config).await + } + + /// Create new test context with custom sequencer config. + /// + /// `home` and `port` fields of the provided config will be overridden to meet tests parallelism + /// requirements. + pub async fn new_with_sequencer_config(sequencer_config: SequencerConfig) -> Result { + // Ensure logger is initialized only once + *LOGGER; + + debug!("Test context setup"); + + let (sequencer_server_handle, sequencer_addr, sequencer_loop_handle, temp_sequencer_dir) = + Self::setup_sequencer(sequencer_config) + .await + .context("Failed to setup sequencer")?; + + // Convert 0.0.0.0 to 127.0.0.1 for client connections + // When binding to port 0, the server binds to 0.0.0.0: + // but clients need to connect to 127.0.0.1: to work reliably + let sequencer_addr = if sequencer_addr.ip().is_unspecified() { + format!("http://127.0.0.1:{}", sequencer_addr.port()) + } else { + format!("http://{sequencer_addr}") + }; + + let (wallet, temp_wallet_dir) = Self::setup_wallet(sequencer_addr.clone()) + .await + .context("Failed to setup wallet")?; + + let sequencer_client = + SequencerClient::new(sequencer_addr).context("Failed to create sequencer client")?; + + Ok(Self { + sequencer_server_handle, + sequencer_loop_handle, + sequencer_client, + wallet, + _temp_sequencer_dir: temp_sequencer_dir, + _temp_wallet_dir: temp_wallet_dir, + }) + } + + async fn setup_sequencer( + mut config: SequencerConfig, + ) -> Result<(ServerHandle, SocketAddr, JoinHandle>, TempDir)> { + let temp_sequencer_dir = + tempfile::tempdir().context("Failed to create temp dir for sequencer home")?; + + debug!( + "Using temp sequencer home at {:?}", + temp_sequencer_dir.path() + ); + config.home = temp_sequencer_dir.path().to_owned(); + // Setting port to 0 lets the OS choose a free port for us + config.port = 0; + + let (sequencer_server_handle, sequencer_addr, sequencer_loop_handle) = + sequencer_runner::startup_sequencer(config).await?; + + Ok(( + sequencer_server_handle, + sequencer_addr, + sequencer_loop_handle, + temp_sequencer_dir, + )) + } + + async fn setup_wallet(sequencer_addr: String) -> Result<(WalletCore, TempDir)> { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let wallet_config_source_path = + PathBuf::from(manifest_dir).join("configs/wallet/wallet_config.json"); + + let temp_wallet_dir = + tempfile::tempdir().context("Failed to create temp dir for wallet home")?; + + let config_path = temp_wallet_dir.path().join("wallet_config.json"); + std::fs::copy(&wallet_config_source_path, &config_path) + .context("Failed to copy wallet config to temp dir")?; + + let storage_path = temp_wallet_dir.path().join("storage.json"); + let config_overrides = WalletConfigOverrides { + sequencer_addr: Some(sequencer_addr), + ..Default::default() + }; + + let wallet = WalletCore::new_init_storage( + config_path, + storage_path, + Some(config_overrides), + "test_pass".to_owned(), + ) + .context("Failed to init wallet")?; + wallet + .store_persistent_data() + .await + .context("Failed to store wallet persistent data")?; + + Ok((wallet, temp_wallet_dir)) + } + + /// Get reference to the wallet. + pub fn wallet(&self) -> &WalletCore { + &self.wallet + } + + /// Get mutable reference to the wallet. + pub fn wallet_mut(&mut self) -> &mut WalletCore { + &mut self.wallet + } + + /// Get reference to the sequencer client. + pub fn sequencer_client(&self) -> &SequencerClient { + &self.sequencer_client + } +} + +impl Drop for TestContext { + fn drop(&mut self) { + debug!("Test context cleanup"); + + let Self { + sequencer_server_handle, + sequencer_loop_handle, + sequencer_client: _, + wallet: _, + _temp_sequencer_dir, + _temp_wallet_dir, + } = self; + + sequencer_loop_handle.abort(); + + // Can't wait here as Drop can't be async, but anyway stop signal should be sent + sequencer_server_handle.stop(true).now_or_never(); + } +} + +pub fn format_public_account_id(account_id: &str) -> String { format!("Public/{account_id}") } -fn make_private_account_input_from_str(account_id: &str) -> String { +pub fn format_private_account_id(account_id: &str) -> String { format!("Private/{account_id}") } -#[allow(clippy::type_complexity)] -pub async fn pre_test( - home_dir: PathBuf, -) -> Result<(ServerHandle, JoinHandle>, TempDir)> { - wallet::cli::execute_setup("test_pass".to_owned()).await?; - - let home_dir_sequencer = home_dir.join("sequencer"); - - let mut sequencer_config = - sequencer_runner::config::from_file(home_dir_sequencer.join("sequencer_config.json")) - .unwrap(); - - let temp_dir_sequencer = replace_home_dir_with_temp_dir_in_configs(&mut sequencer_config); - - let (seq_http_server_handle, sequencer_loop_handle) = - startup_sequencer(sequencer_config).await?; - - Ok(( - seq_http_server_handle, - sequencer_loop_handle, - temp_dir_sequencer, - )) -} - -pub fn replace_home_dir_with_temp_dir_in_configs( - sequencer_config: &mut SequencerConfig, -) -> TempDir { - let temp_dir_sequencer = tempfile::tempdir().unwrap(); - - sequencer_config.home = temp_dir_sequencer.path().to_path_buf(); - - temp_dir_sequencer -} - -#[allow(clippy::type_complexity)] -pub async fn post_test(residual: (ServerHandle, JoinHandle>, TempDir)) { - let (seq_http_server_handle, sequencer_loop_handle, _) = residual; - - info!("Cleanup"); - - sequencer_loop_handle.abort(); - seq_http_server_handle.stop(true).await; - - let wallet_home = wallet::helperfunctions::get_home().unwrap(); - let persistent_data_home = wallet_home.join("storage.json"); - - // Removing persistent accounts after run to not affect other executions - // Not necessary an error, if fails as there is tests for failure scenario - let _ = std::fs::remove_file(persistent_data_home) - .inspect_err(|err| warn!("Failed to remove persistent data with err {err:#?}")); - - // At this point all of the references to sequencer_core must be lost. - // So they are dropped and tempdirs will be dropped too, -} - -pub async fn main_tests_runner() -> Result<()> { - env_logger::init(); - - let args = Args::parse(); - let Args { - home_dir, - test_name, - } = args; - - let function_map = prepare_function_map(); - - match test_name.as_str() { - "all" => { - // Tests that use default config - for (_, fn_pointer) in function_map { - fn_pointer(home_dir.clone()).await; - } - // Run TPS test with its own specific config - tps_test().await; - } - _ => { - let fn_pointer = function_map.get(&test_name).expect("Unknown test name"); - - fn_pointer(home_dir.clone()).await; - } - } - - Ok(()) -} - -async fn fetch_privacy_preserving_tx( +pub async fn fetch_privacy_preserving_tx( seq_client: &SequencerClient, tx_hash: String, ) -> PrivacyPreservingTransaction { @@ -161,7 +225,7 @@ async fn fetch_privacy_preserving_tx( } } -async fn verify_commitment_is_in_state( +pub async fn verify_commitment_is_in_state( commitment: Commitment, seq_client: &SequencerClient, ) -> bool { @@ -173,15 +237,15 @@ async fn verify_commitment_is_in_state( #[cfg(test)] mod tests { - use crate::{make_private_account_input_from_str, make_public_account_input_from_str}; + use super::{format_private_account_id, format_public_account_id}; #[test] fn correct_account_id_from_prefix() { let account_id1 = "cafecafe"; let account_id2 = "deadbeaf"; - let account_id1_pub = make_public_account_input_from_str(account_id1); - let account_id2_priv = make_private_account_input_from_str(account_id2); + let account_id1_pub = format_public_account_id(account_id1); + let account_id2_priv = format_private_account_id(account_id2); assert_eq!(account_id1_pub, "Public/cafecafe".to_string()); assert_eq!(account_id2_priv, "Private/deadbeaf".to_string()); diff --git a/integration_tests/src/main.rs b/integration_tests/src/main.rs deleted file mode 100644 index 583df2aa..00000000 --- a/integration_tests/src/main.rs +++ /dev/null @@ -1,15 +0,0 @@ -use anyhow::Result; -use integration_tests::main_tests_runner; - -pub const NUM_THREADS: usize = 8; - -fn main() -> Result<()> { - actix::System::with_tokio_rt(|| { - tokio::runtime::Builder::new_multi_thread() - .worker_threads(NUM_THREADS) - .enable_all() - .build() - .unwrap() - }) - .block_on(main_tests_runner()) -} diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs deleted file mode 100644 index e722cc5f..00000000 --- a/integration_tests/src/test_suite_map.rs +++ /dev/null @@ -1,3154 +0,0 @@ -use std::{ - collections::HashMap, - path::PathBuf, - pin::Pin, - str::FromStr, - time::{Duration, Instant}, -}; - -use actix_web::dev::ServerHandle; -use anyhow::Result; -use common::{PINATA_BASE58, sequencer_client::SequencerClient}; -use key_protocol::key_management::key_tree::chain_index::ChainIndex; -use log::info; -use nssa::{AccountId, program::Program}; -use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point}; -use sequencer_runner::startup_sequencer; -use tempfile::TempDir; -use tokio::task::JoinHandle; -use wallet::{ - WalletCore, - cli::{ - Command, SubcommandReturnValue, - account::{AccountSubcommand, NewSubcommand}, - config::ConfigSubcommand, - programs::{ - amm::AmmProgramAgnosticSubcommand, native_token_transfer::AuthTransferSubcommand, - pinata::PinataProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand, - }, - }, - config::PersistentStorage, - helperfunctions::{fetch_config, fetch_persistent_storage}, -}; - -use crate::{ - ACC_RECEIVER, ACC_RECEIVER_PRIVATE, ACC_SENDER, ACC_SENDER_PRIVATE, - NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, - fetch_privacy_preserving_tx, make_private_account_input_from_str, - make_public_account_input_from_str, post_test, pre_test, - replace_home_dir_with_temp_dir_in_configs, tps_test_utils::TpsTestManager, - verify_commitment_is_in_state, -}; - -type TestFunction = fn(PathBuf) -> Pin>>; - -pub fn prepare_function_map() -> HashMap { - let mut function_map: HashMap = HashMap::new(); - - #[nssa_integration_test] - pub async fn test_success() { - info!("########## test_success ##########"); - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 9900); - assert_eq!(acc_2_balance.balance, 20100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_move_to_another_account() { - info!("########## test_success_move_to_another_account ##########"); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let PersistentStorage { - accounts: persistent_accounts, - last_synced_block: _, - } = fetch_persistent_storage().await.unwrap(); - - let mut new_persistent_account_id = String::new(); - - for per_acc in persistent_accounts { - if (per_acc.account_id().to_string() != ACC_RECEIVER) - && (per_acc.account_id().to_string() != ACC_SENDER) - { - new_persistent_account_id = per_acc.account_id().to_string(); - } - } - - if new_persistent_account_id == String::new() { - panic!("Failed to produce new account, not present in persistent accounts"); - } - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str( - &new_persistent_account_id, - )), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(new_persistent_account_id) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 9900); - assert_eq!(acc_2_balance.balance, 100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_failure() { - info!("########## test_failure ##########"); - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 1000000, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let failed_send = wallet::cli::execute_subcommand(command).await; - - assert!(failed_send.is_err()); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 10000); - assert_eq!(acc_2_balance.balance, 20000); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_two_transactions() { - info!("########## test_success_two_transactions ##########"); - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 9900); - assert_eq!(acc_2_balance.balance, 20100); - - info!("First TX Success!"); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 9800); - assert_eq!(acc_2_balance.balance, 20200); - - info!("Second TX Success!"); - } - - #[nssa_integration_test] - pub async fn test_get_account() { - info!("########## test_get_account ##########"); - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let account = seq_client - .get_account(ACC_SENDER.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - account.program_owner, - Program::authenticated_transfer_program().id() - ); - assert_eq!(account.balance, 10000); - assert!(account.data.is_empty()); - assert_eq!(account.nonce, 0); - } - - /// This test creates a new token using the token program. After creating the token, the test - /// executes a token transfer to a new account. - #[nssa_integration_test] - pub async fn test_success_token_program() { - info!("########## test_success_token_program ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 - // bytes)] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - // Check the status of the token holding account with the total supply is the expected after - // the execution - let supply_acc = seq_client - .get_account(supply_account_id.to_string()) - .await - .unwrap() - .account; - - // The account must be owned by the token program - assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16 - // bytes) ] First byte of the data equal to 1 means it's a token holding account - assert_eq!(supply_acc.data.as_ref()[0], 1); - // Bytes from 1 to 33 represent the id of the token this account is associated with. - // In this example, this is a token account of the newly created token, so it is expected - // to be equal to the account_id of the token definition account. - assert_eq!( - &supply_acc.data.as_ref()[1..33], - definition_account_id.to_bytes() - ); - assert_eq!( - u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()), - 37 - ); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Check the status of the account at `supply_account_id` is the expected after the - // execution - let supply_acc = seq_client - .get_account(supply_account_id.to_string()) - .await - .unwrap() - .account; - // The account must be owned by the token program - assert_eq!(supply_acc.program_owner, Program::token().id()); - // First byte equal to 1 means it's a token holding account - assert_eq!(supply_acc.data[0], 1); - // Bytes from 1 to 33 represent the id of the token this account is associated with. - assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes()); - assert_eq!( - u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()), - 30 - ); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = seq_client - .get_account(recipient_account_id.to_string()) - .await - .unwrap() - .account; - - // The account must be owned by the token program - assert_eq!(recipient_acc.program_owner, Program::token().id()); - // First byte equal to 1 means it's a token holding account - assert_eq!(recipient_acc.data[0], 1); - // Bytes from 1 to 33 represent the id of the token this account is associated with. - assert_eq!(&recipient_acc.data[1..33], definition_account_id.to_bytes()); - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 7 - ); - - // Burn 3 tokens from `recipient_acc` - let subcommand = TokenProgramAgnosticSubcommand::Burn { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: make_public_account_input_from_str(&recipient_account_id.to_string()), - amount: 3, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = seq_client - .get_account(recipient_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 4 - ); - - // Mint 10 tokens at `recipient_acc` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - holder_npk: None, - holder_ipk: None, - amount: 10, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = seq_client - .get_account(recipient_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 14 - ); - } - - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a private token transfer to a new account. All accounts are private owned - /// except definition which is public. - #[nssa_integration_test] - pub async fn test_success_token_program_private_owned_supply() { - info!("########## test_success_token_program_private_owned_supply ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 - // bytes)] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Transfer additional 7 tokens from `supply_acc` to the account at account_id - // `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Burn 3 tokens from `recipient_acc` - let subcommand = TokenProgramAgnosticSubcommand::Burn { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: make_private_account_input_from_str(&recipient_account_id.to_string()), - amount: 3, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 11 - ); - - // Mint 10 tokens at `recipient_acc` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - holder_npk: None, - holder_ipk: None, - amount: 10, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 21 - ); - - // Now the same mint, but in foreign way - - // Create new account for receiving a mint transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id2, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let (holder_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&recipient_account_id2) - .unwrap(); - - // Mint 9 tokens at `recipient_acc2` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: None, - holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)), - holder_ipk: Some(hex::encode( - holder_keys.incoming_viewing_public_key.0.clone(), - )), - amount: 9, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Sync to claim holder - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id2) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id2` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id2) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 9 - ); - } - - /// This test creates a new private token using the token program. All accounts are private - /// owned except supply which is public. - #[nssa_integration_test] - pub async fn test_success_token_program_private_owned_definition() { - info!("########## test_success_token_program_private_owned_definition ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (private) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (public) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_private_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&definition_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Check the status of the token definition account is the expected after the execution - let supply_acc = seq_client - .get_account(supply_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token holding account has the following layout: - // [ 0x01 || definition id (32 bytes) || balance (little endian 16 bytes) ] - assert_eq!( - supply_acc.data.as_ref(), - &[ - 1, 128, 101, 5, 31, 43, 36, 97, 108, 164, 92, 25, 157, 173, 5, 14, 194, 121, 239, - 84, 19, 160, 243, 47, 193, 2, 250, 247, 232, 253, 191, 232, 173, 37, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - // Create new account for receiving a mint transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id_pr, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new account for receiving a mint transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id_pub, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Mint 10 tokens at `recipient_acc_pub` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: Some(make_public_account_input_from_str( - &recipient_account_id_pub.to_string(), - )), - holder_npk: None, - holder_ipk: None, - amount: 10, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - // Check the status of the account at `recipient_account_id_pub` is the expected after the - // execution - let recipient_acc_pub = seq_client - .get_account(recipient_account_id_pub.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(recipient_acc_pub.data[33..].try_into().unwrap()), - 10 - ); - - let (holder_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&recipient_account_id_pr) - .unwrap(); - - // Mint 5 tokens at `recipient_acc_pr` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: None, - holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)), - holder_ipk: Some(hex::encode( - holder_keys.incoming_viewing_public_key.0.clone(), - )), - amount: 5, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Sync to claim holder - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id_pr) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id_pr` is the expected after the - // execution - let recipient_acc_pr = wallet_storage - .get_account_private(&recipient_account_id_pr) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc_pr.data[33..].try_into().unwrap()), - 5 - ); - - // Mint 5 tokens at `recipient_acc_pr` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: Some(make_private_account_input_from_str( - &recipient_account_id_pr.to_string(), - )), - holder_npk: None, - holder_ipk: None, - amount: 5, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id_pr) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id_pr` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id_pr) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 10 - ); - - // Burn 5 tokens at `recipient_acc_pub` - let subcommand = TokenProgramAgnosticSubcommand::Burn { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: make_public_account_input_from_str(&recipient_account_id_pub.to_string()), - amount: 5, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - // Check the status of the account at `recipient_account_id_pub` is the expected after the - // execution - let recipient_acc_pub = seq_client - .get_account(recipient_account_id_pub.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(recipient_acc_pub.data[33..].try_into().unwrap()), - 5 - ); - - // Burn 5 tokens at `recipient_acc_pr` - let subcommand = TokenProgramAgnosticSubcommand::Burn { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: make_private_account_input_from_str(&recipient_account_id_pr.to_string()), - amount: 5, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id_pr) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id_pr` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id_pr) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 5 - ); - } - - /// This test creates a new private token using the token program. All accounts are private - /// owned. - #[nssa_integration_test] - pub async fn test_success_token_program_private_owned_definition_and_supply() { - info!( - "########## test_success_token_program_private_owned_definition_and_supply ##########" - ); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (private) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_private_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&definition_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - let supply_acc = wallet_storage - .get_account_private(&supply_account_id) - .unwrap(); - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 - // bytes)] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token holding account has the following layout: - // [ 0x01 || definition id (32 bytes) || balance (little endian 16 bytes) ] - assert_eq!( - supply_acc.data.as_ref(), - &[ - 1, 128, 101, 5, 31, 43, 36, 97, 108, 164, 92, 25, 157, 173, 5, 14, 194, 121, 239, - 84, 19, 160, 243, 47, 193, 2, 250, 247, 232, 253, 191, 232, 173, 37, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - } - - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a private token transfer to a new account. - #[nssa_integration_test] - pub async fn test_success_token_program_private_claiming_path() { - info!("########## test_success_token_program_private_claiming_path ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 - // bytes)] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let (recipient_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&recipient_account_id) - .unwrap(); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: None, - to_npk: Some(hex::encode(recipient_keys.nullifer_public_key.0)), - to_ipk: Some(hex::encode( - recipient_keys.incoming_viewing_public_key.0.clone(), - )), - amount: 7, - }; - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - } - - /// This test creates a new public token using the token program. After creating the token, the - /// test executes a shielded token transfer to a new account. All accounts are owned except - /// definition. - #[nssa_integration_test] - pub async fn test_success_token_program_shielded_owned() { - info!("########## test_success_token_program_shielded_owned ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (public) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 - // bytes)] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Transfer additional 7 tokens from `supply_acc` to the account at account_id - // `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - } - - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a deshielded token transfer to a new account. All accounts are owned - /// except definition. - #[nssa_integration_test] - pub async fn test_success_token_program_deshielded_owned() { - info!("########## test_success_token_program_deshielded_owned ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 - // bytes)] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Transfer additional 7 tokens from `supply_acc` to the account at account_id - // `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - } - - #[nssa_integration_test] - pub async fn test_success_private_transfer_to_another_owned_account() { - info!("########## test_success_private_transfer_to_another_owned_account ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - let to: AccountId = ACC_RECEIVER_PRIVATE.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str(&to.to_string())), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage.get_private_account_commitment(&to).unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_private_transfer_to_another_foreign_account() { - info!("########## test_success_private_transfer_to_another_foreign_account ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - let to_npk = NullifierPublicKey([42; 32]); - let to_npk_string = hex::encode(to_npk.0); - let to_ipk = Secp256k1Point::from_scalar(to_npk.0); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: None, - to_npk: Some(to_npk_string), - to_ipk: Some(hex::encode(to_ipk.0)), - amount: 100, - }); - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - - let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; - assert_eq!(tx.message.new_commitments[0], new_commitment1); - - assert_eq!(tx.message.new_commitments.len(), 2); - for commitment in tx.message.new_commitments.into_iter() { - assert!(verify_commitment_is_in_state(commitment, &seq_client).await); - } - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_private_transfer_to_another_owned_account_claiming_path() { - info!( - "########## test_success_private_transfer_to_another_owned_account_claiming_path ##########" - ); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - - let command = - Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let (to_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&to_account_id) - .cloned() - .unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: None, - to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)), - to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)), - amount: 100, - }); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { - panic!("FAILED TO SEND TX"); - }; - - let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; - - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::cli::execute_subcommand(command).await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - assert_eq!(tx.message.new_commitments[0], new_commitment1); - - assert_eq!(tx.message.new_commitments.len(), 2); - for commitment in tx.message.new_commitments.into_iter() { - assert!(verify_commitment_is_in_state(commitment, &seq_client).await); - } - - let to_res_acc = wallet_storage.get_account_private(&to_account_id).unwrap(); - - assert_eq!(to_res_acc.balance, 100); - - info!("Success!"); - } - - // #[nssa_integration_test] - // pub async fn test_success_private_transfer_to_another_owned_account_cont_run_path() { - // info!( - // "########## test_success_private_transfer_to_another_owned_account_cont_run_path - // ##########" ); - // let continious_run_handle = tokio::spawn(wallet::cli::execute_continious_run()); - - // let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - - // let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {})); - - // let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - // let SubcommandReturnValue::RegisterAccount { - // account_id: to_account_id, - // } = sub_ret - // else { - // panic!("FAILED TO REGISTER ACCOUNT"); - // }; - - // let wallet_config = fetch_config().await.unwrap(); - // let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - // let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - // .await - // .unwrap(); - - // let (to_keys, _) = wallet_storage - // .storage - // .user_data - // .user_private_accounts - // .get(&to_account_id) - // .cloned() - // .unwrap(); - - // let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - // from: make_private_account_input_from_str(&from.to_string()), - // to: None, - // to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)), - // to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)), - // amount: 100, - // }); - - // let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - // let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { - // panic!("FAILED TO SEND TX"); - // }; - - // let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; - // println!("Waiting for next blocks to check if continoius run fetch account"); - // tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - // tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - // .await - // .unwrap(); - - // assert_eq!(tx.message.new_commitments.len(), 2); - // for commitment in tx.message.new_commitments.into_iter() { - // assert!(verify_commitment_is_in_state(commitment, &seq_client).await); - // } - - // let to_res_acc = wallet_storage.get_account_private(&to_account_id).unwrap(); - - // assert_eq!(to_res_acc.balance, 100); - - // continious_run_handle.abort(); - - // info!("Success!"); - // } - - #[nssa_integration_test] - pub async fn test_success_deshielded_transfer_to_another_account() { - info!("########## test_success_deshielded_transfer_to_another_account ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - let to: AccountId = ACC_RECEIVER.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_public_account_input_from_str(&to.to_string())), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let from_acc = wallet_storage.get_account_private(&from).unwrap(); - assert_eq!(from_acc.balance, 10000); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let from_acc = wallet_storage.get_account_private(&from).unwrap(); - let new_commitment = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment, &seq_client).await); - - let acc_2_balance = seq_client - .get_account_balance(to.to_string()) - .await - .unwrap(); - - assert_eq!(from_acc.balance, 10000 - 100); - assert_eq!(acc_2_balance.balance, 20100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_shielded_transfer_to_another_owned_account() { - info!("########## test_success_shielded_transfer_to_another_owned_account ##########"); - let from: AccountId = ACC_SENDER.parse().unwrap(); - let to: AccountId = ACC_RECEIVER_PRIVATE.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str(&to.to_string())), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let acc_to = wallet_storage.get_account_private(&to).unwrap(); - let new_commitment = wallet_storage.get_private_account_commitment(&to).unwrap(); - assert!(verify_commitment_is_in_state(new_commitment, &seq_client).await); - - let acc_from_balance = seq_client - .get_account_balance(from.to_string()) - .await - .unwrap(); - - assert_eq!(acc_from_balance.balance, 9900); - assert_eq!(acc_to.balance, 20000 + 100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_shielded_transfer_to_another_foreign_account() { - info!("########## test_success_shielded_transfer_to_another_foreign_account ##########"); - let to_npk = NullifierPublicKey([42; 32]); - let to_npk_string = hex::encode(to_npk.0); - let to_ipk = Secp256k1Point::from_scalar(to_npk.0); - let from: AccountId = ACC_SENDER.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: None, - to_npk: Some(to_npk_string), - to_ipk: Some(hex::encode(to_ipk.0)), - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash).await; - - let acc_1_balance = seq_client - .get_account_balance(from.to_string()) - .await - .unwrap(); - - assert!( - verify_commitment_is_in_state(tx.message.new_commitments[0].clone(), &seq_client).await - ); - - assert_eq!(acc_1_balance.balance, 9900); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_pinata() { - info!("########## test_pinata ##########"); - let pinata_account_id = PINATA_BASE58; - - let pinata_prize = 150; - let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: make_public_account_input_from_str(ACC_SENDER), - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let pinata_balance_pre = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let pinata_balance_post = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let winner_balance_post = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap() - .balance; - - assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); - assert_eq!(winner_balance_post, 10000 + pinata_prize); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_program_deployment() { - info!("########## test program deployment ##########"); - - let manifest_dir = env!("CARGO_MANIFEST_DIR"); - let binary_filepath: PathBuf = PathBuf::from(manifest_dir) - .join("../artifacts/test_program_methods") - .join(NSSA_PROGRAM_FOR_TEST_DATA_CHANGER); - - let command = Command::DeployProgram { - binary_filepath: binary_filepath.clone(), - }; - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // The program is the data changer and takes one account as input. - // We pass an uninitialized account and we expect after execution to be owned by the data - // changer program (NSSA account claiming mechanism) with data equal to [0] (due to program - // logic) - // - let bytecode = std::fs::read(binary_filepath).unwrap(); - let data_changer = Program::new(bytecode).unwrap(); - let account_id: AccountId = "11".repeat(16).parse().unwrap(); - let message = nssa::public_transaction::Message::try_new( - data_changer.id(), - vec![account_id], - vec![], - vec![0], - ) - .unwrap(); - let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); - let transaction = nssa::PublicTransaction::new(message, witness_set); - let _response = seq_client.send_tx_public(transaction).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let post_state_account = seq_client - .get_account(account_id.to_string()) - .await - .unwrap() - .account; - assert_eq!(post_state_account.program_owner, data_changer.id()); - assert_eq!(post_state_account.balance, 0); - assert_eq!(post_state_account.data.as_ref(), &[0]); - assert_eq!(post_state_account.nonce, 0); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_authenticated_transfer_initialize_function() { - info!("########## test initialize account for authenticated transfer ##########"); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); - let SubcommandReturnValue::RegisterAccount { account_id } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("Error creating account"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Init { - account_id: make_public_account_input_from_str(&account_id.to_string()), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Checking correct execution"); - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let account = seq_client - .get_account(account_id.to_string()) - .await - .unwrap() - .account; - - let expected_program_owner = Program::authenticated_transfer_program().id(); - let expected_nonce = 1; - let expected_balance = 0; - - assert_eq!(account.program_owner, expected_program_owner); - assert_eq!(account.balance, expected_balance); - assert_eq!(account.nonce, expected_nonce); - assert!(account.data.is_empty()); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_authenticated_transfer_initialize_function_private() { - info!("########## test initialize private account for authenticated transfer ##########"); - let command = - Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); - let SubcommandReturnValue::RegisterAccount { account_id } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("Error creating account"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Init { - account_id: make_private_account_input_from_str(&account_id.to_string()), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct execution"); - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let account = wallet_storage.get_account_private(&account_id).unwrap(); - - let expected_program_owner = Program::authenticated_transfer_program().id(); - let expected_balance = 0; - - assert_eq!(account.program_owner, expected_program_owner); - assert_eq!(account.balance, expected_balance); - assert!(account.data.is_empty()); - } - - #[nssa_integration_test] - pub async fn test_pinata_private_receiver() { - info!("########## test_pinata_private_receiver ##########"); - let pinata_account_id = PINATA_BASE58; - let pinata_prize = 150; - - let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: make_private_account_input_from_str(ACC_SENDER_PRIVATE), - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let pinata_balance_pre = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let pinata_balance_post = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&ACC_SENDER_PRIVATE.parse().unwrap()) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_pinata_private_receiver_new_account() { - info!("########## test_pinata_private_receiver_new_account ##########"); - let pinata_account_id = PINATA_BASE58; - let pinata_prize = 150; - - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: winner_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: make_private_account_input_from_str(&winner_account_id.to_string()), - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let pinata_balance_pre = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let pinata_balance_post = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&winner_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_modify_config_fields() { - info!("########## test_modify_config_fields ##########"); - - let wallet_config = fetch_config().await.unwrap(); - let old_seq_poll_timeout_millis = wallet_config.seq_poll_timeout_millis; - - // Change config field - let command = Command::Config(ConfigSubcommand::Set { - key: "seq_poll_timeout_millis".to_string(), - value: "1000".to_string(), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - - assert_eq!(wallet_config.seq_poll_timeout_millis, 1000); - - // Return how it was at the beginning - let command = Command::Config(ConfigSubcommand::Set { - key: "seq_poll_timeout_millis".to_string(), - value: old_seq_poll_timeout_millis.to_string(), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_keys_restoration() { - info!("########## test_keys_restoration ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { - cci: Some(ChainIndex::root()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id1, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { - cci: Some(ChainIndex::from_str("/0").unwrap()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id2, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str( - &to_account_id1.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str( - &to_account_id2.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 101, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let from: AccountId = ACC_SENDER.parse().unwrap(); - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { - cci: Some(ChainIndex::root()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id3, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { - cci: Some(ChainIndex::from_str("/0").unwrap()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id4, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: Some(make_public_account_input_from_str( - &to_account_id3.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 102, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: Some(make_public_account_input_from_str( - &to_account_id4.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 103, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("########## PREPARATION END ##########"); - - wallet::cli::execute_keys_restoration("test_pass".to_string(), 10) - .await - .unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let acc1 = wallet_storage - .storage - .user_data - .private_key_tree - .get_node(to_account_id1) - .expect("Acc 1 should be restored"); - - let acc2 = wallet_storage - .storage - .user_data - .private_key_tree - .get_node(to_account_id2) - .expect("Acc 2 should be restored"); - - let _ = wallet_storage - .storage - .user_data - .public_key_tree - .get_node(to_account_id3) - .expect("Acc 3 should be restored"); - - let _ = wallet_storage - .storage - .user_data - .public_key_tree - .get_node(to_account_id4) - .expect("Acc 4 should be restored"); - - assert_eq!( - acc1.value.1.program_owner, - Program::authenticated_transfer_program().id() - ); - assert_eq!( - acc2.value.1.program_owner, - Program::authenticated_transfer_program().id() - ); - - assert_eq!(acc1.value.1.balance, 100); - assert_eq!(acc2.value.1.balance, 101); - - info!("########## TREE CHECKS END ##########"); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&to_account_id1.to_string()), - to: Some(make_private_account_input_from_str( - &to_account_id2.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 10, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&to_account_id3.to_string()), - to: Some(make_public_account_input_from_str( - &to_account_id4.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 11, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let comm1 = wallet_storage - .get_private_account_commitment(&to_account_id1) - .expect("Acc 1 commitment should exist"); - let comm2 = wallet_storage - .get_private_account_commitment(&to_account_id2) - .expect("Acc 2 commitment should exist"); - - assert!(verify_commitment_is_in_state(comm1, &seq_client).await); - assert!(verify_commitment_is_in_state(comm2, &seq_client).await); - - let acc3 = seq_client - .get_account_balance(to_account_id3.to_string()) - .await - .expect("Acc 3 must be present in public state"); - let acc4 = seq_client - .get_account_balance(to_account_id4.to_string()) - .await - .expect("Acc 4 must be present in public state"); - - assert_eq!(acc3.balance, 91); - assert_eq!(acc4.balance, 114); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_amm_public() { - info!("########## test_amm_public ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id_1, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id_1, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id_1, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token definition - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id_2, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id_2, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id_2, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id_1.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id_1.to_string()), - name: "A NAM1".to_string(), - total_supply: 37, - }; - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id_1.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id_1.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id_2.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id_2.to_string()), - name: "A NAM2".to_string(), - total_supply: 37, - }; - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id_2.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id_2.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("=================== SETUP FINISHED ==============="); - - // Create new AMM - - // Setup accounts - // Create new account for the user holding lp - let SubcommandReturnValue::RegisterAccount { - account_id: user_holding_lp, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Send creation tx - let subcommand = AmmProgramAgnosticSubcommand::New { - user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()), - balance_a: 3, - balance_b: 3, - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), - 4 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), - 4 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), - 3 - ); - - info!("=================== AMM DEFINITION FINISHED ==============="); - - // Make swap - - let subcommand = AmmProgramAgnosticSubcommand::Swap { - user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - amount_in: 2, - min_amount_out: 1, - token_definition: definition_account_id_1.to_string(), - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), - 2 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), - 5 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), - 3 - ); - - info!("=================== FIRST SWAP FINISHED ==============="); - - // Make swap - - let subcommand = AmmProgramAgnosticSubcommand::Swap { - user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - amount_in: 2, - min_amount_out: 1, - token_definition: definition_account_id_2.to_string(), - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), - 4 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), - 3 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), - 3 - ); - - info!("=================== SECOND SWAP FINISHED ==============="); - - // Add liquidity - - let subcommand = AmmProgramAgnosticSubcommand::AddLiquidity { - user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()), - min_amount_lp: 1, - max_amount_a: 2, - max_amount_b: 2, - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), - 3 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), - 1 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), - 4 - ); - - info!("=================== ADD LIQ FINISHED ==============="); - - // Remove liquidity - - let subcommand = AmmProgramAgnosticSubcommand::RemoveLiquidity { - user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()), - balance_lp: 2, - min_amount_a: 1, - min_amount_b: 1, - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), - 5 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), - 4 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), - 2 - ); - - info!("Success!"); - } - - println!("{function_map:#?}"); - - function_map -} - -#[allow(clippy::type_complexity)] -async fn pre_tps_test( - test: &TpsTestManager, -) -> Result<(ServerHandle, JoinHandle>, TempDir)> { - info!("Generating tps test config"); - let mut sequencer_config = test.generate_tps_test_config(); - info!("Done"); - - let temp_dir_sequencer = replace_home_dir_with_temp_dir_in_configs(&mut sequencer_config); - - let (seq_http_server_handle, sequencer_loop_handle) = - startup_sequencer(sequencer_config).await?; - - Ok(( - seq_http_server_handle, - sequencer_loop_handle, - temp_dir_sequencer, - )) -} - -pub async fn tps_test() { - let num_transactions = 300 * 5; - let target_tps = 12; - let tps_test = TpsTestManager::new(target_tps, num_transactions); - - let target_time = tps_test.target_time(); - info!("Target time: {:?} seconds", target_time.as_secs()); - let res = pre_tps_test(&tps_test).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - info!("TPS test begin"); - let txs = tps_test.build_public_txs(); - let now = Instant::now(); - - let mut tx_hashes = vec![]; - for (i, tx) in txs.into_iter().enumerate() { - let tx_hash = seq_client.send_tx_public(tx).await.unwrap().tx_hash; - info!("Sent tx {i}"); - tx_hashes.push(tx_hash); - } - - for (i, tx_hash) in tx_hashes.iter().enumerate() { - loop { - if now.elapsed().as_millis() > target_time.as_millis() { - panic!("TPS test failed by timeout"); - } - - let tx_obj = seq_client - .get_transaction_by_hash(tx_hash.clone()) - .await - .inspect_err(|err| { - log::warn!( - "Failed to get transaction by hash {tx_hash:#?} with error: {err:#?}" - ) - }); - - if let Ok(tx_obj) = tx_obj - && tx_obj.transaction.is_some() - { - info!("Found tx {i} with hash {tx_hash}"); - break; - } - } - } - let time_elapsed = now.elapsed().as_secs(); - - info!("TPS test finished successfully"); - info!("Target TPS: {}", target_tps); - info!( - "Processed {} transactions in {}s", - tx_hashes.len(), - time_elapsed - ); - info!("Target time: {:?}s", target_time.as_secs()); - - post_test(res).await; -} diff --git a/integration_tests/tests/account.rs b/integration_tests/tests/account.rs new file mode 100644 index 00000000..e5e700dd --- /dev/null +++ b/integration_tests/tests/account.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use integration_tests::{ACC_SENDER, TestContext}; +use log::info; +use nssa::program::Program; +use tokio::test; + +#[test] +async fn get_existing_account() -> Result<()> { + let ctx = TestContext::new().await?; + + let account = ctx + .sequencer_client() + .get_account(ACC_SENDER.to_string()) + .await? + .account; + + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!(account.balance, 10000); + assert!(account.data.is_empty()); + assert_eq!(account.nonce, 0); + + info!("Successfully retrieved account with correct details"); + + Ok(()) +} diff --git a/integration_tests/tests/amm.rs b/integration_tests/tests/amm.rs new file mode 100644 index 00000000..073cd03d --- /dev/null +++ b/integration_tests/tests/amm.rs @@ -0,0 +1,405 @@ +use std::time::Duration; + +use anyhow::Result; +use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id}; +use log::info; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::{amm::AmmProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand}, +}; + +#[test] +async fn amm_public() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create new account for the token definition + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id_1, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token supply holder + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id_1, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for receiving a token transaction + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_1, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token definition + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id_2, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token supply holder + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id_2, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for receiving a token transaction + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_2, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id_1.to_string()), + supply_account_id: format_public_account_id(&supply_account_id_1.to_string()), + name: "A NAM1".to_string(), + total_supply: 37, + }; + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1` + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_public_account_id(&supply_account_id_1.to_string()), + to: Some(format_public_account_id( + &recipient_account_id_1.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id_2.to_string()), + supply_account_id: format_public_account_id(&supply_account_id_2.to_string()), + name: "A NAM2".to_string(), + total_supply: 37, + }; + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_2` + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_public_account_id(&supply_account_id_2.to_string()), + to: Some(format_public_account_id( + &recipient_account_id_2.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("=================== SETUP FINISHED ==============="); + + // Create new AMM + + // Setup accounts + // Create new account for the user holding lp + let SubcommandReturnValue::RegisterAccount { + account_id: user_holding_lp, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Send creation tx + let subcommand = AmmProgramAgnosticSubcommand::New { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + user_holding_lp: format_public_account_id(&user_holding_lp.to_string()), + balance_a: 3, + balance_b: 3, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 4 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 4 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 3 + ); + + info!("=================== AMM DEFINITION FINISHED ==============="); + + // Make swap + + let subcommand = AmmProgramAgnosticSubcommand::Swap { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + amount_in: 2, + min_amount_out: 1, + token_definition: definition_account_id_1.to_string(), + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 2 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 5 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 3 + ); + + info!("=================== FIRST SWAP FINISHED ==============="); + + // Make swap + + let subcommand = AmmProgramAgnosticSubcommand::Swap { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + amount_in: 2, + min_amount_out: 1, + token_definition: definition_account_id_2.to_string(), + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 4 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 3 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 3 + ); + + info!("=================== SECOND SWAP FINISHED ==============="); + + // Add liquidity + + let subcommand = AmmProgramAgnosticSubcommand::AddLiquidity { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + user_holding_lp: format_public_account_id(&user_holding_lp.to_string()), + min_amount_lp: 1, + max_amount_a: 2, + max_amount_b: 2, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 3 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 1 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 4 + ); + + info!("=================== ADD LIQ FINISHED ==============="); + + // Remove liquidity + + let subcommand = AmmProgramAgnosticSubcommand::RemoveLiquidity { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + user_holding_lp: format_public_account_id(&user_holding_lp.to_string()), + balance_lp: 2, + min_amount_a: 1, + min_amount_b: 1, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 5 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 4 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 2 + ); + + info!("Success!"); + + Ok(()) +} diff --git a/integration_tests/tests/auth_transfer/main.rs b/integration_tests/tests/auth_transfer/main.rs new file mode 100644 index 00000000..c97008bd --- /dev/null +++ b/integration_tests/tests/auth_transfer/main.rs @@ -0,0 +1,2 @@ +mod private; +mod public; diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs new file mode 100644 index 00000000..4674ed0b --- /dev/null +++ b/integration_tests/tests/auth_transfer/private.rs @@ -0,0 +1,417 @@ +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use integration_tests::{ + ACC_RECEIVER, ACC_RECEIVER_PRIVATE, ACC_SENDER, ACC_SENDER_PRIVATE, + TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx, + format_private_account_id, format_public_account_id, verify_commitment_is_in_state, +}; +use log::info; +use nssa::{AccountId, program::Program}; +use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point}; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::native_token_transfer::AuthTransferSubcommand, +}; + +#[test] +async fn private_transfer_to_owned_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + let to: AccountId = ACC_RECEIVER_PRIVATE.parse()?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: Some(format_private_account_id(&to.to_string())), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&from) + .context("Failed to get private account commitment for sender")?; + assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await); + + let new_commitment2 = ctx + .wallet() + .get_private_account_commitment(&to) + .context("Failed to get private account commitment for receiver")?; + assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await); + + info!("Successfully transferred privately to owned account"); + + Ok(()) +} + +#[test] +async fn private_transfer_to_foreign_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + let to_npk = NullifierPublicKey([42; 32]); + let to_npk_string = hex::encode(to_npk.0); + let to_ipk = Secp256k1Point::from_scalar(to_npk.0); + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: None, + to_npk: Some(to_npk_string), + to_ipk: Some(hex::encode(to_ipk.0)), + amount: 100, + }); + + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = result else { + anyhow::bail!("Expected PrivacyPreservingTransfer return value"); + }; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&from) + .context("Failed to get private account commitment for sender")?; + + let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await; + assert_eq!(tx.message.new_commitments[0], new_commitment1); + + assert_eq!(tx.message.new_commitments.len(), 2); + for commitment in tx.message.new_commitments.into_iter() { + assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); + } + + info!("Successfully transferred privately to foreign account"); + + Ok(()) +} + +#[test] +async fn deshielded_transfer_to_public_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + let to: AccountId = ACC_RECEIVER.parse()?; + + // Check initial balance of the private sender + let from_acc = ctx + .wallet() + .get_account_private(&from) + .context("Failed to get sender's private account")?; + assert_eq!(from_acc.balance, 10000); + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: Some(format_public_account_id(&to.to_string())), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let from_acc = ctx + .wallet() + .get_account_private(&from) + .context("Failed to get sender's private account")?; + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&from) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(to.to_string()) + .await?; + + assert_eq!(from_acc.balance, 9900); + assert_eq!(acc_2_balance.balance, 20100); + + info!("Successfully deshielded transfer to public account"); + + Ok(()) +} + +#[test] +async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + + // Create a new private account + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); + + let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id, + } = sub_ret + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Get the keys for the newly created account + let (to_keys, _) = ctx + .wallet() + .storage() + .user_data + .get_private_account(&to_account_id) + .cloned() + .context("Failed to get private account")?; + + // Send to this account using claiming path (using npk and ipk instead of account ID) + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: None, + to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)), + to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)), + amount: 100, + }); + + let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { + anyhow::bail!("Expected PrivacyPreservingTransfer return value"); + }; + + let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await; + + // Sync the wallet to claim the new account + let command = Command::Account(AccountSubcommand::SyncPrivate {}); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&from) + .context("Failed to get private account commitment for sender")?; + assert_eq!(tx.message.new_commitments[0], new_commitment1); + + assert_eq!(tx.message.new_commitments.len(), 2); + for commitment in tx.message.new_commitments.into_iter() { + assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); + } + + let to_res_acc = ctx + .wallet() + .get_account_private(&to_account_id) + .context("Failed to get recipient's private account")?; + assert_eq!(to_res_acc.balance, 100); + + info!("Successfully transferred using claiming path"); + + Ok(()) +} + +#[test] +async fn shielded_transfer_to_owned_private_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER.parse()?; + let to: AccountId = ACC_RECEIVER_PRIVATE.parse()?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&from.to_string()), + to: Some(format_private_account_id(&to.to_string())), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let acc_to = ctx + .wallet() + .get_account_private(&to) + .context("Failed to get receiver's private account")?; + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&to) + .context("Failed to get receiver's commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let acc_from_balance = ctx + .sequencer_client() + .get_account_balance(from.to_string()) + .await?; + + assert_eq!(acc_from_balance.balance, 9900); + assert_eq!(acc_to.balance, 20100); + + info!("Successfully shielded transfer to owned private account"); + + Ok(()) +} + +#[test] +async fn shielded_transfer_to_foreign_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let to_npk = NullifierPublicKey([42; 32]); + let to_npk_string = hex::encode(to_npk.0); + let to_ipk = Secp256k1Point::from_scalar(to_npk.0); + let from: AccountId = ACC_SENDER.parse()?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&from.to_string()), + to: None, + to_npk: Some(to_npk_string), + to_ipk: Some(hex::encode(to_ipk.0)), + amount: 100, + }); + + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = result else { + anyhow::bail!("Expected PrivacyPreservingTransfer return value"); + }; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash).await; + + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(from.to_string()) + .await?; + + assert!( + verify_commitment_is_in_state( + tx.message.new_commitments[0].clone(), + ctx.sequencer_client() + ) + .await + ); + + assert_eq!(acc_1_balance.balance, 9900); + + info!("Successfully shielded transfer to foreign account"); + + Ok(()) +} + +#[test] +#[ignore = "Flaky, TODO: #197"] +async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // NOTE: This test needs refactoring - continuous run mode doesn't work well with TestContext + // The original implementation spawned wallet::cli::execute_continuous_run() in background + // but this conflicts with TestContext's wallet management + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + + // Create a new private account + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); + let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id, + } = sub_ret + else { + anyhow::bail!("Failed to register account"); + }; + + // Get the newly created account's keys + let (to_keys, _) = ctx + .wallet() + .storage() + .user_data + .get_private_account(&to_account_id) + .cloned() + .context("Failed to get private account")?; + + // Send transfer using nullifier and incoming viewing public keys + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: None, + to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)), + to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)), + amount: 100, + }); + + let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { + anyhow::bail!("Failed to send transaction"); + }; + + let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await; + + info!("Waiting for next blocks to check if continuous run fetches account"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify commitments are in state + assert_eq!(tx.message.new_commitments.len(), 2); + for commitment in tx.message.new_commitments.into_iter() { + assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); + } + + // Verify receiver account balance + let to_res_acc = ctx + .wallet() + .get_account_private(&to_account_id) + .context("Failed to get receiver account")?; + + assert_eq!(to_res_acc.balance, 100); + + Ok(()) +} + +#[test] +async fn initialize_private_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { account_id } = result else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Init { + account_id: format_private_account_id(&account_id.to_string()), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Syncing private accounts"); + let command = Command::Account(AccountSubcommand::SyncPrivate {}); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&account_id) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let account = ctx + .wallet() + .get_account_private(&account_id) + .context("Failed to get private account")?; + + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!(account.balance, 0); + assert!(account.data.is_empty()); + + info!("Successfully initialized private account"); + + Ok(()) +} diff --git a/integration_tests/tests/auth_transfer/public.rs b/integration_tests/tests/auth_transfer/public.rs new file mode 100644 index 00000000..467e3ebc --- /dev/null +++ b/integration_tests/tests/auth_transfer/public.rs @@ -0,0 +1,248 @@ +use std::time::Duration; + +use anyhow::Result; +use integration_tests::{ + ACC_RECEIVER, ACC_SENDER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id, +}; +use log::info; +use nssa::program::Program; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::native_token_transfer::AuthTransferSubcommand, +}; + +#[test] +async fn successful_transfer_to_existing_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(ACC_RECEIVER)), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(ACC_RECEIVER.to_string()) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 9900); + assert_eq!(acc_2_balance.balance, 20100); + + Ok(()) +} + +#[test] +pub async fn successful_transfer_to_new_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command) + .await + .unwrap(); + + let new_persistent_account_id = ctx + .wallet() + .storage() + .user_data + .account_ids() + .map(ToString::to_string) + .find(|acc_id| acc_id != ACC_SENDER && acc_id != ACC_RECEIVER) + .expect("Failed to find newly created account in the wallet storage"); + + if new_persistent_account_id == String::new() { + panic!("Failed to produce new account, not present in persistent accounts"); + } + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(&new_persistent_account_id)), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(new_persistent_account_id) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 9900); + assert_eq!(acc_2_balance.balance, 100); + + Ok(()) +} + +#[test] +async fn failed_transfer_with_insufficient_balance() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(ACC_RECEIVER)), + to_npk: None, + to_ipk: None, + amount: 1000000, + }); + + let failed_send = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await; + assert!(failed_send.is_err()); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking balances unchanged"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(ACC_RECEIVER.to_string()) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 10000); + assert_eq!(acc_2_balance.balance, 20000); + + Ok(()) +} + +#[test] +async fn two_consecutive_successful_transfers() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // First transfer + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(ACC_RECEIVER)), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move after first transfer"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(ACC_RECEIVER.to_string()) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 9900); + assert_eq!(acc_2_balance.balance, 20100); + + info!("First TX Success!"); + + // Second transfer + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(ACC_RECEIVER)), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move after second transfer"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(ACC_RECEIVER.to_string()) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 9800); + assert_eq!(acc_2_balance.balance, 20200); + + info!("Second TX Success!"); + + Ok(()) +} + +#[test] +async fn initialize_public_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { account_id } = result else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Init { + account_id: format_public_account_id(&account_id.to_string()), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Checking correct execution"); + let account = ctx + .sequencer_client() + .get_account(account_id.to_string()) + .await? + .account; + + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!(account.balance, 0); + assert_eq!(account.nonce, 1); + assert!(account.data.is_empty()); + + info!("Successfully initialized public account"); + + Ok(()) +} diff --git a/integration_tests/tests/config.rs b/integration_tests/tests/config.rs new file mode 100644 index 00000000..ca800d0f --- /dev/null +++ b/integration_tests/tests/config.rs @@ -0,0 +1,33 @@ +use anyhow::Result; +use integration_tests::TestContext; +use log::info; +use tokio::test; +use wallet::cli::{Command, config::ConfigSubcommand}; + +#[test] +async fn modify_config_field() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let old_seq_poll_timeout_millis = ctx.wallet().config().seq_poll_timeout_millis; + + // Change config field + let command = Command::Config(ConfigSubcommand::Set { + key: "seq_poll_timeout_millis".to_string(), + value: "1000".to_string(), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let new_seq_poll_timeout_millis = ctx.wallet().config().seq_poll_timeout_millis; + assert_eq!(new_seq_poll_timeout_millis, 1000); + + // Return how it was at the beginning + let command = Command::Config(ConfigSubcommand::Set { + key: "seq_poll_timeout_millis".to_string(), + value: old_seq_poll_timeout_millis.to_string(), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Successfully modified and restored config field"); + + Ok(()) +} diff --git a/integration_tests/tests/keys_restoration.rs b/integration_tests/tests/keys_restoration.rs new file mode 100644 index 00000000..9076c87f --- /dev/null +++ b/integration_tests/tests/keys_restoration.rs @@ -0,0 +1,217 @@ +use std::{str::FromStr, time::Duration}; + +use anyhow::Result; +use integration_tests::{ + ACC_SENDER, ACC_SENDER_PRIVATE, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, + format_private_account_id, format_public_account_id, verify_commitment_is_in_state, +}; +use key_protocol::key_management::key_tree::chain_index::ChainIndex; +use log::info; +use nssa::{AccountId, program::Program}; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::native_token_transfer::AuthTransferSubcommand, +}; + +#[test] +async fn restore_keys_from_seed() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + + // Create first private account at root + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: Some(ChainIndex::root()), + })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id1, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create second private account at /0 + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: Some(ChainIndex::from_str("/0")?), + })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id2, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Send to first private account + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: Some(format_private_account_id(&to_account_id1.to_string())), + to_npk: None, + to_ipk: None, + amount: 100, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + // Send to second private account + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: Some(format_private_account_id(&to_account_id2.to_string())), + to_npk: None, + to_ipk: None, + amount: 101, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let from: AccountId = ACC_SENDER.parse()?; + + // Create first public account at root + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: Some(ChainIndex::root()), + })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id3, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create second public account at /0 + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: Some(ChainIndex::from_str("/0")?), + })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id4, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Send to first public account + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&from.to_string()), + to: Some(format_public_account_id(&to_account_id3.to_string())), + to_npk: None, + to_ipk: None, + amount: 102, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + // Send to second public account + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&from.to_string()), + to: Some(format_public_account_id(&to_account_id4.to_string())), + to_npk: None, + to_ipk: None, + amount: 103, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Preparation complete, performing keys restoration"); + + // Restore keys from seed + wallet::cli::execute_keys_restoration(ctx.wallet_mut(), 10).await?; + + // Verify restored private accounts + let acc1 = ctx + .wallet() + .storage() + .user_data + .private_key_tree + .get_node(to_account_id1) + .expect("Acc 1 should be restored"); + + let acc2 = ctx + .wallet() + .storage() + .user_data + .private_key_tree + .get_node(to_account_id2) + .expect("Acc 2 should be restored"); + + // Verify restored public accounts + let _acc3 = ctx + .wallet() + .storage() + .user_data + .public_key_tree + .get_node(to_account_id3) + .expect("Acc 3 should be restored"); + + let _acc4 = ctx + .wallet() + .storage() + .user_data + .public_key_tree + .get_node(to_account_id4) + .expect("Acc 4 should be restored"); + + assert_eq!( + acc1.value.1.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!( + acc2.value.1.program_owner, + Program::authenticated_transfer_program().id() + ); + + assert_eq!(acc1.value.1.balance, 100); + assert_eq!(acc2.value.1.balance, 101); + + info!("Tree checks passed, testing restored accounts can transact"); + + // Test that restored accounts can send transactions + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&to_account_id1.to_string()), + to: Some(format_private_account_id(&to_account_id2.to_string())), + to_npk: None, + to_ipk: None, + amount: 10, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&to_account_id3.to_string()), + to: Some(format_public_account_id(&to_account_id4.to_string())), + to_npk: None, + to_ipk: None, + amount: 11, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify commitments exist for private accounts + let comm1 = ctx + .wallet() + .get_private_account_commitment(&to_account_id1) + .expect("Acc 1 commitment should exist"); + let comm2 = ctx + .wallet() + .get_private_account_commitment(&to_account_id2) + .expect("Acc 2 commitment should exist"); + + assert!(verify_commitment_is_in_state(comm1, ctx.sequencer_client()).await); + assert!(verify_commitment_is_in_state(comm2, ctx.sequencer_client()).await); + + // Verify public account balances + let acc3 = ctx + .sequencer_client() + .get_account_balance(to_account_id3.to_string()) + .await?; + let acc4 = ctx + .sequencer_client() + .get_account_balance(to_account_id4.to_string()) + .await?; + + assert_eq!(acc3.balance, 91); // 102 - 11 + assert_eq!(acc4.balance, 114); // 103 + 11 + + info!("Successfully restored keys and verified transactions"); + + Ok(()) +} diff --git a/integration_tests/tests/pinata.rs b/integration_tests/tests/pinata.rs new file mode 100644 index 00000000..a3f2b4b7 --- /dev/null +++ b/integration_tests/tests/pinata.rs @@ -0,0 +1,175 @@ +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use common::PINATA_BASE58; +use integration_tests::{ + ACC_SENDER, ACC_SENDER_PRIVATE, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, + format_private_account_id, format_public_account_id, verify_commitment_is_in_state, +}; +use log::info; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::{ + native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand, + }, +}; + +#[test] +async fn claim_pinata_to_existing_public_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let pinata_prize = 150; + let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { + to: format_public_account_id(ACC_SENDER), + }); + + let pinata_balance_pre = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move"); + let pinata_balance_post = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + let winner_balance_post = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await? + .balance; + + assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); + assert_eq!(winner_balance_post, 10000 + pinata_prize); + + info!("Successfully claimed pinata to public account"); + + Ok(()) +} + +#[test] +async fn claim_pinata_to_existing_private_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let pinata_prize = 150; + let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { + to: format_private_account_id(ACC_SENDER_PRIVATE), + }); + + let pinata_balance_pre = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = result else { + anyhow::bail!("Expected PrivacyPreservingTransfer return value"); + }; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Syncing private accounts"); + let command = Command::Account(AccountSubcommand::SyncPrivate {}); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&ACC_SENDER_PRIVATE.parse()?) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let pinata_balance_post = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); + + info!("Successfully claimed pinata to existing private account"); + + Ok(()) +} + +#[test] +async fn claim_pinata_to_new_private_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let pinata_prize = 150; + + // Create new private account + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: winner_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + let winner_account_id_formatted = format_private_account_id(&winner_account_id.to_string()); + + // Initialize account under auth transfer program + let command = Command::AuthTransfer(AuthTransferSubcommand::Init { + account_id: winner_account_id_formatted.clone(), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&winner_account_id) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Claim pinata to the new private account + let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { + to: winner_account_id_formatted, + }); + + let pinata_balance_pre = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&winner_account_id) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let pinata_balance_post = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); + + info!("Successfully claimed pinata to new private account"); + + Ok(()) +} diff --git a/integration_tests/tests/program_deployment.rs b/integration_tests/tests/program_deployment.rs new file mode 100644 index 00000000..25771ec2 --- /dev/null +++ b/integration_tests/tests/program_deployment.rs @@ -0,0 +1,64 @@ +use std::{path::PathBuf, time::Duration}; + +use anyhow::Result; +use integration_tests::{ + NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, +}; +use log::info; +use nssa::{AccountId, program::Program}; +use tokio::test; +use wallet::cli::Command; + +#[test] +async fn deploy_and_execute_program() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let binary_filepath: PathBuf = PathBuf::from(manifest_dir) + .join("../artifacts/test_program_methods") + .join(NSSA_PROGRAM_FOR_TEST_DATA_CHANGER); + + let command = Command::DeployProgram { + binary_filepath: binary_filepath.clone(), + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // The program is the data changer and takes one account as input. + // We pass an uninitialized account and we expect after execution to be owned by the data + // changer program (NSSA account claiming mechanism) with data equal to [0] (due to program + // logic) + let bytecode = std::fs::read(binary_filepath)?; + let data_changer = Program::new(bytecode)?; + let account_id: AccountId = "11".repeat(16).parse()?; + let message = nssa::public_transaction::Message::try_new( + data_changer.id(), + vec![account_id], + vec![], + vec![0], + )?; + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let transaction = nssa::PublicTransaction::new(message, witness_set); + let _response = ctx.sequencer_client().send_tx_public(transaction).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let post_state_account = ctx + .sequencer_client() + .get_account(account_id.to_string()) + .await? + .account; + + assert_eq!(post_state_account.program_owner, data_changer.id()); + assert_eq!(post_state_account.balance, 0); + assert_eq!(post_state_account.data.as_ref(), &[0]); + assert_eq!(post_state_account.nonce, 0); + + info!("Successfully deployed and executed program"); + + Ok(()) +} diff --git a/integration_tests/tests/token.rs b/integration_tests/tests/token.rs new file mode 100644 index 00000000..9a8b714a --- /dev/null +++ b/integration_tests/tests/token.rs @@ -0,0 +1,968 @@ +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use integration_tests::{ + TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_private_account_id, + format_public_account_id, verify_commitment_is_in_state, +}; +use key_protocol::key_management::key_tree::chain_index::ChainIndex; +use log::info; +use nssa::program::Program; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::token::TokenProgramAgnosticSubcommand, +}; + +#[test] +async fn create_and_transfer_public_token() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create new account for the token definition + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token supply holder + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for receiving a token transaction + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id.to_string()), + supply_account_id: format_public_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the token definition account + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!(definition_acc.program_owner, Program::token().id()); + // The data of a token definition account has the following layout: + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 bytes)] + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // Check the status of the token holding account with the total supply + let supply_acc = ctx + .sequencer_client() + .get_account(supply_account_id.to_string()) + .await? + .account; + + // The account must be owned by the token program + assert_eq!(supply_acc.program_owner, Program::token().id()); + // The data of a token holding account has the following layout: + // [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16 bytes) ] + // First byte of the data equal to 1 means it's a token holding account + assert_eq!(supply_acc.data.as_ref()[0], 1); + // Bytes from 1 to 33 represent the id of the token this account is associated with + assert_eq!( + &supply_acc.data.as_ref()[1..33], + definition_account_id.to_bytes() + ); + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37); + + // Transfer 7 tokens from supply_acc to recipient_account_id + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_public_account_id(&supply_account_id.to_string()), + to: Some(format_public_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the supply account after transfer + let supply_acc = ctx + .sequencer_client() + .get_account(supply_account_id.to_string()) + .await? + .account; + assert_eq!(supply_acc.program_owner, Program::token().id()); + assert_eq!(supply_acc.data[0], 1); + assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes()); + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30); + + // Check the status of the recipient account after transfer + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id.to_string()) + .await? + .account; + assert_eq!(recipient_acc.program_owner, Program::token().id()); + assert_eq!(recipient_acc.data[0], 1); + assert_eq!(&recipient_acc.data[1..33], definition_account_id.to_bytes()); + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7); + + // Burn 3 tokens from recipient_acc + let subcommand = TokenProgramAgnosticSubcommand::Burn { + definition: format_public_account_id(&definition_account_id.to_string()), + holder: format_public_account_id(&recipient_account_id.to_string()), + amount: 3, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the token definition account after burn + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // Check the status of the recipient account after burn + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id.to_string()) + .await? + .account; + + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 4); + + // Mint 10 tokens at recipient_acc + let subcommand = TokenProgramAgnosticSubcommand::Mint { + definition: format_public_account_id(&definition_account_id.to_string()), + holder: Some(format_public_account_id(&recipient_account_id.to_string())), + holder_npk: None, + holder_ipk: None, + amount: 10, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the token definition account after mint + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // Check the status of the recipient account after mint + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(recipient_acc.data[33..].try_into()?), + 14 + ); + + info!("Successfully created and transferred public token"); + + Ok(()) +} + +#[test] +async fn create_and_transfer_token_with_private_supply() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create new account for the token definition (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token supply holder (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for receiving a token transaction (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id.to_string()), + supply_account_id: format_private_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the token definition account + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!(definition_acc.program_owner, Program::token().id()); + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await); + + // Transfer 7 tokens from supply_acc to recipient_account_id + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_private_account_id(&supply_account_id.to_string()), + to: Some(format_private_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await); + + let new_commitment2 = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await); + + // Burn 3 tokens from recipient_acc + let subcommand = TokenProgramAgnosticSubcommand::Burn { + definition: format_public_account_id(&definition_account_id.to_string()), + holder: format_private_account_id(&recipient_account_id.to_string()), + amount: 3, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the token definition account after burn + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + let new_commitment2 = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await); + + // Check the recipient account balance after burn + let recipient_acc = ctx + .wallet() + .get_account_private(&recipient_account_id) + .context("Failed to get recipient account")?; + + assert_eq!( + u128::from_le_bytes(recipient_acc.data[33..].try_into()?), + 4 // 7 - 3 + ); + + info!("Successfully created and transferred token with private supply"); + + Ok(()) +} + +#[test] +async fn create_token_with_private_definition() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: Some(ChainIndex::root()), + })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: Some(ChainIndex::root()), + })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token with private definition + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_private_account_id(&definition_account_id.to_string()), + supply_account_id: format_public_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify private definition commitment + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&definition_account_id) + .context("Failed to get definition commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Verify supply account + let supply_acc = ctx + .sequencer_client() + .get_account(supply_account_id.to_string()) + .await? + .account; + + assert_eq!(supply_acc.program_owner, Program::token().id()); + assert_eq!(supply_acc.data.as_ref()[0], 1); + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37); + + // Create private recipient account + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_private, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create public recipient account + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_public, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Mint to public account + let subcommand = TokenProgramAgnosticSubcommand::Mint { + definition: format_private_account_id(&definition_account_id.to_string()), + holder: Some(format_public_account_id( + &recipient_account_id_public.to_string(), + )), + holder_npk: None, + holder_ipk: None, + amount: 10, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify definition account has updated supply + let definition_acc = ctx + .wallet() + .get_account_private(&definition_account_id) + .context("Failed to get definition account")?; + + assert_eq!( + u128::from_le_bytes(definition_acc.data[7..23].try_into()?), + 47 // 37 + 10 + ); + + // Verify public recipient received tokens + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_public.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(recipient_acc.data[33..].try_into()?), + 10 + ); + + // Mint to private account + let subcommand = TokenProgramAgnosticSubcommand::Mint { + definition: format_private_account_id(&definition_account_id.to_string()), + holder: Some(format_private_account_id( + &recipient_account_id_private.to_string(), + )), + holder_npk: None, + holder_ipk: None, + amount: 5, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify private recipient commitment + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id_private) + .context("Failed to get recipient commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Verify private recipient balance + let recipient_acc_private = ctx + .wallet() + .get_account_private(&recipient_account_id_private) + .context("Failed to get private recipient account")?; + + assert_eq!( + u128::from_le_bytes(recipient_acc_private.data[33..].try_into()?), + 5 + ); + + info!("Successfully created token with private definition and minted to both account types"); + + Ok(()) +} + +#[test] +async fn create_token_with_private_definition_and_supply() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token with both private definition and supply + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_private_account_id(&definition_account_id.to_string()), + supply_account_id: format_private_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify definition commitment + let definition_commitment = ctx + .wallet() + .get_private_account_commitment(&definition_account_id) + .context("Failed to get definition commitment")?; + assert!(verify_commitment_is_in_state(definition_commitment, ctx.sequencer_client()).await); + + // Verify supply commitment + let supply_commitment = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply commitment")?; + assert!(verify_commitment_is_in_state(supply_commitment, ctx.sequencer_client()).await); + + // Verify supply balance + let supply_acc = ctx + .wallet() + .get_account_private(&supply_account_id) + .context("Failed to get supply account")?; + + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37); + + // Create recipient account + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Transfer tokens + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_private_account_id(&supply_account_id.to_string()), + to: Some(format_private_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify both commitments updated + let supply_commitment = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply commitment")?; + assert!(verify_commitment_is_in_state(supply_commitment, ctx.sequencer_client()).await); + + let recipient_commitment = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient commitment")?; + assert!(verify_commitment_is_in_state(recipient_commitment, ctx.sequencer_client()).await); + + // Verify balances + let supply_acc = ctx + .wallet() + .get_account_private(&supply_account_id) + .context("Failed to get supply account")?; + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30); + + let recipient_acc = ctx + .wallet() + .get_account_private(&recipient_account_id) + .context("Failed to get recipient account")?; + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7); + + info!("Successfully created and transferred token with both private definition and supply"); + + Ok(()) +} + +#[test] +async fn shielded_token_transfer() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create recipient account (private) for shielded transfer + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id.to_string()), + supply_account_id: format_public_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Perform shielded transfer: public supply -> private recipient + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_public_account_id(&supply_account_id.to_string()), + to: Some(format_private_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify supply account balance + let supply_acc = ctx + .sequencer_client() + .get_account(supply_account_id.to_string()) + .await? + .account; + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30); + + // Verify recipient commitment exists + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Verify recipient balance + let recipient_acc = ctx + .wallet() + .get_account_private(&recipient_account_id) + .context("Failed to get recipient account")?; + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7); + + info!("Successfully performed shielded token transfer"); + + Ok(()) +} + +#[test] +async fn deshielded_token_transfer() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create recipient account (public) for deshielded transfer + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token with private supply + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id.to_string()), + supply_account_id: format_private_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Perform deshielded transfer: private supply -> public recipient + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_private_account_id(&supply_account_id.to_string()), + to: Some(format_public_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify supply account commitment exists + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Verify supply balance + let supply_acc = ctx + .wallet() + .get_account_private(&supply_account_id) + .context("Failed to get supply account")?; + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30); + + // Verify recipient balance + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id.to_string()) + .await? + .account; + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7); + + info!("Successfully performed deshielded token transfer"); + + Ok(()) +} + +#[test] +async fn token_claiming_path_with_private_accounts() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_private_account_id(&definition_account_id.to_string()), + supply_account_id: format_private_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Create new private account for claiming path + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Get keys for foreign mint (claiming path) + let (holder_keys, _) = ctx + .wallet() + .storage() + .user_data + .get_private_account(&recipient_account_id) + .cloned() + .context("Failed to get private account keys")?; + + // Mint using claiming path (foreign account) + let subcommand = TokenProgramAgnosticSubcommand::Mint { + definition: format_private_account_id(&definition_account_id.to_string()), + holder: None, + holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)), + holder_ipk: Some(hex::encode(holder_keys.incoming_viewing_public_key.0)), + amount: 9, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Sync to claim the account + let command = Command::Account(AccountSubcommand::SyncPrivate {}); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + // Verify commitment exists + let recipient_commitment = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient commitment")?; + assert!(verify_commitment_is_in_state(recipient_commitment, ctx.sequencer_client()).await); + + // Verify balance + let recipient_acc = ctx + .wallet() + .get_account_private(&recipient_account_id) + .context("Failed to get recipient account")?; + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 9); + + info!("Successfully minted tokens using claiming path"); + + Ok(()) +} diff --git a/integration_tests/src/tps_test_utils.rs b/integration_tests/tests/tps.rs similarity index 71% rename from integration_tests/src/tps_test_utils.rs rename to integration_tests/tests/tps.rs index 154253c8..3fdc8ac8 100644 --- a/integration_tests/src/tps_test_utils.rs +++ b/integration_tests/tests/tps.rs @@ -1,6 +1,9 @@ -use std::time::Duration; +use std::time::{Duration, Instant}; +use anyhow::Result; +use integration_tests::TestContext; use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; +use log::info; use nssa::{ Account, AccountId, PrivacyPreservingTransaction, PrivateKey, PublicKey, PublicTransaction, privacy_preserving_transaction::{self as pptx, circuit}, @@ -13,6 +16,78 @@ use nssa_core::{ encryption::IncomingViewingPublicKey, }; use sequencer_core::config::{AccountInitialData, CommitmentsInitialData, SequencerConfig}; +use tokio::test; + +// TODO: Make a proper benchmark instead of an ad-hoc test +#[test] +pub async fn tps_test() -> Result<()> { + let num_transactions = 300 * 5; + let target_tps = 12; + + let tps_test = TpsTestManager::new(target_tps, num_transactions); + let ctx = TestContext::new_with_sequencer_config(tps_test.generate_sequencer_config()).await?; + + let target_time = tps_test.target_time(); + info!( + "TPS test begin. Target time is {target_time:?} for {num_transactions} transactions ({target_tps} TPS)" + ); + + let txs = tps_test.build_public_txs(); + let now = Instant::now(); + + let mut tx_hashes = vec![]; + for (i, tx) in txs.into_iter().enumerate() { + let tx_hash = ctx + .sequencer_client() + .send_tx_public(tx) + .await + .unwrap() + .tx_hash; + info!("Sent tx {i}"); + tx_hashes.push(tx_hash); + } + + for (i, tx_hash) in tx_hashes.iter().enumerate() { + loop { + if now.elapsed().as_millis() > target_time.as_millis() { + panic!("TPS test failed by timeout"); + } + + let tx_obj = ctx + .sequencer_client() + .get_transaction_by_hash(tx_hash.clone()) + .await + .inspect_err(|err| { + log::warn!( + "Failed to get transaction by hash {tx_hash:#?} with error: {err:#?}" + ) + }); + + if let Ok(tx_obj) = tx_obj + && tx_obj.transaction.is_some() + { + info!("Found tx {i} with hash {tx_hash}"); + break; + } + } + } + let time_elapsed = now.elapsed().as_secs(); + + let tx_processed = tx_hashes.len(); + let actual_tps = tx_processed as u64 / time_elapsed; + info!("Processed {tx_processed} transactions in {time_elapsed:?} ({actual_tps} TPS)",); + + assert_eq!(tx_processed, num_transactions); + + assert!( + time_elapsed <= target_time.as_secs(), + "Elapsed time {time_elapsed:?} exceeded target time {target_time:?}" + ); + + info!("TPS test finished successfully"); + + Ok(()) +} pub(crate) struct TpsTestManager { public_keypairs: Vec<(PrivateKey, AccountId)>, @@ -32,7 +107,7 @@ impl TpsTestManager { let account_id = AccountId::from(&public_key); (private_key, account_id) }) - .collect::>(); + .collect(); Self { public_keypairs, target_tps, @@ -72,7 +147,7 @@ impl TpsTestManager { /// Generates a sequencer configuration with initial balance in a number of public accounts. /// The transactions generated with the function `build_public_txs` will be valid in a node /// started with the config from this method. - pub(crate) fn generate_tps_test_config(&self) -> SequencerConfig { + pub(crate) fn generate_sequencer_config(&self) -> SequencerConfig { // Create public public keypairs let initial_public_accounts = self .public_keypairs @@ -110,6 +185,7 @@ impl TpsTestManager { initial_accounts: initial_public_accounts, initial_commitments: vec![initial_commitment], signing_key: [37; 32], + bedrock_config: None, } } } @@ -118,7 +194,7 @@ impl TpsTestManager { /// it may take a while to run. In normal execution of the node this transaction will be accepted /// only once. Disabling the node's nullifier uniqueness check allows to submit this transaction /// multiple times with the purpose of testing the node's processing performance. -#[allow(unused)] +#[expect(dead_code, reason = "No idea if we need this, should we remove it?")] fn build_privacy_transaction() -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); let sender_nsk = [1; 32]; @@ -159,16 +235,16 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { ]], ); let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![sender_pre, recipient_pre], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ (sender_npk.clone(), sender_ss), (recipient_npk.clone(), recipient_ss), ], - &[sender_nsk], - &[Some(proof)], + vec![sender_nsk], + vec![Some(proof)], &program.into(), ) .unwrap(); diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index b46c46c9..4814552a 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -165,6 +165,14 @@ impl NSSAUserData { .map(Into::into) } } + + pub fn account_ids(&self) -> impl Iterator { + self.default_pub_account_signing_keys + .keys() + .chain(self.public_key_tree.account_id_map.keys()) + .chain(self.default_user_private_accounts.keys()) + .chain(self.private_key_tree.account_id_map.keys()) + } } impl Default for NSSAUserData { diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index f1c7709b..a508cc08 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -24,8 +24,9 @@ risc0-binfmt = "3.0.2" [dev-dependencies] test_program_methods.workspace = true -hex-literal = "1.0.0" env_logger.workspace = true +hex-literal = "1.0.0" +test-case = "3.3.1" [features] default = [] diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index 51467afe..55ab0ded 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -15,9 +15,8 @@ pub type Nonce = u128; /// Account to be used both in public and private contexts #[derive( - Clone, Default, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, + Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, )] -#[cfg_attr(any(feature = "host", test), derive(Debug))] pub struct Account { pub program_owner: ProgramId, pub balance: u128, @@ -25,8 +24,7 @@ pub struct Account { pub nonce: Nonce, } -#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] -#[cfg_attr(any(feature = "host", test), derive(Debug))] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct AccountWithMetadata { pub account: Account, pub is_authorized: bool, @@ -45,6 +43,7 @@ impl AccountWithMetadata { } #[derive( + Debug, Default, Copy, Clone, @@ -56,7 +55,7 @@ impl AccountWithMetadata { BorshSerialize, BorshDeserialize, )] -#[cfg_attr(any(feature = "host", test), derive(Debug, PartialOrd, Ord))] +#[cfg_attr(any(feature = "host", test), derive(PartialOrd, Ord))] pub struct AccountId { value: [u8; 32], } diff --git a/nssa/core/src/account/data.rs b/nssa/core/src/account/data.rs index 974cb06c..396bbe6e 100644 --- a/nssa/core/src/account/data.rs +++ b/nssa/core/src/account/data.rs @@ -5,8 +5,7 @@ use serde::{Deserialize, Serialize}; pub const DATA_MAX_LENGTH_IN_BYTES: usize = 100 * 1024; // 100 KiB -#[derive(Default, Clone, PartialEq, Eq, Serialize, BorshSerialize)] -#[cfg_attr(any(feature = "host", test), derive(Debug))] +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, BorshSerialize)] pub struct Data(Vec); impl Data { diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index ec30700b..8d9d59fa 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use crate::{Commitment, account::AccountId}; -#[derive(Serialize, Deserialize, PartialEq, Eq)] -#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[cfg_attr(any(feature = "host", test), derive(Clone, Hash))] pub struct NullifierPublicKey(pub [u8; 32]); impl From<&NullifierPublicKey> for AccountId { diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 357a4a58..32b3e2c0 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -30,6 +30,20 @@ impl PdaSeed { } } +pub fn compute_authorized_pdas( + caller_program_id: Option, + pda_seeds: &[PdaSeed], +) -> HashSet { + caller_program_id + .map(|caller_program_id| { + pda_seeds + .iter() + .map(|pda_seed| AccountId::from((&caller_program_id, pda_seed))) + .collect() + }) + .unwrap_or_default() +} + impl From<(&ProgramId, &PdaSeed)> for AccountId { fn from(value: (&ProgramId, &PdaSeed)) -> Self { use risc0_zkvm::sha::{Impl, Sha256}; @@ -93,6 +107,13 @@ impl AccountPostState { } } + /// Creates a post state that requests ownership of the account + /// if the account's program owner is the default program ID. + pub fn new_claimed_if_default(account: Account) -> Self { + let claim = account.program_owner == DEFAULT_PROGRAM_ID; + Self { account, claim } + } + /// Returns `true` if this post state requests that the account /// be claimed (owned) by the executing program. pub fn requires_claim(&self) -> bool { @@ -108,6 +129,11 @@ impl AccountPostState { pub fn account_mut(&mut self) -> &mut Account { &mut self.account } + + /// Consumes the post state and returns the underlying account + pub fn into_account(self) -> Account { + self.account + } } #[derive(Serialize, Deserialize, Clone)] diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index f29eb1c5..1b490de8 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -1,11 +1,11 @@ -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, account::AccountWithMetadata, - program::{InstructionData, ProgramId, ProgramOutput}, + program::{ChainedCall, InstructionData, ProgramId, ProgramOutput}, }; use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; @@ -43,27 +43,44 @@ impl From for ProgramWithDependencies { } /// Generates a proof of the execution of a NSSA program inside the privacy preserving execution -/// circuit +/// circuit. #[expect(clippy::too_many_arguments, reason = "TODO: fix later")] pub fn execute_and_prove( - pre_states: &[AccountWithMetadata], - instruction_data: &InstructionData, - visibility_mask: &[u8], - private_account_nonces: &[u128], - private_account_keys: &[(NullifierPublicKey, SharedSecretKey)], - private_account_nsks: &[NullifierSecretKey], - private_account_membership_proofs: &[Option], + pre_states: Vec, + instruction_data: InstructionData, + visibility_mask: Vec, + private_account_nonces: Vec, + private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>, + private_account_nsks: Vec, + private_account_membership_proofs: Vec>, program_with_dependencies: &ProgramWithDependencies, ) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { - let mut program = &program_with_dependencies.program; - let dependencies = &program_with_dependencies.dependencies; - let mut instruction_data = instruction_data.clone(); - let mut pre_states = pre_states.to_vec(); + let ProgramWithDependencies { + program, + dependencies, + } = program_with_dependencies; let mut env_builder = ExecutorEnv::builder(); let mut program_outputs = Vec::new(); - for _i in 0..MAX_NUMBER_CHAINED_CALLS { - let inner_receipt = execute_and_prove_program(program, &pre_states, &instruction_data)?; + let initial_call = ChainedCall { + program_id: program.id(), + instruction_data: instruction_data.clone(), + pre_states, + pda_seeds: vec![], + }; + + let mut chained_calls = VecDeque::from_iter([(initial_call, program)]); + let mut chain_calls_counter = 0; + while let Some((chained_call, program)) = chained_calls.pop_front() { + if chain_calls_counter >= MAX_NUMBER_CHAINED_CALLS { + return Err(NssaError::MaxChainedCallsDepthExceeded); + } + + let inner_receipt = execute_and_prove_program( + program, + &chained_call.pre_states, + &chained_call.instruction_data, + )?; let program_output: ProgramOutput = inner_receipt .journal @@ -76,39 +93,23 @@ pub fn execute_and_prove( // Prove circuit. env_builder.add_assumption(inner_receipt); - // TODO: Remove when multi-chain calls are supported in the circuit - assert!(program_output.chained_calls.len() <= 1); - // TODO: Modify when multi-chain calls are supported in the circuit - if let Some(next_call) = program_output.chained_calls.first() { - program = dependencies - .get(&next_call.program_id) + for new_call in program_output.chained_calls.into_iter().rev() { + let next_program = dependencies + .get(&new_call.program_id) .ok_or(NssaError::InvalidProgramBehavior)?; - instruction_data = next_call.instruction_data.clone(); - // Build post states with metadata for next call - let mut post_states_with_metadata = Vec::new(); - for (pre, post) in program_output - .pre_states - .iter() - .zip(program_output.post_states) - { - let mut post_with_metadata = pre.clone(); - post_with_metadata.account = post.account().clone(); - post_states_with_metadata.push(post_with_metadata); - } - - pre_states = next_call.pre_states.clone(); - } else { - break; + chained_calls.push_front((new_call, next_program)); } + + chain_calls_counter += 1; } let circuit_input = PrivacyPreservingCircuitInput { program_outputs, - visibility_mask: visibility_mask.to_vec(), - private_account_nonces: private_account_nonces.to_vec(), - private_account_keys: private_account_keys.to_vec(), - private_account_nsks: private_account_nsks.to_vec(), - private_account_membership_proofs: private_account_membership_proofs.to_vec(), + visibility_mask, + private_account_nonces, + private_account_keys, + private_account_nsks, + private_account_membership_proofs, program_id: program_with_dependencies.program.id(), }; @@ -215,13 +216,13 @@ mod tests { let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( - &[sender, recipient], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[0, 2], - &[0xdeadbeef], - &[(recipient_keys.npk(), shared_secret)], - &[], - &[None], + vec![sender, recipient], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![0, 2], + vec![0xdeadbeef], + vec![(recipient_keys.npk(), shared_secret)], + vec![], + vec![None], &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -311,16 +312,16 @@ mod tests { let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( - &[sender_pre.clone(), recipient], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![sender_pre.clone(), recipient], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ (sender_keys.npk(), shared_secret_1), (recipient_keys.npk(), shared_secret_2), ], - &[sender_keys.nsk], - &[commitment_set.get_proof_for(&commitment_sender), None], + vec![sender_keys.nsk], + vec![commitment_set.get_proof_for(&commitment_sender), None], &program.into(), ) .unwrap(); diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 69cb02c5..943b16ed 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -226,6 +226,15 @@ mod tests { } } + pub fn changer_claimer() -> Self { + use test_program_methods::{CHANGER_CLAIMER_ELF, CHANGER_CLAIMER_ID}; + + Program { + id: CHANGER_CLAIMER_ID, + elf: CHANGER_CLAIMER_ELF.to_vec(), + } + } + pub fn noop() -> Self { use test_program_methods::{NOOP_ELF, NOOP_ID}; @@ -235,6 +244,17 @@ mod tests { } } + pub fn malicious_authorization_changer() -> Self { + use test_program_methods::{ + MALICIOUS_AUTHORIZATION_CHANGER_ELF, MALICIOUS_AUTHORIZATION_CHANGER_ID, + }; + + Program { + id: MALICIOUS_AUTHORIZATION_CHANGER_ID, + elf: MALICIOUS_AUTHORIZATION_CHANGER_ELF.to_vec(), + } + } + pub fn modified_transfer_program() -> Self { use test_program_methods::MODIFIED_TRANSFER_ELF; // This unwrap won't panic since the `MODIFIED_TRANSFER_ELF` comes from risc0 build of diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 68437e46..f5badb6a 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -4,7 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use log::debug; use nssa_core::{ account::{Account, AccountId, AccountWithMetadata}, - program::{ChainedCall, DEFAULT_PROGRAM_ID, PdaSeed, ProgramId, validate_execution}, + program::{ChainedCall, DEFAULT_PROGRAM_ID, validate_execution}, }; use sha2::{Digest, digest::FixedOutput}; @@ -119,7 +119,7 @@ impl PublicTransaction { return Err(NssaError::MaxChainedCallsDepthExceeded); } - // Check the `program_id` corresponds to a deployed program + // Check that the `program_id` corresponds to a deployed program let Some(program) = state.programs().get(&chained_call.program_id) else { return Err(NssaError::InvalidInput("Unknown program".into())); }; @@ -135,12 +135,14 @@ impl PublicTransaction { chained_call.program_id, program_output ); - let authorized_pdas = - self.compute_authorized_pdas(&caller_program_id, &chained_call.pda_seeds); + let authorized_pdas = nssa_core::program::compute_authorized_pdas( + caller_program_id, + &chained_call.pda_seeds, + ); for pre in &program_output.pre_states { let account_id = pre.account_id; - // Check that the program output pre_states coinicide with the values in the public + // Check that the program output pre_states coincide with the values in the public // state or with any modifications to those values during the chain of calls. let expected_pre = state_diff .get(&account_id) @@ -198,22 +200,23 @@ impl PublicTransaction { chain_calls_counter += 1; } - Ok(state_diff) - } - - fn compute_authorized_pdas( - &self, - caller_program_id: &Option, - pda_seeds: &[PdaSeed], - ) -> HashSet { - if let Some(caller_program_id) = caller_program_id { - pda_seeds - .iter() - .map(|pda_seed| AccountId::from((caller_program_id, pda_seed))) - .collect() - } else { - HashSet::new() + // Check that all modified uninitialized accounts where claimed + for post in state_diff.iter().filter_map(|(account_id, post)| { + let pre = state.get_account_by_id(account_id); + if pre.program_owner != DEFAULT_PROGRAM_ID { + return None; + } + if pre == *post { + return None; + } + Some(post) + }) { + if post.program_owner == DEFAULT_PROGRAM_ID { + return Err(NssaError::InvalidProgramBehavior); + } } + + Ok(state_diff) } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index bc0ff62e..1a384b2f 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -504,6 +504,7 @@ pub mod tests { self.insert_program(Program::chain_caller()); self.insert_program(Program::amm()); self.insert_program(Program::claimer()); + self.insert_program(Program::changer_claimer()); self } @@ -865,13 +866,13 @@ pub mod tests { let epk = EphemeralPublicKey::from_scalar(esk); let (output, proof) = circuit::execute_and_prove( - &[sender, recipient], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[0, 2], - &[0xdeadbeef], - &[(recipient_keys.npk(), shared_secret)], - &[], - &[None], + vec![sender, recipient], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![0, 2], + vec![0xdeadbeef], + vec![(recipient_keys.npk(), shared_secret)], + vec![], + vec![None], &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -912,16 +913,16 @@ pub mod tests { let epk_2 = EphemeralPublicKey::from_scalar(esk_2); let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 2], - &new_nonces, - &[ + vec![sender_pre, recipient_pre], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 2], + new_nonces.to_vec(), + vec![ (sender_keys.npk(), shared_secret_1), (recipient_keys.npk(), shared_secret_2), ], - &[sender_keys.nsk], - &[state.get_proof_for_commitment(&sender_commitment), None], + vec![sender_keys.nsk], + vec![state.get_proof_for_commitment(&sender_commitment), None], &program.into(), ) .unwrap(); @@ -965,13 +966,13 @@ pub mod tests { let epk = EphemeralPublicKey::from_scalar(esk); let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 0], - &[new_nonce], - &[(sender_keys.npk(), shared_secret)], - &[sender_keys.nsk], - &[state.get_proof_for_commitment(&sender_commitment)], + vec![sender_pre, recipient_pre], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 0], + vec![new_nonce], + vec![(sender_keys.npk(), shared_secret)], + vec![sender_keys.nsk], + vec![state.get_proof_for_commitment(&sender_commitment)], &program.into(), ) .unwrap(); @@ -1179,13 +1180,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(10u128).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(10u128).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1206,13 +1207,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(10u128).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(10u128).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1233,13 +1234,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(()).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(()).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1260,13 +1261,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(vec![0]).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(vec![0]).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1289,13 +1290,13 @@ pub mod tests { let large_data: Vec = vec![0; nssa_core::account::data::DATA_MAX_LENGTH_IN_BYTES + 1]; let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(large_data).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(large_data).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.to_owned().into(), ); @@ -1316,13 +1317,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(()).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(()).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1352,13 +1353,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account_1, public_account_2], - &Program::serialize_instruction(()).unwrap(), - &[0, 0], - &[], - &[], - &[], - &[], + vec![public_account_1, public_account_2], + Program::serialize_instruction(()).unwrap(), + vec![0, 0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1379,13 +1380,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(()).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(()).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1415,13 +1416,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account_1, public_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[0, 0], - &[], - &[], - &[], - &[], + vec![public_account_1, public_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![0, 0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1453,13 +1454,13 @@ pub mod tests { // Setting only one visibility mask for a circuit execution with two pre_state accounts. let visibility_mask = [0]; let result = execute_and_prove( - &[public_account_1, public_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &visibility_mask, - &[], - &[], - &[], - &[], + vec![public_account_1, public_account_2], + Program::serialize_instruction(10u128).unwrap(), + visibility_mask.to_vec(), + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1486,11 +1487,11 @@ pub mod tests { // Setting only one nonce for an execution with two private accounts. let private_account_nonces = [0xdeadbeef1]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &private_account_nonces, - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + private_account_nonces.to_vec(), + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1500,8 +1501,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1530,13 +1531,13 @@ pub mod tests { SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), )]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &private_account_keys, - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + private_account_keys.to_vec(), + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1563,11 +1564,11 @@ pub mod tests { // Setting no second commitment proof. let private_account_membership_proofs = [Some((0, vec![]))]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1577,8 +1578,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &private_account_membership_proofs, + vec![sender_keys.nsk], + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -1605,11 +1606,11 @@ pub mod tests { // Setting no auth key for an execution with one non default private accounts. let private_account_nsks = []; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1619,8 +1620,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &private_account_nsks, - &[], + private_account_nsks.to_vec(), + vec![], &program.into(), ); @@ -1663,13 +1664,13 @@ pub mod tests { let private_account_nsks = [recipient_keys.nsk]; let private_account_membership_proofs = [Some((0, vec![]))]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &private_account_keys, - &private_account_nsks, - &private_account_membership_proofs, + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + private_account_keys.to_vec(), + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -1701,11 +1702,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1715,8 +1716,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1749,11 +1750,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1763,8 +1764,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1796,11 +1797,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1810,8 +1811,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1843,11 +1844,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1857,8 +1858,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1888,11 +1889,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1902,8 +1903,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1927,13 +1928,13 @@ pub mod tests { let visibility_mask = [0, 3]; let result = execute_and_prove( - &[public_account_1, public_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &visibility_mask, - &[], - &[], - &[], - &[], + vec![public_account_1, public_account_2], + Program::serialize_instruction(10u128).unwrap(), + visibility_mask.to_vec(), + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1961,11 +1962,11 @@ pub mod tests { // accounts. let private_account_nonces = [0xdeadbeef1, 0xdeadbeef2, 0xdeadbeef3]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &private_account_nonces, - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + private_account_nonces.to_vec(), + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1975,8 +1976,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -2017,13 +2018,13 @@ pub mod tests { ), ]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &private_account_keys, - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + private_account_keys.to_vec(), + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -2053,11 +2054,11 @@ pub mod tests { let private_account_nsks = [sender_keys.nsk, recipient_keys.nsk]; let private_account_membership_proofs = [Some((0, vec![])), Some((1, vec![]))]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &visibility_mask, - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + visibility_mask.to_vec(), + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -2067,8 +2068,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &private_account_nsks, - &private_account_membership_proofs, + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -2149,16 +2150,16 @@ pub mod tests { let private_account_membership_proofs = [Some((1, vec![])), Some((1, vec![]))]; let shared_secret = SharedSecretKey::new(&[55; 32], &sender_keys.ivk()); let result = execute_and_prove( - &[private_account_1.clone(), private_account_1], - &Program::serialize_instruction(100u128).unwrap(), - &visibility_mask, - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1.clone(), private_account_1], + Program::serialize_instruction(100u128).unwrap(), + visibility_mask.to_vec(), + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ (sender_keys.npk(), shared_secret), (sender_keys.npk(), shared_secret), ], - &private_account_nsks, - &private_account_membership_proofs, + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -3941,8 +3942,9 @@ pub mod tests { assert_eq!(to_post, expected_to_post); } - #[test] - fn test_private_chained_call() { + #[test_case::test_case(1; "single call")] + #[test_case::test_case(2; "two calls")] + fn test_private_chained_call(number_of_calls: u32) { // Arrange let chain_caller = Program::chain_caller(); let auth_transfers = Program::authenticated_transfer_program(); @@ -3978,7 +3980,7 @@ pub mod tests { let instruction: (u128, ProgramId, u32, Option) = ( amount, Program::authenticated_transfer_program().id(), - 1, + number_of_calls, None, ); @@ -3999,14 +4001,14 @@ pub mod tests { let to_new_nonce = 0xdeadbeef2; let from_expected_post = Account { - balance: initial_balance - amount, + balance: initial_balance - number_of_calls as u128 * amount, nonce: from_new_nonce, ..from_account.account.clone() }; let from_expected_commitment = Commitment::new(&from_keys.npk(), &from_expected_post); let to_expected_post = Account { - balance: amount, + balance: number_of_calls as u128 * amount, nonce: to_new_nonce, ..to_account.account.clone() }; @@ -4014,13 +4016,13 @@ pub mod tests { // Act let (output, proof) = execute_and_prove( - &[to_account, from_account], - &Program::serialize_instruction(instruction).unwrap(), - &[1, 1], - &[from_new_nonce, to_new_nonce], - &[(from_keys.npk(), to_ss), (to_keys.npk(), from_ss)], - &[from_keys.nsk, to_keys.nsk], - &[ + vec![to_account, from_account], + Program::serialize_instruction(instruction).unwrap(), + vec![1, 1], + vec![from_new_nonce, to_new_nonce], + vec![(from_keys.npk(), to_ss), (to_keys.npk(), from_ss)], + vec![from_keys.nsk, to_keys.nsk], + vec![ state.get_proof_for_commitment(&from_commitment), state.get_proof_for_commitment(&to_commitment), ], @@ -4255,13 +4257,13 @@ pub mod tests { // Execute and prove the circuit with the authorized account but no commitment proof let (output, proof) = execute_and_prove( - std::slice::from_ref(&authorized_account), - &Program::serialize_instruction(balance).unwrap(), - &[1], - &[nonce], - &[(private_keys.npk(), shared_secret)], - &[private_keys.nsk], - &[None], + vec![authorized_account], + Program::serialize_instruction(balance).unwrap(), + vec![1], + vec![nonce], + vec![(private_keys.npk(), shared_secret)], + vec![private_keys.nsk], + vec![None], &program.into(), ) .unwrap(); @@ -4308,13 +4310,13 @@ pub mod tests { // Step 2: Execute claimer program to claim the account with authentication let (output, proof) = execute_and_prove( - std::slice::from_ref(&authorized_account), - &Program::serialize_instruction(balance).unwrap(), - &[1], - &[nonce], - &[(private_keys.npk(), shared_secret)], - &[private_keys.nsk], - &[None], + vec![authorized_account.clone()], + Program::serialize_instruction(balance).unwrap(), + vec![1], + vec![nonce], + vec![(private_keys.npk(), shared_secret)], + vec![private_keys.nsk], + vec![None], &claimer_program.into(), ) .unwrap(); @@ -4356,16 +4358,174 @@ pub mod tests { // Step 3: Try to execute noop program with authentication but without initialization let res = execute_and_prove( - std::slice::from_ref(&account_metadata), - &Program::serialize_instruction(()).unwrap(), - &[1], - &[nonce2], - &[(private_keys.npk(), shared_secret2)], - &[private_keys.nsk], - &[None], + vec![account_metadata], + Program::serialize_instruction(()).unwrap(), + vec![1], + vec![nonce2], + vec![(private_keys.npk(), shared_secret2)], + vec![private_keys.nsk], + vec![None], &noop_program.into(), ); assert!(matches!(res, Err(NssaError::CircuitProvingError(_)))); } + + #[test] + fn test_public_changer_claimer_no_data_change_no_claim_succeeds() { + let initial_data = []; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_id = AccountId::new([1; 32]); + let program_id = Program::changer_claimer().id(); + // Don't change data (None) and don't claim (false) + let instruction: (Option>, bool) = (None, false); + + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], instruction) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + // Should succeed - no changes made, no claim needed + assert!(result.is_ok()); + // Account should remain default/unclaimed + assert_eq!(state.get_account_by_id(&account_id), Account::default()); + } + + #[test] + fn test_public_changer_claimer_data_change_no_claim_fails() { + let initial_data = []; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_id = AccountId::new([1; 32]); + let program_id = Program::changer_claimer().id(); + // Change data but don't claim (false) - should fail + let new_data = vec![1, 2, 3, 4, 5]; + let instruction: (Option>, bool) = (Some(new_data), false); + + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], instruction) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + // Should fail - cannot modify data without claiming the account + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_private_changer_claimer_no_data_change_no_claim_succeeds() { + let program = Program::changer_claimer(); + let sender_keys = test_private_account_keys_1(); + let private_account = + AccountWithMetadata::new(Account::default(), true, &sender_keys.npk()); + // Don't change data (None) and don't claim (false) + let instruction: (Option>, bool) = (None, false); + + let result = execute_and_prove( + vec![private_account], + Program::serialize_instruction(instruction).unwrap(), + vec![1], + vec![2], + vec![( + sender_keys.npk(), + SharedSecretKey::new(&[3; 32], &sender_keys.ivk()), + )], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + // Should succeed - no changes made, no claim needed + assert!(result.is_ok()); + } + + #[test] + fn test_private_changer_claimer_data_change_no_claim_fails() { + let program = Program::changer_claimer(); + let sender_keys = test_private_account_keys_1(); + let private_account = + AccountWithMetadata::new(Account::default(), true, &sender_keys.npk()); + // Change data but don't claim (false) - should fail + let new_data = vec![1, 2, 3, 4, 5]; + let instruction: (Option>, bool) = (Some(new_data), false); + + let result = execute_and_prove( + vec![private_account], + Program::serialize_instruction(instruction).unwrap(), + vec![1], + vec![2], + vec![( + sender_keys.npk(), + SharedSecretKey::new(&[3; 32], &sender_keys.ivk()), + )], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + // Should fail - cannot modify data without claiming the account + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() { + // Arrange + let malicious_program = Program::malicious_authorization_changer(); + let auth_transfers = Program::authenticated_transfer_program(); + let sender_keys = test_public_account_keys_1(); + let recipient_keys = test_private_account_keys_1(); + + let sender_account = AccountWithMetadata::new( + Account { + program_owner: auth_transfers.id(), + balance: 100, + ..Default::default() + }, + false, + sender_keys.account_id(), + ); + let recipient_account = + AccountWithMetadata::new(Account::default(), true, &recipient_keys.npk()); + + let recipient_commitment = + Commitment::new(&recipient_keys.npk(), &recipient_account.account); + let state = V02State::new_with_genesis_accounts( + &[(sender_account.account_id, sender_account.account.balance)], + std::slice::from_ref(&recipient_commitment), + ) + .with_test_programs(); + + let balance_to_transfer = 10u128; + let instruction = (balance_to_transfer, auth_transfers.id()); + + let recipient_esk = [3; 32]; + let recipient = SharedSecretKey::new(&recipient_esk, &recipient_keys.ivk()); + + let mut dependencies = HashMap::new(); + dependencies.insert(auth_transfers.id(), auth_transfers); + let program_with_deps = ProgramWithDependencies::new(malicious_program, dependencies); + + let recipient_new_nonce = 0xdeadbeef1; + + // Act - execute the malicious program - this should fail during proving + let result = execute_and_prove( + vec![sender_account, recipient_account], + Program::serialize_instruction(instruction).unwrap(), + vec![0, 1], + vec![recipient_new_nonce], + vec![(recipient_keys.npk(), recipient)], + vec![recipient_keys.nsk], + vec![state.get_proof_for_commitment(&recipient_commitment)], + &program_with_deps, + ); + + // Assert - should fail because the malicious program tries to manipulate is_authorized + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } diff --git a/program_methods/guest/src/bin/pinata.rs b/program_methods/guest/src/bin/pinata.rs index a0c46a1a..0dc3c108 100644 --- a/program_methods/guest/src/bin/pinata.rs +++ b/program_methods/guest/src/bin/pinata.rs @@ -77,7 +77,7 @@ fn main() { instruction_words, vec![pinata, winner], vec![ - AccountPostState::new(pinata_post), + AccountPostState::new_claimed_if_default(pinata_post), AccountPostState::new(winner_post), ], ); diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/program_methods/guest/src/bin/privacy_preserving_circuit.rs index ffe4b130..4bbd895f 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -1,12 +1,18 @@ -use std::collections::HashMap; +use std::{ + collections::{HashMap, HashSet, VecDeque, hash_map::Entry}, + convert::Infallible, +}; use nssa_core::{ - Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, - NullifierPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, - account::{Account, AccountId, AccountWithMetadata}, + Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, MembershipProof, + Nullifier, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, + PrivacyPreservingCircuitOutput, SharedSecretKey, + account::{Account, AccountId, AccountWithMetadata, Nonce}, compute_digest_for_path, - encryption::Ciphertext, - program::{DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, validate_execution}, + program::{ + AccountPostState, ChainedCall, DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, ProgramId, + ProgramOutput, validate_execution, + }, }; use risc0_zkvm::{guest::env, serde::to_vec}; @@ -18,118 +24,224 @@ fn main() { private_account_keys, private_account_nsks, private_account_membership_proofs, - mut program_id, + program_id, } = env::read(); - let mut pre_states: Vec = Vec::new(); - let mut state_diff: HashMap = HashMap::new(); + let execution_state = ExecutionState::derive_from_outputs(program_id, program_outputs); - let num_calls = program_outputs.len(); - if num_calls > MAX_NUMBER_CHAINED_CALLS { - panic!("Max chained calls depth is exceeded"); - } + let output = compute_circuit_output( + execution_state, + &visibility_mask, + &private_account_nonces, + &private_account_keys, + &private_account_nsks, + &private_account_membership_proofs, + ); - let Some(last_program_call) = program_outputs.last() else { - panic!("Program outputs is empty") - }; + env::commit(&output); +} - if !last_program_call.chained_calls.is_empty() { - panic!("Call stack is incomplete"); - } +/// State of the involved accounts before and after program execution. +struct ExecutionState { + pre_states: Vec, + post_states: HashMap, +} - for window in program_outputs.windows(2) { - let caller = &window[0]; - let callee = &window[1]; - - if caller.chained_calls.len() > 1 { - panic!("Privacy Multi-chained calls are not supported yet"); - } - - // TODO: Modify when multi-chain calls are supported in the circuit - let Some(caller_chained_call) = &caller.chained_calls.first() else { - panic!("Expected chained call"); +impl ExecutionState { + /// Validate program outputs and derive the overall execution state. + pub fn derive_from_outputs(program_id: ProgramId, program_outputs: Vec) -> Self { + let Some(first_output) = program_outputs.first() else { + panic!("No program outputs provided"); }; - // Check that instruction data in caller is the instruction data in callee - if caller_chained_call.instruction_data != callee.instruction_data { - panic!("Invalid instruction data"); - } - - // Check that account pre_states in caller are the ones in calle - if caller_chained_call.pre_states != callee.pre_states { - panic!("Invalid pre states"); - } - } - - for (i, program_output) in program_outputs.iter().enumerate() { - let mut program_output = program_output.clone(); - - // Check that `program_output` is consistent with the execution of the corresponding - // program. - let program_output_words = - &to_vec(&program_output).expect("program_output must be serializable"); - env::verify(program_id, program_output_words) - .expect("program output must match the program's execution"); - - // Check that the program is well behaved. - // See the # Programs section for the definition of the `validate_execution` method. - if !validate_execution( - &program_output.pre_states, - &program_output.post_states, + let initial_call = ChainedCall { program_id, - ) { - panic!("Bad behaved program"); - } + instruction_data: first_output.instruction_data.clone(), + pre_states: first_output.pre_states.clone(), + pda_seeds: Vec::new(), + }; + let mut chained_calls = VecDeque::from_iter([(initial_call, None)]); - // The invoked program claims the accounts with default program id. - for post in program_output - .post_states - .iter_mut() - .filter(|post| post.requires_claim()) - { - // The invoked program can only claim accounts with default program id. - if post.account().program_owner == DEFAULT_PROGRAM_ID { - post.account_mut().program_owner = program_id; - } else { - panic!("Cannot claim an initialized account") + let mut execution_state = ExecutionState { + pre_states: Vec::new(), + post_states: HashMap::new(), + }; + + let mut program_outputs_iter = program_outputs.into_iter(); + let mut chain_calls_counter = 0; + + while let Some((chained_call, caller_program_id)) = chained_calls.pop_front() { + assert!( + chain_calls_counter <= MAX_NUMBER_CHAINED_CALLS, + "Max chained calls depth is exceeded" + ); + + let Some(program_output) = program_outputs_iter.next() else { + panic!("Insufficient program outputs for chained calls"); + }; + + // Check that instruction data in chained call is the instruction data in program output + assert_eq!( + chained_call.instruction_data, program_output.instruction_data, + "Mismatched instruction data between chained call and program output" + ); + + // Check that `program_output` is consistent with the execution of the corresponding + // program. + let program_output_words = + &to_vec(&program_output).expect("program_output must be serializable"); + env::verify(chained_call.program_id, program_output_words).unwrap_or_else( + |_: Infallible| unreachable!("Infallible error is never constructed"), + ); + + // Check that the program is well behaved. + // See the # Programs section for the definition of the `validate_execution` method. + let execution_valid = validate_execution( + &program_output.pre_states, + &program_output.post_states, + chained_call.program_id, + ); + assert!(execution_valid, "Bad behaved program"); + + for next_call in program_output.chained_calls.iter().rev() { + chained_calls.push_front((next_call.clone(), Some(chained_call.program_id))); } + + let authorized_pdas = nssa_core::program::compute_authorized_pdas( + caller_program_id, + &chained_call.pda_seeds, + ); + execution_state.validate_and_sync_states( + chained_call.program_id, + authorized_pdas, + program_output.pre_states, + program_output.post_states, + ); + chain_calls_counter += 1; } - for (pre, post) in program_output + assert!( + program_outputs_iter.next().is_none(), + "Inner call without a chained call found", + ); + + // Check that all modified uninitialized accounts were claimed + for (account_id, post) in execution_state .pre_states .iter() - .zip(&program_output.post_states) + .filter(|a| a.account.program_owner == DEFAULT_PROGRAM_ID) + .map(|a| { + let post = execution_state + .post_states + .get(&a.account_id) + .expect("Post state must exist for pre state"); + (a, post) + }) + .filter(|(pre_default, post)| pre_default.account != **post) + .map(|(pre, post)| (pre.account_id, post)) { - if let Some(account_pre) = state_diff.get(&pre.account_id) { - if account_pre != &pre.account { - panic!("Invalid input"); - } - } else { - pre_states.push(pre.clone()); - } - state_diff.insert(pre.account_id, post.account().clone()); + assert_ne!( + post.program_owner, DEFAULT_PROGRAM_ID, + "Account {account_id:?} was modified but not claimed" + ); } - // TODO: Modify when multi-chain calls are supported in the circuit - if let Some(next_chained_call) = &program_output.chained_calls.first() { - program_id = next_chained_call.program_id; - } else if i != program_outputs.len() - 1 { - panic!("Inner call without a chained call found") - }; + execution_state } - let n_accounts = pre_states.len(); - if visibility_mask.len() != n_accounts { - panic!("Invalid visibility mask length"); + /// Validate program pre and post states and populate the execution state. + fn validate_and_sync_states( + &mut self, + program_id: ProgramId, + authorized_pdas: HashSet, + pre_states: Vec, + post_states: Vec, + ) { + for (pre, mut post) in pre_states.into_iter().zip(post_states) { + let pre_account_id = pre.account_id; + let post_states_entry = self.post_states.entry(pre.account_id); + match &post_states_entry { + Entry::Occupied(occupied) => { + // Ensure that new pre state is the same as known post state + assert_eq!( + occupied.get(), + &pre.account, + "Inconsistent pre state for account {pre_account_id:?}", + ); + + let previous_is_authorized = self + .pre_states + .iter() + .find(|acc| acc.account_id == pre_account_id) + .map(|acc| acc.is_authorized) + .unwrap_or_else(|| { + panic!( + "Pre state must exist in execution state for account {pre_account_id:?}", + ) + }); + + let is_authorized = + previous_is_authorized || authorized_pdas.contains(&pre_account_id); + + assert_eq!( + pre.is_authorized, is_authorized, + "Inconsistent authorization for account {pre_account_id:?}", + ); + } + Entry::Vacant(_) => { + self.pre_states.push(pre); + } + } + + if post.requires_claim() { + // The invoked program can only claim accounts with default program id. + if post.account().program_owner == DEFAULT_PROGRAM_ID { + post.account_mut().program_owner = program_id; + } else { + panic!("Cannot claim an initialized account {pre_account_id:?}"); + } + } + + post_states_entry.insert_entry(post.into_account()); + } } - // These lists will be the public outputs of this circuit - // and will be populated next. - let mut public_pre_states: Vec = Vec::new(); - let mut public_post_states: Vec = Vec::new(); - let mut ciphertexts: Vec = Vec::new(); - let mut new_commitments: Vec = Vec::new(); - let mut new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)> = Vec::new(); + /// Get an iterator over pre and post states of each account involved in the execution. + pub fn into_states_iter( + mut self, + ) -> impl ExactSizeIterator { + self.pre_states.into_iter().map(move |pre| { + let post = self + .post_states + .remove(&pre.account_id) + .expect("Account from pre states should exist in state diff"); + (pre, post) + }) + } +} + +fn compute_circuit_output( + execution_state: ExecutionState, + visibility_mask: &[u8], + private_account_nonces: &[Nonce], + private_account_keys: &[(NullifierPublicKey, SharedSecretKey)], + private_account_nsks: &[NullifierSecretKey], + private_account_membership_proofs: &[Option], +) -> PrivacyPreservingCircuitOutput { + let mut output = PrivacyPreservingCircuitOutput { + public_pre_states: Vec::new(), + public_post_states: Vec::new(), + ciphertexts: Vec::new(), + new_commitments: Vec::new(), + new_nullifiers: Vec::new(), + }; + + let states_iter = execution_state.into_states_iter(); + assert_eq!( + visibility_mask.len(), + states_iter.len(), + "Invalid visibility mask length" + ); let mut private_nonces_iter = private_account_nonces.iter(); let mut private_keys_iter = private_account_keys.iter(); @@ -137,141 +249,156 @@ fn main() { let mut private_membership_proofs_iter = private_account_membership_proofs.iter(); let mut output_index = 0; - for i in 0..n_accounts { - match visibility_mask[i] { + for (visibility_mask, (pre_state, post_state)) in + visibility_mask.iter().copied().zip(states_iter) + { + match visibility_mask { 0 => { // Public account - public_pre_states.push(pre_states[i].clone()); - - let mut post = state_diff.get(&pre_states[i].account_id).unwrap().clone(); - - if post.program_owner == DEFAULT_PROGRAM_ID { - // Claim account - post.program_owner = program_id; - } - public_post_states.push(post); + output.public_pre_states.push(pre_state); + output.public_post_states.push(post_state); } 1 | 2 => { - let new_nonce = private_nonces_iter.next().expect("Missing private nonce"); - let (npk, shared_secret) = private_keys_iter.next().expect("Missing keys"); + let Some((npk, shared_secret)) = private_keys_iter.next() else { + panic!("Missing private account key"); + }; - if AccountId::from(npk) != pre_states[i].account_id { - panic!("AccountId mismatch"); - } + assert_eq!( + AccountId::from(npk), + pre_state.account_id, + "AccountId mismatch" + ); - if visibility_mask[i] == 1 { + let new_nullifier = if visibility_mask == 1 { // Private account with authentication - let nsk = private_nsks_iter.next().expect("Missing nsk"); + + let Some(nsk) = private_nsks_iter.next() else { + panic!("Missing private account nullifier secret key"); + }; // Verify the nullifier public key - let expected_npk = NullifierPublicKey::from(nsk); - if &expected_npk != npk { - panic!("Nullifier public key mismatch"); - } + assert_eq!( + npk, + &NullifierPublicKey::from(nsk), + "Nullifier public key mismatch" + ); // Check pre_state authorization - if !pre_states[i].is_authorized { - panic!("Pre-state not authorized"); - } + assert!( + pre_state.is_authorized, + "Pre-state not authorized for authenticated private account" + ); - let membership_proof_opt = private_membership_proofs_iter - .next() - .expect("Missing membership proof"); - let (nullifier, set_digest) = membership_proof_opt - .as_ref() - .map(|membership_proof| { - // Compute commitment set digest associated with provided auth path - let commitment_pre = Commitment::new(npk, &pre_states[i].account); - let set_digest = - compute_digest_for_path(&commitment_pre, membership_proof); + let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { + panic!("Missing membership proof"); + }; - // Compute update nullifier - let nullifier = Nullifier::for_account_update(&commitment_pre, nsk); - (nullifier, set_digest) - }) - .unwrap_or_else(|| { - if pre_states[i].account != Account::default() { - panic!("Found new private account with non default values."); - } - - // Compute initialization nullifier - let nullifier = Nullifier::for_account_initialization(npk); - (nullifier, DUMMY_COMMITMENT_HASH) - }); - new_nullifiers.push((nullifier, set_digest)); + compute_nullifier_and_set_digest( + membership_proof_opt.as_ref(), + &pre_state.account, + npk, + nsk, + ) } else { // Private account without authentication - if pre_states[i].account != Account::default() { - panic!("Found new private account with non default values."); - } - if pre_states[i].is_authorized { - panic!("Found new private account marked as authorized."); - } + assert_eq!( + pre_state.account, + Account::default(), + "Found new private account with non default values", + ); + + assert!( + !pre_state.is_authorized, + "Found new private account marked as authorized." + ); + + let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { + panic!("Missing membership proof"); + }; - let membership_proof_opt = private_membership_proofs_iter - .next() - .expect("Missing membership proof"); assert!( membership_proof_opt.is_none(), "Membership proof must be None for unauthorized accounts" ); + let nullifier = Nullifier::for_account_initialization(npk); - new_nullifiers.push((nullifier, DUMMY_COMMITMENT_HASH)); - } + (nullifier, DUMMY_COMMITMENT_HASH) + }; + output.new_nullifiers.push(new_nullifier); // Update post-state with new nonce - let mut post_with_updated_values = - state_diff.get(&pre_states[i].account_id).unwrap().clone(); - post_with_updated_values.nonce = *new_nonce; - - if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID { - // Claim account - post_with_updated_values.program_owner = program_id; - } + let mut post_with_updated_nonce = post_state; + let Some(new_nonce) = private_nonces_iter.next() else { + panic!("Missing private account nonce"); + }; + post_with_updated_nonce.nonce = *new_nonce; // Compute commitment - let commitment_post = Commitment::new(npk, &post_with_updated_values); + let commitment_post = Commitment::new(npk, &post_with_updated_nonce); // Encrypt and push post state let encrypted_account = EncryptionScheme::encrypt( - &post_with_updated_values, + &post_with_updated_nonce, shared_secret, &commitment_post, output_index, ); - new_commitments.push(commitment_post); - ciphertexts.push(encrypted_account); + output.new_commitments.push(commitment_post); + output.ciphertexts.push(encrypted_account); output_index += 1; } _ => panic!("Invalid visibility mask value"), } } - if private_nonces_iter.next().is_some() { - panic!("Too many nonces"); - } + assert!(private_nonces_iter.next().is_none(), "Too many nonces"); - if private_keys_iter.next().is_some() { - panic!("Too many private account keys"); - } + assert!( + private_keys_iter.next().is_none(), + "Too many private account keys" + ); - if private_nsks_iter.next().is_some() { - panic!("Too many private account authentication keys"); - } + assert!( + private_nsks_iter.next().is_none(), + "Too many private account nullifier secret keys" + ); - if private_membership_proofs_iter.next().is_some() { - panic!("Too many private account membership proofs"); - } + assert!( + private_membership_proofs_iter.next().is_none(), + "Too many private account membership proofs" + ); - let output = PrivacyPreservingCircuitOutput { - public_pre_states, - public_post_states, - ciphertexts, - new_commitments, - new_nullifiers, - }; - - env::commit(&output); + output +} + +fn compute_nullifier_and_set_digest( + membership_proof_opt: Option<&MembershipProof>, + pre_account: &Account, + npk: &NullifierPublicKey, + nsk: &NullifierSecretKey, +) -> (Nullifier, CommitmentSetDigest) { + membership_proof_opt + .as_ref() + .map(|membership_proof| { + // Compute commitment set digest associated with provided auth path + let commitment_pre = Commitment::new(npk, pre_account); + let set_digest = compute_digest_for_path(&commitment_pre, membership_proof); + + // Compute update nullifier + let nullifier = Nullifier::for_account_update(&commitment_pre, nsk); + (nullifier, set_digest) + }) + .unwrap_or_else(|| { + assert_eq!( + *pre_account, + Account::default(), + "Found new private account with non default values" + ); + + // Compute initialization nullifier + let nullifier = Nullifier::for_account_initialization(npk); + (nullifier, DUMMY_COMMITMENT_HASH) + }) } diff --git a/sequencer_core/Cargo.toml b/sequencer_core/Cargo.toml index 06164e9e..8d2886ce 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -13,9 +13,16 @@ mempool.workspace = true base58.workspace = true anyhow.workspace = true serde.workspace = true +serde_json.workspace = true tempfile.workspace = true chrono.workspace = true log.workspace = true +bedrock_client.workspace = true +logos-blockchain-key-management-system-service.workspace = true +logos-blockchain-core.workspace = true +rand.workspace = true +reqwest.workspace = true +borsh.workspace = true [features] default = [] diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs new file mode 100644 index 00000000..0aa22420 --- /dev/null +++ b/sequencer_core/src/block_settlement_client.rs @@ -0,0 +1,117 @@ +use std::{fs, path::Path}; + +use anyhow::{Context, Result, anyhow}; +use bedrock_client::BedrockClient; +use common::block::HashableBlockData; +use logos_blockchain_core::mantle::{ + MantleTx, Op, OpProof, SignedMantleTx, Transaction, TxHash, ledger, + ops::channel::{ChannelId, MsgId, inscribe::InscriptionOp}, +}; +use logos_blockchain_key_management_system_service::keys::{ + ED25519_SECRET_KEY_SIZE, Ed25519Key, Ed25519PublicKey, +}; + +use crate::config::BedrockConfig; + +/// A component that posts block data to logos blockchain +pub struct BlockSettlementClient { + bedrock_client: BedrockClient, + bedrock_signing_key: Ed25519Key, + bedrock_channel_id: ChannelId, + last_message_id: MsgId, +} + +impl BlockSettlementClient { + pub fn try_new(home: &Path, config: &BedrockConfig) -> Result { + let bedrock_signing_key = load_or_create_signing_key(&home.join("bedrock_signing_key")) + .context("Failed to load or create signing key")?; + let bedrock_channel_id = ChannelId::from(config.channel_id); + let bedrock_client = BedrockClient::new(None, config.node_url.clone()) + .context("Failed to initialize bedrock client")?; + let channel_genesis_msg = MsgId::from([0; 32]); + Ok(Self { + bedrock_client, + bedrock_signing_key, + bedrock_channel_id, + last_message_id: channel_genesis_msg, + }) + } + + /// Create and sign a transaction for inscribing data + pub fn create_inscribe_tx(&self, data: Vec) -> (SignedMantleTx, MsgId) { + let verifying_key_bytes = self.bedrock_signing_key.public_key().to_bytes(); + let verifying_key = + Ed25519PublicKey::from_bytes(&verifying_key_bytes).expect("valid ed25519 public key"); + + let inscribe_op = InscriptionOp { + channel_id: self.bedrock_channel_id, + inscription: data, + parent: self.last_message_id, + signer: verifying_key, + }; + let inscribe_op_id = inscribe_op.id(); + + let ledger_tx = ledger::Tx::new(vec![], vec![]); + + let inscribe_tx = MantleTx { + ops: vec![Op::ChannelInscribe(inscribe_op)], + ledger_tx, + // Altruistic test config + storage_gas_price: 0, + execution_gas_price: 0, + }; + + let tx_hash = inscribe_tx.hash(); + let signature_bytes = self + .bedrock_signing_key + .sign_payload(tx_hash.as_signing_bytes().as_ref()) + .to_bytes(); + let signature = + logos_blockchain_key_management_system_service::keys::Ed25519Signature::from_bytes( + &signature_bytes, + ); + + let signed_mantle_tx = SignedMantleTx { + ops_proofs: vec![OpProof::Ed25519Sig(signature)], + ledger_tx_proof: empty_ledger_signature(&tx_hash), + mantle_tx: inscribe_tx, + }; + (signed_mantle_tx, inscribe_op_id) + } + + /// Post a transaction to the node and wait for inclusion + pub async fn post_and_wait(&mut self, block_data: &HashableBlockData) -> Result { + let inscription_data = borsh::to_vec(&block_data)?; + let (tx, new_msg_id) = self.create_inscribe_tx(inscription_data); + + // Post the transaction + self.bedrock_client.post_transaction(tx).await?; + + self.last_message_id = new_msg_id; + + Ok(block_data.block_id) + } +} + +/// Load signing key from file or generate a new one if it doesn't exist +fn load_or_create_signing_key(path: &Path) -> Result { + if path.exists() { + let key_bytes = fs::read(path)?; + let key_array: [u8; ED25519_SECRET_KEY_SIZE] = key_bytes + .try_into() + .map_err(|_| anyhow!("Found key with incorrect length"))?; + Ok(Ed25519Key::from_bytes(&key_array)) + } else { + let mut key_bytes = [0u8; ED25519_SECRET_KEY_SIZE]; + rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut key_bytes); + fs::write(path, key_bytes)?; + Ok(Ed25519Key::from_bytes(&key_bytes)) + } +} + +fn empty_ledger_signature( + tx_hash: &TxHash, +) -> logos_blockchain_key_management_system_service::keys::ZkSignature { + logos_blockchain_key_management_system_service::keys::ZkKey::multi_sign(&[], tx_hash.as_ref()) + .expect("multi-sign with empty key set works") +} diff --git a/sequencer_core/src/block_store.rs b/sequencer_core/src/block_store.rs index 67535022..cd9aa194 100644 --- a/sequencer_core/src/block_store.rs +++ b/sequencer_core/src/block_store.rs @@ -46,7 +46,7 @@ impl SequencerBlockStore { } pub fn get_block_at_id(&self, id: u64) -> Result { - Ok(self.dbio.get_block(id)?.into_block(&self.signing_key)) + Ok(self.dbio.get_block(id)?) } pub fn put_block_at_id(&mut self, block: Block) -> Result<()> { @@ -113,7 +113,7 @@ mod tests { transactions: vec![], }; - let genesis_block = genesis_block_hashable_data.into_block(&signing_key); + let genesis_block = genesis_block_hashable_data.into_pending_block(&signing_key); // Start an empty node store let mut node_store = SequencerBlockStore::open_db_with_genesis(path, Some(genesis_block), signing_key) diff --git a/sequencer_core/src/config.rs b/sequencer_core/src/config.rs index 2f4ee3be..5911cc52 100644 --- a/sequencer_core/src/config.rs +++ b/sequencer_core/src/config.rs @@ -1,5 +1,11 @@ -use std::path::PathBuf; +use std::{ + fs::File, + io::BufReader, + path::{Path, PathBuf}, +}; +use anyhow::Result; +use reqwest::Url; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Clone)] @@ -42,4 +48,23 @@ pub struct SequencerConfig { pub initial_commitments: Vec, /// Sequencer own signing key pub signing_key: [u8; 32], + /// Bedrock configuration options + pub bedrock_config: Option, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct BedrockConfig { + /// Bedrock channel ID + pub channel_id: [u8; 32], + /// Bedrock Url + pub node_url: Url, +} + +impl SequencerConfig { + pub fn from_path(config_home: &Path) -> Result { + let file = File::open(config_home)?; + let reader = BufReader::new(file); + + Ok(serde_json::from_reader(reader)?) + } } diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 8e193ff6..89cafc4c 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -13,8 +13,9 @@ use log::warn; use mempool::{MemPool, MemPoolHandle}; use serde::{Deserialize, Serialize}; -use crate::block_store::SequencerBlockStore; +use crate::{block_settlement_client::BlockSettlementClient, block_store::SequencerBlockStore}; +mod block_settlement_client; pub mod block_store; pub mod config; @@ -24,6 +25,7 @@ pub struct SequencerCore { mempool: MemPool, sequencer_config: SequencerConfig, chain_height: u64, + block_settlement_client: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -51,7 +53,7 @@ impl SequencerCore { }; let signing_key = nssa::PrivateKey::try_new(config.signing_key).unwrap(); - let genesis_block = hashable_data.into_block(&signing_key); + let genesis_block = hashable_data.into_pending_block(&signing_key); // Sequencer should panic if unable to open db, // as fixing this issue may require actions non-native to program scope @@ -87,12 +89,18 @@ impl SequencerCore { state.add_pinata_program(PINATA_BASE58.parse().unwrap()); let (mempool, mempool_handle) = MemPool::new(config.mempool_max_size); + let block_settlement_client = config.bedrock_config.as_ref().map(|bedrock_config| { + BlockSettlementClient::try_new(&config.home, bedrock_config) + .expect("Block settlement client should be constructible") + }); + let mut this = Self { state, block_store, mempool, chain_height: config.genesis_id, sequencer_config: config, + block_settlement_client, }; this.sync_state_with_stored_blocks(); @@ -137,9 +145,21 @@ impl SequencerCore { Ok(tx) } + pub async fn produce_new_block_and_post_to_settlement_layer(&mut self) -> Result { + let block_data = self.produce_new_block_with_mempool_transactions()?; + + if let Some(block_settlement) = self.block_settlement_client.as_mut() { + block_settlement.post_and_wait(&block_data).await?; + log::info!("Posted block data to Bedrock"); + } + + Ok(self.chain_height) + } + /// Produces new block from transactions in mempool - pub fn produce_new_block_with_mempool_transactions(&mut self) -> Result { + pub fn produce_new_block_with_mempool_transactions(&mut self) -> Result { let now = Instant::now(); + let new_block_height = self.chain_height + 1; let mut valid_transactions = vec![]; @@ -167,8 +187,6 @@ impl SequencerCore { let curr_time = chrono::Utc::now().timestamp_millis() as u64; - let num_txs_in_block = valid_transactions.len(); - let hashable_data = HashableBlockData { block_id: new_block_height, transactions: valid_transactions, @@ -176,7 +194,9 @@ impl SequencerCore { timestamp: curr_time, }; - let block = hashable_data.into_block(self.block_store.signing_key()); + let block = hashable_data + .clone() + .into_pending_block(self.block_store.signing_key()); self.block_store.put_block_at_id(block)?; @@ -194,11 +214,10 @@ impl SequencerCore { // ``` log::info!( "Created block with {} transactions in {} seconds", - num_txs_in_block, + hashable_data.transactions.len(), now.elapsed().as_secs() ); - - Ok(self.chain_height) + Ok(hashable_data) } pub fn state(&self) -> &nssa::V02State { @@ -277,6 +296,7 @@ mod tests { initial_accounts, initial_commitments: vec![], signing_key: *sequencer_sign_key_for_testing().value(), + bedrock_config: None, } } @@ -619,9 +639,9 @@ mod tests { let tx = common::test_utils::produce_dummy_empty_transaction(); mempool_handle.push(tx).await.unwrap(); - let block_id = sequencer.produce_new_block_with_mempool_transactions(); - assert!(block_id.is_ok()); - assert_eq!(block_id.unwrap(), genesis_height + 1); + let block = sequencer.produce_new_block_with_mempool_transactions(); + assert!(block.is_ok()); + assert_eq!(block.unwrap().block_id, genesis_height + 1); } #[tokio::test] @@ -658,7 +678,8 @@ mod tests { // Create block let current_height = sequencer .produce_new_block_with_mempool_transactions() - .unwrap(); + .unwrap() + .block_id; let block = sequencer .block_store .get_block_at_id(current_height) @@ -697,7 +718,8 @@ mod tests { mempool_handle.push(tx.clone()).await.unwrap(); let current_height = sequencer .produce_new_block_with_mempool_transactions() - .unwrap(); + .unwrap() + .block_id; let block = sequencer .block_store .get_block_at_id(current_height) @@ -708,7 +730,8 @@ mod tests { mempool_handle.push(tx.clone()).await.unwrap(); let current_height = sequencer .produce_new_block_with_mempool_transactions() - .unwrap(); + .unwrap() + .block_id; let block = sequencer .block_store .get_block_at_id(current_height) @@ -743,7 +766,8 @@ mod tests { mempool_handle.push(tx.clone()).await.unwrap(); let current_height = sequencer .produce_new_block_with_mempool_transactions() - .unwrap(); + .unwrap() + .block_id; let block = sequencer .block_store .get_block_at_id(current_height) diff --git a/sequencer_rpc/src/net_utils.rs b/sequencer_rpc/src/net_utils.rs index 33eacae7..8b9b7e64 100644 --- a/sequencer_rpc/src/net_utils.rs +++ b/sequencer_rpc/src/net_utils.rs @@ -1,4 +1,4 @@ -use std::{io, sync::Arc}; +use std::{io, net::SocketAddr, sync::Arc}; use actix_cors::Cors; use actix_web::{App, Error as HttpError, HttpResponse, HttpServer, http, middleware, web}; @@ -42,25 +42,24 @@ fn get_cors(cors_allowed_origins: &[String]) -> Cors { .max_age(3600) } -#[allow(clippy::too_many_arguments)] pub fn new_http_server( config: RpcConfig, seuquencer_core: Arc>, mempool_handle: MemPoolHandle, -) -> io::Result { +) -> io::Result<(actix_web::dev::Server, SocketAddr)> { let RpcConfig { addr, cors_allowed_origins, limits_config, } = config; - info!(target:NETWORK, "Starting http server at {addr}"); + info!(target:NETWORK, "Starting HTTP server at {addr}"); let handler = web::Data::new(JsonHandler { sequencer_state: seuquencer_core.clone(), mempool_handle, }); // HTTP server - Ok(HttpServer::new(move || { + let http_server = HttpServer::new(move || { App::new() .wrap(get_cors(&cors_allowed_origins)) .app_data(handler.clone()) @@ -70,6 +69,14 @@ pub fn new_http_server( }) .bind(addr)? .shutdown_timeout(SHUTDOWN_TIMEOUT_SECS) - .disable_signals() - .run()) + .disable_signals(); + + let [addr] = http_server + .addrs() + .try_into() + .expect("Exactly one address bound is expected for sequencer HTTP server"); + + info!(target:NETWORK, "HTTP server started at {addr}"); + + Ok((http_server.run(), addr)) } diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index 387abf28..b89993f9 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -388,6 +388,7 @@ mod tests { initial_accounts, initial_commitments: vec![], signing_key: *sequencer_sign_key_for_testing().value(), + bedrock_config: None, } } diff --git a/sequencer_runner/Cargo.toml b/sequencer_runner/Cargo.toml index 72001451..55f56dec 100644 --- a/sequencer_runner/Cargo.toml +++ b/sequencer_runner/Cargo.toml @@ -10,7 +10,6 @@ sequencer_rpc.workspace = true clap = { workspace = true, features = ["derive", "env"] } anyhow.workspace = true -serde_json.workspace = true env_logger.workspace = true log.workspace = true actix.workspace = true diff --git a/sequencer_runner/Dockerfile b/sequencer_runner/Dockerfile index 3b2153c3..84df3f3c 100644 --- a/sequencer_runner/Dockerfile +++ b/sequencer_runner/Dockerfile @@ -7,6 +7,7 @@ RUN apt-get update && apt-get install -y \ libssl-dev \ libclang-dev \ clang \ + curl \ && rm -rf /var/lib/apt/lists/* WORKDIR /sequencer_runner @@ -31,6 +32,14 @@ RUN cargo build --release --bin sequencer_runner # Strip debug symbols to reduce binary size RUN strip /sequencer_runner/target/release/sequencer_runner +# Install r0vm +RUN curl -L https://risczero.com/install | bash +ENV PATH="/root/.cargo/bin:/root/.risc0/bin:${PATH}" +RUN rzup install +RUN cp "$(which r0vm)" /usr/local/bin/r0vm +RUN test -x /usr/local/bin/r0vm +RUN r0vm --version + # Runtime stage - minimal image FROM debian:trixie-slim @@ -47,6 +56,9 @@ RUN useradd -m -u 1000 -s /bin/bash sequencer_user && \ # Copy binary from builder COPY --from=builder --chown=sequencer_user:sequencer_user /sequencer_runner/target/release/sequencer_runner /usr/local/bin/sequencer_runner +# Copy r0vm binary from builder +COPY --from=builder --chown=sequencer_user:sequencer_user /usr/local/bin/r0vm /usr/local/bin/r0vm + # Copy entrypoint script COPY sequencer_runner/docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh @@ -71,6 +83,9 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ # Run the application ENV RUST_LOG=info +# Set explicit location for r0vm binary +ENV RISC0_SERVER_PATH=/usr/local/bin/r0vm + USER root ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/sequencer_runner/configs/debug/sequencer_config.json b/sequencer_runner/configs/debug/sequencer_config.json index 58348f68..ad43ba65 100644 --- a/sequencer_runner/configs/debug/sequencer_config.json +++ b/sequencer_runner/configs/debug/sequencer_config.json @@ -154,5 +154,9 @@ 37, 37, 37 - ] -} \ No newline at end of file + ], + "bedrock_config": { + "channel_id": [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], + "node_url": "http://localhost:8080" + } +} diff --git a/sequencer_runner/src/config.rs b/sequencer_runner/src/config.rs deleted file mode 100644 index 58f539bc..00000000 --- a/sequencer_runner/src/config.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::{fs::File, io::BufReader, path::PathBuf}; - -use anyhow::Result; -use sequencer_core::config::SequencerConfig; - -pub fn from_file(config_home: PathBuf) -> Result { - let file = File::open(config_home)?; - let reader = BufReader::new(file); - - Ok(serde_json::from_reader(reader)?) -} diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index e9f4a844..fd4a6c08 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, sync::Arc}; +use std::{net::SocketAddr, path::PathBuf, sync::Arc}; use actix_web::dev::ServerHandle; use anyhow::Result; @@ -9,8 +9,6 @@ use sequencer_core::{SequencerCore, config::SequencerConfig}; use sequencer_rpc::new_http_server; use tokio::{sync::Mutex, task::JoinHandle}; -pub mod config; - pub const RUST_LOG: &str = "RUST_LOG"; #[derive(Parser, Debug)] @@ -22,7 +20,7 @@ struct Args { pub async fn startup_sequencer( app_config: SequencerConfig, -) -> Result<(ServerHandle, JoinHandle>)> { +) -> Result<(ServerHandle, SocketAddr, JoinHandle>)> { let block_timeout = app_config.block_create_timeout_millis; let port = app_config.port; @@ -32,7 +30,7 @@ pub async fn startup_sequencer( let seq_core_wrapped = Arc::new(Mutex::new(sequencer_core)); - let http_server = new_http_server( + let (http_server, addr) = new_http_server( RpcConfig::with_port(port), Arc::clone(&seq_core_wrapped), mempool_handle, @@ -52,7 +50,9 @@ pub async fn startup_sequencer( let id = { let mut state = seq_core_wrapped.lock().await; - state.produce_new_block_with_mempool_transactions()? + state + .produce_new_block_and_post_to_settlement_layer() + .await? }; info!("Block with id {id} created"); @@ -61,7 +61,7 @@ pub async fn startup_sequencer( } }); - Ok((http_server_handle, main_loop_handle)) + Ok((http_server_handle, addr, main_loop_handle)) } pub async fn main_runner() -> Result<()> { @@ -70,7 +70,7 @@ pub async fn main_runner() -> Result<()> { let args = Args::parse(); let Args { home_dir } = args; - let app_config = config::from_file(home_dir.join("sequencer_config.json"))?; + let app_config = SequencerConfig::from_path(&home_dir.join("sequencer_config.json"))?; if let Some(ref rust_log) = app_config.override_rust_log { info!("RUST_LOG env var set to {rust_log:?}"); @@ -81,7 +81,7 @@ pub async fn main_runner() -> Result<()> { } // ToDo: Add restart on failures - let (_, main_loop_handle) = startup_sequencer(app_config).await?; + let (_, _, main_loop_handle) = startup_sequencer(app_config).await?; main_loop_handle.await??; diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 87b78705..883684c2 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -1,6 +1,6 @@ use std::{path::Path, sync::Arc}; -use common::block::{Block, HashableBlockData}; +use common::block::Block; use error::DbError; use rocksdb::{ BoundColumnFamily, ColumnFamilyDescriptor, DBWithThreadMode, MultiThreaded, Options, @@ -26,6 +26,8 @@ pub const DB_META_FIRST_BLOCK_IN_DB_KEY: &str = "first_block_in_db"; pub const DB_META_LAST_BLOCK_IN_DB_KEY: &str = "last_block_in_db"; /// Key base for storing metainformation which describe if first block has been set pub const DB_META_FIRST_BLOCK_SET_KEY: &str = "first_block_set"; +/// Key base for storing metainformation about the last finalized block on Bedrock +pub const DB_META_LAST_FINALIZED_BLOCK_ID: &str = "last_finalized_block_id"; /// Key base for storing snapshot which describe block id pub const DB_SNAPSHOT_BLOCK_ID_KEY: &str = "block_id"; @@ -75,6 +77,7 @@ impl RocksDBIO { dbio.put_meta_first_block_in_db(block)?; dbio.put_meta_is_first_block_set()?; dbio.put_meta_last_block_in_db(block_id)?; + dbio.put_meta_last_finalized_block_id(None)?; Ok(dbio) } else { @@ -232,6 +235,28 @@ impl RocksDBIO { Ok(()) } + pub fn put_meta_last_finalized_block_id(&self, block_id: Option) -> DbResult<()> { + let cf_meta = self.meta_column(); + self.db + .put_cf( + &cf_meta, + borsh::to_vec(&DB_META_LAST_FINALIZED_BLOCK_ID).map_err(|err| { + DbError::borsh_cast_message( + err, + Some("Failed to serialize DB_META_LAST_FINALIZED_BLOCK_ID".to_string()), + ) + })?, + borsh::to_vec(&block_id).map_err(|err| { + DbError::borsh_cast_message( + err, + Some("Failed to serialize last block id".to_string()), + ) + })?, + ) + .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; + Ok(()) + } + pub fn put_meta_is_first_block_set(&self) -> DbResult<()> { let cf_meta = self.meta_column(); self.db @@ -269,7 +294,7 @@ impl RocksDBIO { Some("Failed to serialize block id".to_string()), ) })?, - borsh::to_vec(&HashableBlockData::from(block)).map_err(|err| { + borsh::to_vec(&block).map_err(|err| { DbError::borsh_cast_message( err, Some("Failed to serialize block data".to_string()), @@ -280,7 +305,7 @@ impl RocksDBIO { Ok(()) } - pub fn get_block(&self, block_id: u64) -> DbResult { + pub fn get_block(&self, block_id: u64) -> DbResult { let cf_block = self.block_column(); let res = self .db @@ -296,14 +321,12 @@ impl RocksDBIO { .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; if let Some(data) = res { - Ok( - borsh::from_slice::(&data).map_err(|serr| { - DbError::borsh_cast_message( - serr, - Some("Failed to deserialize block data".to_string()), - ) - })?, - ) + Ok(borsh::from_slice::(&data).map_err(|serr| { + DbError::borsh_cast_message( + serr, + Some("Failed to deserialize block data".to_string()), + ) + })?) } else { Err(DbError::db_interaction_error( "Block on this id not found".to_string(), diff --git a/test_program_methods/guest/src/bin/changer_claimer.rs b/test_program_methods/guest/src/bin/changer_claimer.rs new file mode 100644 index 00000000..8d28a490 --- /dev/null +++ b/test_program_methods/guest/src/bin/changer_claimer.rs @@ -0,0 +1,38 @@ +use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; + +type Instruction = (Option>, bool); + +/// A program that optionally modifies the account data and optionally claims it. +fn main() { + let ( + ProgramInput { + pre_states, + instruction: (data_opt, should_claim), + }, + instruction_words, + ) = read_nssa_inputs::(); + + let [pre] = match pre_states.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + let account_pre = &pre.account; + let mut account_post = account_pre.clone(); + + // Update data if provided + if let Some(data) = data_opt { + account_post.data = data + .try_into() + .expect("provided data should fit into data limit"); + } + + // Claim or not based on the boolean flag + let post_state = if should_claim { + AccountPostState::new_claimed(account_post) + } else { + AccountPostState::new(account_post) + }; + + write_nssa_outputs(instruction_words, vec![pre], vec![post_state]); +} diff --git a/test_program_methods/guest/src/bin/malicious_authorization_changer.rs b/test_program_methods/guest/src/bin/malicious_authorization_changer.rs new file mode 100644 index 00000000..7dc0ac68 --- /dev/null +++ b/test_program_methods/guest/src/bin/malicious_authorization_changer.rs @@ -0,0 +1,53 @@ +use nssa_core::{ + account::AccountWithMetadata, + program::{ + AccountPostState, ChainedCall, ProgramId, ProgramInput, read_nssa_inputs, + write_nssa_outputs_with_chained_call, + }, +}; +use risc0_zkvm::serde::to_vec; + +type Instruction = (u128, ProgramId); + +/// A malicious test program that attempts to change authorization status. +/// It accepts two accounts and executes a native token transfer program via chain call, +/// but sets the `is_authorized` field of the first account to true. +fn main() { + let ( + ProgramInput { + pre_states, + instruction: (balance, transfer_program_id), + }, + instruction_words, + ) = read_nssa_inputs::(); + + let [sender, receiver] = match pre_states.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + // Maliciously set is_authorized to true for the first account + let authorised_sender = AccountWithMetadata { + is_authorized: true, + ..sender.clone() + }; + + let instruction_data = to_vec(&balance).unwrap(); + + let chained_call = ChainedCall { + program_id: transfer_program_id, + instruction_data, + pre_states: vec![authorised_sender.clone(), receiver.clone()], + pda_seeds: vec![], + }; + + write_nssa_outputs_with_chained_call( + instruction_words, + vec![sender.clone(), receiver.clone()], + vec![ + AccountPostState::new(sender.account), + AccountPostState::new(receiver.account), + ], + vec![chained_call], + ); +} diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 9e4b0785..bef25007 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -14,7 +14,7 @@ serde_json.workspace = true env_logger.workspace = true log.workspace = true serde.workspace = true -tokio.workspace = true +tokio = { workspace = true, features = ["macros"] } clap.workspace = true base64.workspace = true bytemuck.workspace = true @@ -25,6 +25,7 @@ rand.workspace = true itertools.workspace = true sha2.workspace = true futures.workspace = true +risc0-zkvm.workspace = true async-stream = "0.3.6" indicatif = { version = "0.18.3", features = ["improved_unicode"] } -risc0-zkvm.workspace = true +optfield = "0.4.0" diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 3a55c55f..21e59366 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -3,7 +3,7 @@ use base58::ToBase58; use clap::Subcommand; use itertools::Itertools as _; use key_protocol::key_management::key_tree::chain_index::ChainIndex; -use nssa::{Account, program::Program}; +use nssa::{Account, PublicKey, program::Program}; use serde::Serialize; use crate::{ @@ -20,6 +20,9 @@ pub enum AccountSubcommand { /// Flag to get raw account data #[arg(short, long)] raw: bool, + /// Display keys (pk for public accounts, npk/ipk for private accounts) + #[arg(short, long)] + keys: bool, /// Valid 32 byte base58 string with privacy prefix #[arg(short, long)] account_id: String, @@ -64,13 +67,20 @@ impl WalletSubcommand for NewSubcommand { NewSubcommand::Public { cci } => { let (account_id, chain_index) = wallet_core.create_new_account_public(cci); + let private_key = wallet_core + .storage + .user_data + .get_pub_account_signing_key(&account_id) + .unwrap(); + + let public_key = PublicKey::new_from_private_key(private_key); + println!( "Generated new account with account_id Public/{account_id} at path {chain_index}" ); + println!("With pk {}", hex::encode(public_key.value())); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::RegisterAccount { account_id }) } @@ -93,9 +103,7 @@ impl WalletSubcommand for NewSubcommand { hex::encode(key.incoming_viewing_public_key.to_bytes()) ); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::RegisterAccount { account_id }) } @@ -205,7 +213,11 @@ impl WalletSubcommand for AccountSubcommand { wallet_core: &mut WalletCore, ) -> Result { match self { - AccountSubcommand::Get { raw, account_id } => { + AccountSubcommand::Get { + raw, + keys, + account_id, + } => { let (account_id, addr_kind) = parse_addr_with_privacy_prefix(&account_id)?; let account_id = account_id.parse()?; @@ -219,9 +231,43 @@ impl WalletSubcommand for AccountSubcommand { .ok_or(anyhow::anyhow!("Private account not found in storage"))?, }; + // Helper closure to display keys for the account + let display_keys = |wallet_core: &WalletCore| -> Result<()> { + match addr_kind { + AccountPrivacyKind::Public => { + let private_key = wallet_core + .storage + .user_data + .get_pub_account_signing_key(&account_id) + .ok_or(anyhow::anyhow!("Public account not found in storage"))?; + + let public_key = PublicKey::new_from_private_key(private_key); + println!("pk {}", hex::encode(public_key.value())); + } + AccountPrivacyKind::Private => { + let (key, _) = wallet_core + .storage + .user_data + .get_private_account(&account_id) + .ok_or(anyhow::anyhow!("Private account not found in storage"))?; + + println!("npk {}", hex::encode(key.nullifer_public_key.0)); + println!( + "ipk {}", + hex::encode(key.incoming_viewing_public_key.to_bytes()) + ); + } + } + Ok(()) + }; + if account == Account::default() { println!("Account is Uninitialized"); + if keys { + display_keys(wallet_core)?; + } + return Ok(SubcommandReturnValue::Empty); } @@ -236,6 +282,10 @@ impl WalletSubcommand for AccountSubcommand { println!("{description}"); println!("{json_view}"); + if keys { + display_keys(wallet_core)?; + } + Ok(SubcommandReturnValue::Empty) } AccountSubcommand::New(new_subcommand) => { @@ -257,9 +307,7 @@ impl WalletSubcommand for AccountSubcommand { { wallet_core.last_synced_block = curr_last_block; - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent data at {path:#?}"); + wallet_core.store_persistent_data().await?; } else { wallet_core.sync_to_block(curr_last_block).await?; } @@ -294,7 +342,7 @@ impl WalletSubcommand for AccountSubcommand { .iter() .map(|(id, chain_index)| format!("{chain_index} Private/{id}")), ) - .format(",\n"); + .format("\n"); println!("{accounts}"); return Ok(SubcommandReturnValue::Empty); diff --git a/wallet/src/cli/chain.rs b/wallet/src/cli/chain.rs index 419fa5e2..17ecd020 100644 --- a/wallet/src/cli/chain.rs +++ b/wallet/src/cli/chain.rs @@ -19,7 +19,7 @@ pub enum ChainSubcommand { /// Get transaction at hash from sequencer Transaction { /// hash - valid 32 byte hex string - #[arg(short, long)] + #[arg(short = 't', long)] hash: String, }, } diff --git a/wallet/src/cli/config.rs b/wallet/src/cli/config.rs index bfebf9bf..b2d4aa93 100644 --- a/wallet/src/cli/config.rs +++ b/wallet/src/cli/config.rs @@ -10,7 +10,13 @@ use crate::{ #[derive(Subcommand, Debug, Clone)] pub enum ConfigSubcommand { /// Getter of config fields - Get { key: String }, + Get { + /// Print all config fields + #[arg(short, long)] + all: bool, + /// Config field key to get + key: Option, + }, /// Setter of config fields Set { key: String, value: String }, /// Prints description of corresponding field @@ -23,58 +29,66 @@ impl WalletSubcommand for ConfigSubcommand { wallet_core: &mut WalletCore, ) -> Result { match self { - ConfigSubcommand::Get { key } => match key.as_str() { - "all" => { + ConfigSubcommand::Get { all, key } => { + if all { let config_str = serde_json::to_string_pretty(&wallet_core.storage.wallet_config)?; println!("{config_str}"); - } - "override_rust_log" => { - if let Some(value) = &wallet_core.storage.wallet_config.override_rust_log { - println!("{value}"); - } else { - println!("Not set"); + } else if let Some(key) = key { + match key.as_str() { + "override_rust_log" => { + if let Some(value) = + &wallet_core.storage.wallet_config.override_rust_log + { + println!("{value}"); + } else { + println!("Not set"); + } + } + "sequencer_addr" => { + println!("{}", wallet_core.storage.wallet_config.sequencer_addr); + } + "seq_poll_timeout_millis" => { + println!( + "{}", + wallet_core.storage.wallet_config.seq_poll_timeout_millis + ); + } + "seq_tx_poll_max_blocks" => { + println!( + "{}", + wallet_core.storage.wallet_config.seq_tx_poll_max_blocks + ); + } + "seq_poll_max_retries" => { + println!("{}", wallet_core.storage.wallet_config.seq_poll_max_retries); + } + "seq_block_poll_max_amount" => { + println!( + "{}", + wallet_core.storage.wallet_config.seq_block_poll_max_amount + ); + } + "initial_accounts" => { + println!("{:#?}", wallet_core.storage.wallet_config.initial_accounts); + } + "basic_auth" => { + if let Some(basic_auth) = &wallet_core.storage.wallet_config.basic_auth + { + println!("{basic_auth}"); + } else { + println!("Not set"); + } + } + _ => { + println!("Unknown field"); + } } + } else { + println!("Please provide a key or use --all flag"); } - "sequencer_addr" => { - println!("{}", wallet_core.storage.wallet_config.sequencer_addr); - } - "seq_poll_timeout_millis" => { - println!( - "{}", - wallet_core.storage.wallet_config.seq_poll_timeout_millis - ); - } - "seq_tx_poll_max_blocks" => { - println!( - "{}", - wallet_core.storage.wallet_config.seq_tx_poll_max_blocks - ); - } - "seq_poll_max_retries" => { - println!("{}", wallet_core.storage.wallet_config.seq_poll_max_retries); - } - "seq_block_poll_max_amount" => { - println!( - "{}", - wallet_core.storage.wallet_config.seq_block_poll_max_amount - ); - } - "initial_accounts" => { - println!("{:#?}", wallet_core.storage.wallet_config.initial_accounts); - } - "basic_auth" => { - if let Some(basic_auth) = &wallet_core.storage.wallet_config.basic_auth { - println!("{basic_auth}"); - } else { - println!("Not set"); - } - } - _ => { - println!("Unknown field"); - } - }, + } ConfigSubcommand::Set { key, value } => { match key.as_str() { "override_rust_log" => { @@ -108,9 +122,7 @@ impl WalletSubcommand for ConfigSubcommand { } } - let path = wallet_core.store_config_changes().await?; - - println!("Stored changed config at {path:#?}"); + wallet_core.store_config_changes().await? } ConfigSubcommand::Description { key } => match key.as_str() { "override_rust_log" => { diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index cf3b2f1c..c742ecbd 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -15,7 +15,6 @@ use crate::{ pinata::PinataProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand, }, }, - helperfunctions::{fetch_config, fetch_persistent_storage, merge_auth_config}, }; pub mod account; @@ -97,43 +96,22 @@ pub enum SubcommandReturnValue { SyncedToBlock(u64), } -pub async fn execute_subcommand(command: Command) -> Result { - execute_subcommand_with_auth(command, None).await -} - -pub async fn execute_subcommand_with_auth( +pub async fn execute_subcommand( + wallet_core: &mut WalletCore, command: Command, - auth: Option, ) -> Result { - if fetch_persistent_storage().await.is_err() { - println!("Persistent storage not found, need to execute setup"); - - let password = read_password_from_stdin()?; - execute_setup_with_auth(password, auth.clone()).await?; - } - - let wallet_config = fetch_config().await?; - let wallet_config = merge_auth_config(wallet_config, auth.clone())?; - let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?; - let subcommand_ret = match command { Command::AuthTransfer(transfer_subcommand) => { - transfer_subcommand - .handle_subcommand(&mut wallet_core) - .await? + transfer_subcommand.handle_subcommand(wallet_core).await? } Command::ChainInfo(chain_subcommand) => { - chain_subcommand.handle_subcommand(&mut wallet_core).await? + chain_subcommand.handle_subcommand(wallet_core).await? } Command::Account(account_subcommand) => { - account_subcommand - .handle_subcommand(&mut wallet_core) - .await? + account_subcommand.handle_subcommand(wallet_core).await? } Command::Pinata(pinata_subcommand) => { - pinata_subcommand - .handle_subcommand(&mut wallet_core) - .await? + pinata_subcommand.handle_subcommand(wallet_core).await? } Command::CheckHealth {} => { let remote_program_ids = wallet_core @@ -165,18 +143,15 @@ pub async fn execute_subcommand_with_auth( SubcommandReturnValue::Empty } - Command::Token(token_subcommand) => { - token_subcommand.handle_subcommand(&mut wallet_core).await? - } - Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(&mut wallet_core).await?, + Command::Token(token_subcommand) => token_subcommand.handle_subcommand(wallet_core).await?, + Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(wallet_core).await?, Command::Config(config_subcommand) => { - config_subcommand - .handle_subcommand(&mut wallet_core) - .await? + config_subcommand.handle_subcommand(wallet_core).await? } Command::RestoreKeys { depth } => { let password = read_password_from_stdin()?; - execute_keys_restoration_with_auth(password, depth, auth).await?; + wallet_core.reset_storage(password)?; + execute_keys_restoration(wallet_core, depth).await?; SubcommandReturnValue::Empty } @@ -200,14 +175,7 @@ pub async fn execute_subcommand_with_auth( Ok(subcommand_ret) } -pub async fn execute_continuous_run() -> Result<()> { - execute_continuous_run_with_auth(None).await -} -pub async fn execute_continuous_run_with_auth(auth: Option) -> Result<()> { - let config = fetch_config().await?; - let config = merge_auth_config(config, auth)?; - let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?; - +pub async fn execute_continuous_run(wallet_core: &mut WalletCore) -> Result<()> { loop { let latest_block_num = wallet_core .sequencer_client @@ -217,7 +185,7 @@ pub async fn execute_continuous_run_with_auth(auth: Option) -> Result<() wallet_core.sync_to_block(latest_block_num).await?; tokio::time::sleep(std::time::Duration::from_millis( - config.seq_poll_timeout_millis, + wallet_core.config().seq_poll_timeout_millis, )) .await; } @@ -233,34 +201,7 @@ pub fn read_password_from_stdin() -> Result { Ok(password.trim().to_string()) } -pub async fn execute_setup(password: String) -> Result<()> { - execute_setup_with_auth(password, None).await -} - -pub async fn execute_setup_with_auth(password: String, auth: Option) -> Result<()> { - let config = fetch_config().await?; - let config = merge_auth_config(config, auth)?; - let wallet_core = WalletCore::start_from_config_new_storage(config.clone(), password).await?; - - wallet_core.store_persistent_data().await?; - - Ok(()) -} - -pub async fn execute_keys_restoration(password: String, depth: u32) -> Result<()> { - execute_keys_restoration_with_auth(password, depth, None).await -} - -pub async fn execute_keys_restoration_with_auth( - password: String, - depth: u32, - auth: Option, -) -> Result<()> { - let config = fetch_config().await?; - let config = merge_auth_config(config, auth)?; - let mut wallet_core = - WalletCore::start_from_config_new_storage(config.clone(), password.clone()).await?; - +pub async fn execute_keys_restoration(wallet_core: &mut WalletCore, depth: u32) -> Result<()> { wallet_core .storage .user_data diff --git a/wallet/src/cli/programs/native_token_transfer.rs b/wallet/src/cli/programs/native_token_transfer.rs index 7868a7cd..0cfc0fb6 100644 --- a/wallet/src/cli/programs/native_token_transfer.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -69,9 +69,7 @@ impl WalletSubcommand for AuthTransferSubcommand { println!("Transaction data is {transfer_tx:?}"); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; } AccountPrivacyKind::Private => { let account_id = account_id.parse()?; @@ -96,9 +94,7 @@ impl WalletSubcommand for AuthTransferSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; } } @@ -337,9 +333,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -381,9 +375,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -421,9 +413,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -454,9 +444,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { let tx_hash = res.tx_hash; - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -500,9 +488,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -520,9 +506,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { println!("Transaction data is {transfer_tx:?}"); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::Empty) } diff --git a/wallet/src/cli/programs/pinata.rs b/wallet/src/cli/programs/pinata.rs index e0b65709..192d3f47 100644 --- a/wallet/src/cli/programs/pinata.rs +++ b/wallet/src/cli/programs/pinata.rs @@ -168,9 +168,7 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } diff --git a/wallet/src/cli/programs/token.rs b/wallet/src/cli/programs/token.rs index 25d61ff3..b5ea1b34 100644 --- a/wallet/src/cli/programs/token.rs +++ b/wallet/src/cli/programs/token.rs @@ -740,9 +740,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -790,9 +788,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -831,9 +827,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -872,9 +866,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -923,9 +915,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -971,9 +961,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1009,9 +997,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1047,9 +1033,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1102,9 +1086,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { println!("Transaction data is {:?}", tx.message); } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1140,9 +1122,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1178,9 +1158,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1216,9 +1194,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1262,9 +1238,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { println!("Transaction data is {:?}", tx.message); } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1323,9 +1297,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1371,9 +1343,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1419,9 +1389,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } diff --git a/wallet/src/config.rs b/wallet/src/config.rs index c06ccc49..45407b6d 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -1,11 +1,17 @@ -use std::str::FromStr; +use std::{ + io::{BufReader, Write as _}, + path::Path, + str::FromStr, +}; +use anyhow::{Context as _, Result}; use key_protocol::key_management::{ KeyChain, key_tree::{ chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, }, }; +use log::warn; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -105,6 +111,25 @@ pub struct PersistentStorage { pub last_synced_block: u64, } +impl PersistentStorage { + pub fn from_path(path: &Path) -> Result { + match std::fs::File::open(path) { + Ok(file) => { + let storage_content = BufReader::new(file); + Ok(serde_json::from_reader(storage_content)?) + } + Err(err) => match err.kind() { + std::io::ErrorKind::NotFound => { + anyhow::bail!("Not found, please setup roots from config command beforehand"); + } + _ => { + anyhow::bail!("IO error {err:#?}"); + } + }, + } + } +} + impl InitialAccountData { pub fn account_id(&self) -> nssa::AccountId { match &self { @@ -172,9 +197,11 @@ pub struct GasConfig { pub gas_limit_runtime: u64, } +#[optfield::optfield(pub WalletConfigOverrides, rewrap, attrs = (derive(Debug, Default)))] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WalletConfig { /// Override rust log (env var logging level) + #[serde(skip_serializing_if = "Option::is_none")] pub override_rust_log: Option, /// Sequencer URL pub sequencer_addr: String, @@ -189,6 +216,7 @@ pub struct WalletConfig { /// Initial accounts for wallet pub initial_accounts: Vec, /// Basic authentication credentials + #[serde(skip_serializing_if = "Option::is_none")] pub basic_auth: Option, } @@ -748,3 +776,98 @@ impl Default for WalletConfig { } } } + +impl WalletConfig { + pub fn from_path_or_initialize_default(config_path: &Path) -> Result { + match std::fs::File::open(config_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + Ok(serde_json::from_reader(reader)?) + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + println!("Config not found, setting up default config"); + + let config_home = config_path.parent().ok_or_else(|| { + anyhow::anyhow!( + "Could not get parent directory of config file at {config_path:#?}" + ) + })?; + std::fs::create_dir_all(config_home)?; + + println!("Created configs dir at path {config_home:#?}"); + + let mut file = std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(config_path)?; + + let config = WalletConfig::default(); + let default_config_serialized = serde_json::to_vec_pretty(&config).unwrap(); + + file.write_all(&default_config_serialized)?; + + println!("Configs set up"); + Ok(config) + } + Err(err) => Err(err).context("IO error"), + } + } + + pub fn apply_overrides(&mut self, overrides: WalletConfigOverrides) { + let WalletConfig { + override_rust_log, + sequencer_addr, + seq_poll_timeout_millis, + seq_tx_poll_max_blocks, + seq_poll_max_retries, + seq_block_poll_max_amount, + initial_accounts, + basic_auth, + } = self; + + let WalletConfigOverrides { + override_rust_log: o_override_rust_log, + sequencer_addr: o_sequencer_addr, + seq_poll_timeout_millis: o_seq_poll_timeout_millis, + seq_tx_poll_max_blocks: o_seq_tx_poll_max_blocks, + seq_poll_max_retries: o_seq_poll_max_retries, + seq_block_poll_max_amount: o_seq_block_poll_max_amount, + initial_accounts: o_initial_accounts, + basic_auth: o_basic_auth, + } = overrides; + + if let Some(v) = o_override_rust_log { + warn!("Overriding wallet config 'override_rust_log' to {v:#?}"); + *override_rust_log = v; + } + if let Some(v) = o_sequencer_addr { + warn!("Overriding wallet config 'sequencer_addr' to {v}"); + *sequencer_addr = v; + } + if let Some(v) = o_seq_poll_timeout_millis { + warn!("Overriding wallet config 'seq_poll_timeout_millis' to {v}"); + *seq_poll_timeout_millis = v; + } + if let Some(v) = o_seq_tx_poll_max_blocks { + warn!("Overriding wallet config 'seq_tx_poll_max_blocks' to {v}"); + *seq_tx_poll_max_blocks = v; + } + if let Some(v) = o_seq_poll_max_retries { + warn!("Overriding wallet config 'seq_poll_max_retries' to {v}"); + *seq_poll_max_retries = v; + } + if let Some(v) = o_seq_block_poll_max_amount { + warn!("Overriding wallet config 'seq_block_poll_max_amount' to {v}"); + *seq_block_poll_max_amount = v; + } + if let Some(v) = o_initial_accounts { + warn!("Overriding wallet config 'initial_accounts' to {v:#?}"); + *initial_accounts = v; + } + if let Some(v) = o_basic_auth { + warn!("Overriding wallet config 'basic_auth' to {v:#?}"); + *basic_auth = v; + } + } +} diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 5f1dcf77..23bf4bb8 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -7,23 +7,22 @@ use nssa::Account; use nssa_core::account::Nonce; use rand::{RngCore, rngs::OsRng}; use serde::Serialize; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::{ HOME_DIR_ENV_VAR, config::{ - BasicAuth, InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, - PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig, + InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, + PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, }, }; /// Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed. -pub fn get_home_nssa_var() -> Result { +fn get_home_nssa_var() -> Result { Ok(PathBuf::from_str(&std::env::var(HOME_DIR_ENV_VAR)?)?) } /// Get home dir for wallet. Env var `HOME` must be set before execution to succeed. -pub fn get_home_default_path() -> Result { +fn get_home_default_path() -> Result { std::env::home_dir() .map(|path| path.join(".nssa").join("wallet")) .ok_or(anyhow::anyhow!("Failed to get HOME")) @@ -38,96 +37,20 @@ pub fn get_home() -> Result { } } -/// Fetch config from default home -pub async fn fetch_config() -> Result { - let config_home = get_home()?; - let mut config_needs_setup = false; - - let config = match tokio::fs::OpenOptions::new() - .read(true) - .open(config_home.join("wallet_config.json")) - .await - { - Ok(mut file) => { - let mut config_contents = vec![]; - file.read_to_end(&mut config_contents).await?; - - serde_json::from_slice(&config_contents)? - } - Err(err) => match err.kind() { - std::io::ErrorKind::NotFound => { - config_needs_setup = true; - - println!("Config not found, setting up default config"); - - WalletConfig::default() - } - _ => anyhow::bail!("IO error {err:#?}"), - }, - }; - - if config_needs_setup { - tokio::fs::create_dir_all(&config_home).await?; - - println!("Created configs dir at path {config_home:#?}"); - - let mut file = tokio::fs::OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(config_home.join("wallet_config.json")) - .await?; - - let default_config_serialized = - serde_json::to_vec_pretty(&WalletConfig::default()).unwrap(); - - file.write_all(&default_config_serialized).await?; - - println!("Configs setted up"); - } - - Ok(config) +/// Fetch config path from default home +pub fn fetch_config_path() -> Result { + let home = get_home()?; + let config_path = home.join("wallet_config.json"); + Ok(config_path) } -/// Parse CLI auth string and merge with config auth, prioritizing CLI -pub fn merge_auth_config( - mut config: WalletConfig, - cli_auth: Option, -) -> Result { - if let Some(auth_str) = cli_auth { - let cli_auth_config: BasicAuth = auth_str.parse()?; - - if config.basic_auth.is_some() { - println!("Warning: CLI auth argument takes precedence over config basic-auth"); - } - - config.basic_auth = Some(cli_auth_config); - } - Ok(config) -} - -/// Fetch data stored at home +/// Fetch path to data storage from default home /// /// File must be created through setup beforehand. -pub async fn fetch_persistent_storage() -> Result { +pub fn fetch_persistent_storage_path() -> Result { let home = get_home()?; let accs_path = home.join("storage.json"); - let mut storage_content = vec![]; - - match tokio::fs::File::open(accs_path).await { - Ok(mut file) => { - file.read_to_end(&mut storage_content).await?; - Ok(serde_json::from_slice(&storage_content)?) - } - Err(err) => match err.kind() { - std::io::ErrorKind::NotFound => { - anyhow::bail!("Not found, please setup roots from config command beforehand"); - } - _ => { - anyhow::bail!("IO error {err:#?}"); - } - }, - } + Ok(accs_path) } /// Produces data for storage diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index bad6435a..45709d05 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, sync::Arc}; -use anyhow::Result; +use anyhow::{Context, Result}; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use chain_storage::WalletChainStore; use common::{ @@ -25,10 +25,8 @@ pub use privacy_preserving_tx::PrivacyPreservingAccount; use tokio::io::AsyncWriteExt; use crate::{ - config::PersistentStorage, - helperfunctions::{ - fetch_persistent_storage, get_home, produce_data_for_storage, produce_random_nonces, - }, + config::{PersistentStorage, WalletConfigOverrides}, + helperfunctions::{produce_data_for_storage, produce_random_nonces}, poller::TxPoller, }; @@ -124,91 +122,133 @@ impl TokenHolding { } pub struct WalletCore { - pub storage: WalletChainStore, - pub poller: TxPoller, + config_path: PathBuf, + storage: WalletChainStore, + storage_path: PathBuf, + poller: TxPoller, + // TODO: Make all fields private pub sequencer_client: Arc, pub last_synced_block: u64, } impl WalletCore { - pub async fn start_from_config_update_chain(config: WalletConfig) -> Result { - let basic_auth = config - .basic_auth - .as_ref() - .map(|auth| (auth.username.clone(), auth.password.clone())); - let client = Arc::new(SequencerClient::new_with_auth( - config.sequencer_addr.clone(), - basic_auth, - )?); - let tx_poller = TxPoller::new(config.clone(), client.clone()); + /// Construct wallet using [`HOME_DIR_ENV_VAR`] env var for paths or user home dir if not set. + pub fn from_env() -> Result { + let config_path = helperfunctions::fetch_config_path()?; + let storage_path = helperfunctions::fetch_persistent_storage_path()?; + Self::new_update_chain(config_path, storage_path, None) + } + + pub fn new_update_chain( + config_path: PathBuf, + storage_path: PathBuf, + config_overrides: Option, + ) -> Result { let PersistentStorage { accounts: persistent_accounts, last_synced_block, - } = fetch_persistent_storage().await?; + } = PersistentStorage::from_path(&storage_path) + .with_context(|| format!("Failed to read persistent storage at {storage_path:#?}"))?; - let storage = WalletChainStore::new(config, persistent_accounts)?; - - Ok(Self { - storage, - poller: tx_poller, - sequencer_client: client.clone(), + Self::new( + config_path, + storage_path, + config_overrides, + |config| WalletChainStore::new(config, persistent_accounts), last_synced_block, - }) + ) } - pub async fn start_from_config_new_storage( - config: WalletConfig, + pub fn new_init_storage( + config_path: PathBuf, + storage_path: PathBuf, + config_overrides: Option, password: String, ) -> Result { + Self::new( + config_path, + storage_path, + config_overrides, + |config| WalletChainStore::new_storage(config, password), + 0, + ) + } + + fn new( + config_path: PathBuf, + storage_path: PathBuf, + config_overrides: Option, + storage_ctor: impl FnOnce(WalletConfig) -> Result, + last_synced_block: u64, + ) -> Result { + let mut config = WalletConfig::from_path_or_initialize_default(&config_path) + .with_context(|| format!("Failed to deserialize wallet config at {config_path:#?}"))?; + if let Some(config_overrides) = config_overrides { + config.apply_overrides(config_overrides); + } + let basic_auth = config .basic_auth .as_ref() .map(|auth| (auth.username.clone(), auth.password.clone())); - let client = Arc::new(SequencerClient::new_with_auth( + let sequencer_client = Arc::new(SequencerClient::new_with_auth( config.sequencer_addr.clone(), basic_auth, )?); - let tx_poller = TxPoller::new(config.clone(), client.clone()); + let tx_poller = TxPoller::new(config.clone(), Arc::clone(&sequencer_client)); - let storage = WalletChainStore::new_storage(config, password)?; + let storage = storage_ctor(config)?; Ok(Self { + config_path, + storage_path, storage, poller: tx_poller, - sequencer_client: client.clone(), - last_synced_block: 0, + sequencer_client, + last_synced_block, }) } - /// Store persistent data at home - pub async fn store_persistent_data(&self) -> Result { - let home = get_home()?; - let storage_path = home.join("storage.json"); + /// Get configuration with applied overrides + pub fn config(&self) -> &WalletConfig { + &self.storage.wallet_config + } - let data = produce_data_for_storage(&self.storage.user_data, self.last_synced_block); - let storage = serde_json::to_vec_pretty(&data)?; + /// Get storage + pub fn storage(&self) -> &WalletChainStore { + &self.storage + } - let mut storage_file = tokio::fs::File::create(storage_path.as_path()).await?; - storage_file.write_all(&storage).await?; - - info!("Stored data at {storage_path:#?}"); - - Ok(storage_path) + /// Reset storage + pub fn reset_storage(&mut self, password: String) -> Result<()> { + self.storage = WalletChainStore::new_storage(self.storage.wallet_config.clone(), password)?; + Ok(()) } /// Store persistent data at home - pub async fn store_config_changes(&self) -> Result { - let home = get_home()?; - let config_path = home.join("wallet_config.json"); + pub async fn store_persistent_data(&self) -> Result<()> { + let data = produce_data_for_storage(&self.storage.user_data, self.last_synced_block); + let storage = serde_json::to_vec_pretty(&data)?; + + let mut storage_file = tokio::fs::File::create(&self.storage_path).await?; + storage_file.write_all(&storage).await?; + + println!("Stored persistent accounts at {:#?}", self.storage_path); + + Ok(()) + } + + /// Store persistent data at home + pub async fn store_config_changes(&self) -> Result<()> { let config = serde_json::to_vec_pretty(&self.storage.wallet_config)?; - let mut config_file = tokio::fs::File::create(config_path.as_path()).await?; + let mut config_file = tokio::fs::File::create(&self.config_path).await?; config_file.write_all(&config).await?; - info!("Stored data at {config_path:#?}"); + info!("Stored data at {:#?}", self.config_path); - Ok(config_path) + Ok(()) } pub fn create_new_account_public( @@ -335,7 +375,7 @@ impl WalletCore { pub async fn send_privacy_preserving_tx( &self, accounts: Vec, - instruction_data: &InstructionData, + instruction_data: InstructionData, program: &ProgramWithDependencies, ) -> Result<(SendTxResponse, Vec), ExecutionFailureKind> { self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| { @@ -347,7 +387,7 @@ impl WalletCore { pub async fn send_privacy_preserving_tx_with_pre_check( &self, accounts: Vec, - instruction_data: &InstructionData, + instruction_data: InstructionData, program: &ProgramWithDependencies, tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, ) -> Result<(SendTxResponse, Vec), ExecutionFailureKind> { @@ -363,16 +403,16 @@ impl WalletCore { let private_account_keys = acc_manager.private_account_keys(); let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove( - &pre_states, + pre_states, instruction_data, - acc_manager.visibility_mask(), - &produce_random_nonces(private_account_keys.len()), - &private_account_keys + acc_manager.visibility_mask().to_vec(), + produce_random_nonces(private_account_keys.len()), + private_account_keys .iter() .map(|keys| (keys.npk.clone(), keys.ssk)) .collect::>(), - &acc_manager.private_account_auth(), - &acc_manager.private_account_membership_proofs(), + acc_manager.private_account_auth(), + acc_manager.private_account_membership_proofs(), &program.to_owned(), ) .unwrap(); diff --git a/wallet/src/main.rs b/wallet/src/main.rs index 708b1fcf..045b1b83 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,36 +1,65 @@ -use anyhow::Result; +use anyhow::{Context as _, Result}; use clap::{CommandFactory as _, Parser as _}; -use tokio::runtime::Builder; -use wallet::cli::{Args, execute_continuous_run_with_auth, execute_subcommand_with_auth}; - -pub const NUM_THREADS: usize = 2; +use wallet::{ + WalletCore, + cli::{Args, execute_continuous_run, execute_subcommand, read_password_from_stdin}, + config::WalletConfigOverrides, + helperfunctions::{fetch_config_path, fetch_persistent_storage_path}, +}; // TODO #169: We have sample configs for sequencer, but not for wallet // TODO #168: Why it requires config as a directory? Maybe better to deduce directory from config // file path? // TODO #172: Why it requires config as env var while sequencer_runner accepts as // argument? -fn main() -> Result<()> { - let runtime = Builder::new_multi_thread() - .worker_threads(NUM_THREADS) - .enable_all() - .build() - .unwrap(); - - let args = Args::parse(); +#[tokio::main] +async fn main() -> Result<()> { + let Args { + continuous_run, + auth, + command, + } = Args::parse(); env_logger::init(); - runtime.block_on(async move { - if let Some(command) = args.command { - let _output = execute_subcommand_with_auth(command, args.auth).await?; - Ok(()) - } else if args.continuous_run { - execute_continuous_run_with_auth(args.auth).await + let config_path = fetch_config_path().context("Could not fetch config path")?; + let storage_path = + fetch_persistent_storage_path().context("Could not fetch persistent storage path")?; + + // Override basic auth if provided via CLI + let config_overrides = WalletConfigOverrides { + basic_auth: auth.map(|auth| auth.parse()).transpose()?.map(Some), + ..Default::default() + }; + + if let Some(command) = command { + let mut wallet = if !storage_path.exists() { + // TODO: Maybe move to `WalletCore::from_env()` or similar? + + println!("Persistent storage not found, need to execute setup"); + + let password = read_password_from_stdin()?; + let wallet = WalletCore::new_init_storage( + config_path, + storage_path, + Some(config_overrides), + password, + )?; + + wallet.store_persistent_data().await?; + wallet } else { - let help = Args::command().render_long_help(); - println!("{help}"); - Ok(()) - } - }) + WalletCore::new_update_chain(config_path, storage_path, Some(config_overrides))? + }; + let _output = execute_subcommand(&mut wallet, command).await?; + Ok(()) + } else if continuous_run { + let mut wallet = + WalletCore::new_update_chain(config_path, storage_path, Some(config_overrides))?; + execute_continuous_run(&mut wallet).await + } else { + let help = Args::command().render_long_help(); + println!("{help}"); + Ok(()) + } } diff --git a/wallet/src/program_facades/native_token_transfer/deshielded.rs b/wallet/src/program_facades/native_token_transfer/deshielded.rs index df604c65..7b774595 100644 --- a/wallet/src/program_facades/native_token_transfer/deshielded.rs +++ b/wallet/src/program_facades/native_token_transfer/deshielded.rs @@ -19,7 +19,7 @@ impl NativeTokenTransfer<'_> { PrivacyPreservingAccount::PrivateOwned(from), PrivacyPreservingAccount::Public(to), ], - &instruction_data, + instruction_data, &program.into(), tx_pre_check, ) diff --git a/wallet/src/program_facades/native_token_transfer/private.rs b/wallet/src/program_facades/native_token_transfer/private.rs index 0baeeac8..eabf1b60 100644 --- a/wallet/src/program_facades/native_token_transfer/private.rs +++ b/wallet/src/program_facades/native_token_transfer/private.rs @@ -15,11 +15,10 @@ impl NativeTokenTransfer<'_> { let instruction: u128 = 0; self.0 - .send_privacy_preserving_tx_with_pre_check( + .send_privacy_preserving_tx( vec![PrivacyPreservingAccount::PrivateOwned(from)], - &Program::serialize_instruction(instruction).unwrap(), + Program::serialize_instruction(instruction).unwrap(), &Program::authenticated_transfer_program().into(), - |_| Ok(()), ) .await .map(|(resp, secrets)| { @@ -47,7 +46,7 @@ impl NativeTokenTransfer<'_> { ipk: to_ipk, }, ], - &instruction_data, + instruction_data, &program.into(), tx_pre_check, ) @@ -74,7 +73,7 @@ impl NativeTokenTransfer<'_> { PrivacyPreservingAccount::PrivateOwned(from), PrivacyPreservingAccount::PrivateOwned(to), ], - &instruction_data, + instruction_data, &program.into(), tx_pre_check, ) diff --git a/wallet/src/program_facades/native_token_transfer/shielded.rs b/wallet/src/program_facades/native_token_transfer/shielded.rs index 6abd2d2c..85c8145c 100644 --- a/wallet/src/program_facades/native_token_transfer/shielded.rs +++ b/wallet/src/program_facades/native_token_transfer/shielded.rs @@ -20,7 +20,7 @@ impl NativeTokenTransfer<'_> { PrivacyPreservingAccount::Public(from), PrivacyPreservingAccount::PrivateOwned(to), ], - &instruction_data, + instruction_data, &program.into(), tx_pre_check, ) @@ -52,7 +52,7 @@ impl NativeTokenTransfer<'_> { ipk: to_ipk, }, ], - &instruction_data, + instruction_data, &program.into(), tx_pre_check, ) diff --git a/wallet/src/program_facades/pinata.rs b/wallet/src/program_facades/pinata.rs index fdd5d700..6036a603 100644 --- a/wallet/src/program_facades/pinata.rs +++ b/wallet/src/program_facades/pinata.rs @@ -37,7 +37,7 @@ impl Pinata<'_> { PrivacyPreservingAccount::Public(pinata_account_id), PrivacyPreservingAccount::PrivateOwned(winner_account_id), ], - &nssa::program::Program::serialize_instruction(solution).unwrap(), + nssa::program::Program::serialize_instruction(solution).unwrap(), &nssa::program::Program::pinata().into(), ) .await diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs index fc03a0ac..0d3f79d7 100644 --- a/wallet/src/program_facades/token.rs +++ b/wallet/src/program_facades/token.rs @@ -52,7 +52,7 @@ impl Token<'_> { PrivacyPreservingAccount::Public(definition_account_id), PrivacyPreservingAccount::PrivateOwned(supply_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -82,7 +82,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::Public(supply_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -112,7 +112,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::PrivateOwned(supply_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -180,7 +180,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(sender_account_id), PrivacyPreservingAccount::PrivateOwned(recipient_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -212,7 +212,7 @@ impl Token<'_> { ipk: recipient_ipk, }, ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -240,7 +240,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(sender_account_id), PrivacyPreservingAccount::Public(recipient_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -269,7 +269,7 @@ impl Token<'_> { PrivacyPreservingAccount::Public(sender_account_id), PrivacyPreservingAccount::PrivateOwned(recipient_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -302,7 +302,7 @@ impl Token<'_> { ipk: recipient_ipk, }, ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -365,7 +365,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::PrivateOwned(holder_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -393,7 +393,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::Public(holder_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -422,7 +422,7 @@ impl Token<'_> { PrivacyPreservingAccount::Public(definition_account_id), PrivacyPreservingAccount::PrivateOwned(holder_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -491,7 +491,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::PrivateOwned(holder_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -523,7 +523,7 @@ impl Token<'_> { ipk: holder_ipk, }, ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -551,7 +551,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::Public(holder_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -580,7 +580,7 @@ impl Token<'_> { PrivacyPreservingAccount::Public(definition_account_id), PrivacyPreservingAccount::PrivateOwned(holder_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -613,7 +613,7 @@ impl Token<'_> { ipk: holder_ipk, }, ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await