From f3e68f643e76e04bbedfb4e73b4c8778c6b9dffd Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Mon, 11 Mar 2024 15:19:54 -0700 Subject: [PATCH 01/19] chore: update crate versions to v0.2.0 --- Cargo.lock | 12 ++++++------ block-producer/Cargo.toml | 8 ++++---- node/Cargo.toml | 12 ++++++------ proto/Cargo.toml | 4 ++-- rpc/Cargo.toml | 12 ++++++------ store/Cargo.toml | 8 ++++---- utils/Cargo.toml | 2 +- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7973814ba..91dd8340b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1119,7 +1119,7 @@ dependencies = [ [[package]] name = "miden-node" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "clap", @@ -1138,7 +1138,7 @@ dependencies = [ [[package]] name = "miden-node-block-producer" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "async-trait", @@ -1167,7 +1167,7 @@ dependencies = [ [[package]] name = "miden-node-proto" -version = "0.1.0" +version = "0.2.0" dependencies = [ "hex", "miden-node-utils", @@ -1184,7 +1184,7 @@ dependencies = [ [[package]] name = "miden-node-rpc" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "clap", @@ -1208,7 +1208,7 @@ dependencies = [ [[package]] name = "miden-node-store" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "clap", @@ -1243,7 +1243,7 @@ dependencies = [ [[package]] name = "miden-node-utils" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "figment", diff --git a/block-producer/Cargo.toml b/block-producer/Cargo.toml index 64593d389..0a99e2fe8 100644 --- a/block-producer/Cargo.toml +++ b/block-producer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-node-block-producer" -version = "0.1.0" +version = "0.2.0" description = "Miden node's block producer component" authors = ["miden contributors"] readme = "README.md" @@ -25,9 +25,9 @@ async-trait = { version = "0.1" } clap = { version = "4.3", features = ["derive"] } figment = { version = "0.10", features = ["toml", "env"] } itertools = { version = "0.12" } -miden-node-proto = { path = "../proto", version = "0.1" } -miden-node-store = { path = "../store", version = "0.1" } -miden-node-utils = { path = "../utils", version = "0.1" } +miden-node-proto = { path = "../proto", version = "0.2" } +miden-node-store = { path = "../store", version = "0.2" } +miden-node-utils = { path = "../utils", version = "0.2" } miden-objects = { workspace = true } miden-processor = { workspace = true } miden-stdlib = { workspace = true } diff --git a/node/Cargo.toml b/node/Cargo.toml index 1e0bfcf8a..26eb2ad83 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-node" -version = "0.1.0" +version = "0.2.0" description = "Miden node single binary" authors = ["miden contributors"] readme = "README.md" @@ -19,10 +19,10 @@ tracing-forest = ["miden-node-block-producer/tracing-forest"] anyhow = { version = "1.0" } clap = { version = "4.3", features = ["derive"] } miden-lib = { workspace = true, features = ["concurrent"] } -miden-node-block-producer = { path = "../block-producer", version = "0.1" } -miden-node-rpc = { path = "../rpc", version = "0.1" } -miden-node-store = { path = "../store", version = "0.1" } -miden-node-utils = { path = "../utils", version = "0.1" } +miden-node-block-producer = { path = "../block-producer", version = "0.2" } +miden-node-rpc = { path = "../rpc", version = "0.2" } +miden-node-store = { path = "../store", version = "0.2" } +miden-node-utils = { path = "../utils", version = "0.2" } miden-objects = { workspace = true } serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.29", features = ["rt-multi-thread", "net", "macros"] } @@ -31,4 +31,4 @@ tracing-subscriber = { workspace = true } [dev-dependencies] figment = { version = "0.10", features = ["toml", "env", "test"] } -miden-node-utils = { path = "../utils", version = "0.1", features = ["tracing-forest"] } +miden-node-utils = { path = "../utils", version = "0.2", features = ["tracing-forest"] } diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 7d68981fb..4a89426e4 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-node-proto" -version = "0.1.0" +version = "0.2.0" description = "Miden RPC message definitions" authors = ["miden contributors"] readme = "README.md" @@ -12,7 +12,7 @@ rust-version = "1.75" [dependencies] hex = { version = "0.4" } -miden-node-utils = { path = "../utils", version = "0.1" } +miden-node-utils = { path = "../utils", version = "0.2" } miden-objects = { workspace = true } prost = { version = "0.12" } thiserror = { workspace = true } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 6137abbaf..336a70e5e 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-node-rpc" -version = "0.1.0" +version = "0.2.0" description = "Miden node's front-end RPC server" authors = ["miden contributors"] readme = "README.md" @@ -16,10 +16,10 @@ clap = { version = "4.3", features = ["derive"] } directories = { version = "5.0" } figment = { version = "0.10", features = ["toml", "env"] } hex = { version = "0.4" } -miden-node-block-producer = { path = "../block-producer", version = "0.1" } -miden-node-proto = { path = "../proto", version = "0.1" } -miden-node-store = { path = "../store", version = "0.1" } -miden-node-utils = { path = "../utils", version = "0.1" } +miden-node-block-producer = { path = "../block-producer", version = "0.2" } +miden-node-proto = { path = "../proto", version = "0.2" } +miden-node-store = { path = "../store", version = "0.2" } +miden-node-utils = { path = "../utils", version = "0.2" } miden-objects = { workspace = true } miden-tx = { workspace = true } prost = { version = "0.12" } @@ -32,4 +32,4 @@ tracing-subscriber = { workspace = true } [dev-dependencies] figment = { version = "0.10", features = ["toml", "env", "test"] } -miden-node-utils = { path = "../utils", version = "0.1", features = ["tracing-forest"] } +miden-node-utils = { path = "../utils", version = "0.2", features = ["tracing-forest"] } diff --git a/store/Cargo.toml b/store/Cargo.toml index 35f50508d..3d9929eaf 100644 --- a/store/Cargo.toml +++ b/store/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-node-store" -version = "0.1.0" +version = "0.2.0" description = "Miden node's state store component" authors = ["miden contributors"] readme = "README.md" @@ -24,8 +24,8 @@ directories = { version = "5.0" } figment = { version = "0.10", features = ["toml", "env"] } hex = { version = "0.4" } miden-lib = { workspace = true } -miden-node-proto = { path = "../proto", version = "0.1" } -miden-node-utils = { path = "../utils", version = "0.1" } +miden-node-proto = { path = "../proto", version = "0.2" } +miden-node-utils = { path = "../utils", version = "0.2" } miden-objects = { workspace = true } once_cell = { version = "1.18.0" } prost = { version = "0.12" } @@ -41,4 +41,4 @@ tracing-subscriber = { workspace = true } [dev-dependencies] figment = { version = "0.10", features = ["toml", "env", "test"] } -miden-node-utils = { path = "../utils", version = "0.1", features = ["tracing-forest"] } +miden-node-utils = { path = "../utils", version = "0.2", features = ["tracing-forest"] } diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 7365ccfd2..442af69df 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-node-utils" -version = "0.1.0" +version = "0.2.0" description = "Miden node's shared utilities" authors = ["miden contributors"] readme = "README.md" From 5c79ccb0069e9364b0c64c71197626a2b0ebb1be Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz <42912740+phklive@users.noreply.github.com> Date: Thu, 14 Mar 2024 10:45:16 +0100 Subject: [PATCH 02/19] Add script to check rust versions + cleanup (#272) * ci: turn doc warnings into errors (#259) * remove unnecessary comments * Add script to check rust versions * removed versions and editions except from root, updated script * add back cargo make doc to ci * Added section for testing * Fixed typo * Set versions to 0.1.0 + inherit all infos from workspace * Fix use of versioning * Set individual versions for crates * Fixed nits, code organization and updated links * Improve naming of ci job * Fix formatting in contributing.md * Add task to format not only check * Reduced indent --------- Co-authored-by: Augusto Hack --- .github/workflows/ci.yml | 11 +++++++++++ .gitignore | 5 +---- CONTRIBUTING.md | 11 +++++++++-- Cargo.toml | 11 +++++++++++ Makefile.toml | 6 ++++++ block-producer/Cargo.toml | 15 ++++++++------- node/Cargo.toml | 15 ++++++++------- proto/Cargo.toml | 13 +++++++------ rpc/Cargo.toml | 13 +++++++------ scripts/check-rust-version.sh | 13 +++++++++++++ store/Cargo.toml | 13 +++++++------ test-macro/Cargo.toml | 15 ++++++++------- utils/Cargo.toml | 13 +++++++------ 13 files changed, 103 insertions(+), 51 deletions(-) create mode 100755 scripts/check-rust-version.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e68234e5d..ad8e18276 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,17 @@ on: types: [opened, reopened, synchronize] jobs: + version: + name: check rust version consistency + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + profile: minimal + override: true + - name: check rust versions + run: ./scripts/check-rust-version.sh + rustfmt: name: rustfmt nightly on ubuntu-latest runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index b050493c9..d03086c8a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Generated by Cargo + # will have compiled files and executables debug/ target/ @@ -6,10 +7,6 @@ target/ # Generated by protox `file_descriptor_set.bin` *.bin -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -# Cargo.lock - # These are backup files generated by rustfmt **/*.rs.bk diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd9ada360..1a8ef1707 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,10 +4,10 @@ We want to make contributing to this project as easy and transparent as possible, whether it's: -- Reporting a [bug](https://github.com/0xPolygonMiden/miden-node/issues/new) +- Reporting a [bug](https://github.com/0xPolygonMiden/miden-node/issues/new?assignees=&labels=bug&projects=&template=1-bugreport.yml&title=%5BBug%5D%3A+) - Taking part in [discussions](https://github.com/0xPolygonMiden/miden-node/discussions) - Submitting a [fix](https://github.com/0xPolygonMiden/miden-node/pulls) -- Proposing new [features](https://github.com/0xPolygonMiden/miden-node/issues/new) +- Proposing new [features](https://github.com/0xPolygonMiden/miden-node/issues/new?assignees=&labels=enhancement&projects=&template=2-feature-request.yml&title=%5BFeature%5D%3A+)   @@ -80,6 +80,13 @@ For example, a new change to the `miden-node-store` crate might have the followi You can find more information about the `cargo make` commands in the [Makefile](Makefile.toml) +### Testing +After writing code different types of tests (unit, integration, end-to-end) are required to make sure that the correct behavior has been achieved and that no bugs have been introduced. You can run tests using the following command: + +``` +cargo make test +``` + ### Versioning We use [semver](https://semver.org/) naming convention. diff --git a/Cargo.toml b/Cargo.toml index 788b34cbb..81310bd4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,19 @@ members = [ "utils", "test-macro", ] + resolver = "2" +[workspace.package] +edition = "2021" +rust-version = "1.75" +license = "MIT" +authors = ["Miden contributors"] +readme = "README.md" +homepage = "https://polygon.technology/polygon-miden" +repository = "https://github.com/0xPolygonMiden/miden-node" +exclude = [".github/"] + [workspace.dependencies] miden-air = { version = "0.8", default-features = false } miden-lib = { version = "0.1" } diff --git a/Makefile.toml b/Makefile.toml index 4df41d232..3843976f7 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -6,6 +6,11 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true [tasks.format] toolchain = "nightly" command = "cargo" +args = ["fmt", "--all"] + +[tasks.format-check] +toolchain = "nightly" +command = "cargo" args = ["fmt", "--all", "--", "--check"] [tasks.clippy-default] @@ -34,6 +39,7 @@ args = ["test", "--all-features", "--workspace", "--", "--nocapture"] [tasks.lint] dependencies = [ "format", + "format-check", "clippy", "docs" ] diff --git a/block-producer/Cargo.toml b/block-producer/Cargo.toml index 0a99e2fe8..864dd9012 100644 --- a/block-producer/Cargo.toml +++ b/block-producer/Cargo.toml @@ -2,13 +2,14 @@ name = "miden-node-block-producer" version = "0.2.0" description = "Miden node's block producer component" -authors = ["miden contributors"] -readme = "README.md" -license = "MIT" -repository = "https://github.com/0xPolygonMiden/miden-node" -keywords = ["miden", "node", "store"] -edition = "2021" -rust-version = "1.75" +keywords = ["miden", "node", "block-producer"] +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true [[bin]] name = "miden-node-block-producer" diff --git a/node/Cargo.toml b/node/Cargo.toml index 26eb2ad83..e9fdcc236 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "miden-node" version = "0.2.0" -description = "Miden node single binary" -authors = ["miden contributors"] -readme = "README.md" -license = "MIT" -repository = "https://github.com/0xPolygonMiden/miden-node" +description = "Miden node binary" keywords = ["miden", "node"] -edition = "2021" -rust-version = "1.75" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true [features] # Makes `make-genesis` subcommand run faster. Is only suitable for testing. diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 4a89426e4..2080e7687 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -2,13 +2,14 @@ name = "miden-node-proto" version = "0.2.0" description = "Miden RPC message definitions" -authors = ["miden contributors"] -readme = "README.md" -license = "MIT" -repository = "https://github.com/0xPolygonMiden/miden-node" keywords = ["miden", "node", "protobuf", "rpc"] -edition = "2021" -rust-version = "1.75" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] hex = { version = "0.4" } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 336a70e5e..48c216c76 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -2,13 +2,14 @@ name = "miden-node-rpc" version = "0.2.0" description = "Miden node's front-end RPC server" -authors = ["miden contributors"] -readme = "README.md" -license = "MIT" -repository = "https://github.com/0xPolygonMiden/miden-node" keywords = ["miden", "node", "rpc"] -edition = "2021" -rust-version = "1.75" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] anyhow = { version = "1.0" } diff --git a/scripts/check-rust-version.sh b/scripts/check-rust-version.sh new file mode 100755 index 000000000..a98dd8bf3 --- /dev/null +++ b/scripts/check-rust-version.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Check rust-toolchain file +TOOLCHAIN_VERSION=$(cat rust-toolchain) + +# Check workspace Cargo.toml file +CARGO_VERSION=$(cat Cargo.toml | grep "rust-version" | cut -d '"' -f 2) +if [ "$CARGO_VERSION" != "$TOOLCHAIN_VERSION" ]; then + echo "Mismatch in $file. Expected $TOOLCHAIN_VERSION, found $CARGO_VERSION" + exit 1 +fi + +echo "Rust versions match ✅" diff --git a/store/Cargo.toml b/store/Cargo.toml index 3d9929eaf..a485300df 100644 --- a/store/Cargo.toml +++ b/store/Cargo.toml @@ -2,13 +2,14 @@ name = "miden-node-store" version = "0.2.0" description = "Miden node's state store component" -authors = ["miden contributors"] -readme = "README.md" -license = "MIT" -repository = "https://github.com/0xPolygonMiden/miden-node" keywords = ["miden", "node", "store"] -edition = "2021" -rust-version = "1.75" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true [[bin]] name = "miden-node-store" diff --git a/test-macro/Cargo.toml b/test-macro/Cargo.toml index dfd5ad2f3..a02c66c9c 100644 --- a/test-macro/Cargo.toml +++ b/test-macro/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "miden-node-test-macro" version = "0.1.0" -description = "Miden's test macro" -authors = ["miden contributors"] -readme = "README.md" -license = "MIT" -repository = "https://github.com/0xPolygonMiden/miden-node" +description = "Miden node's test macro" keywords = ["miden", "node", "utils", "macro"] -edition = "2021" -rust-version = "1.75" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] quote = { version = "1.0" } diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 442af69df..39f2d4486 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -2,13 +2,14 @@ name = "miden-node-utils" version = "0.2.0" description = "Miden node's shared utilities" -authors = ["miden contributors"] -readme = "README.md" -license = "MIT" -repository = "https://github.com/0xPolygonMiden/miden-node" keywords = ["miden", "node", "utils"] -edition = "2021" -rust-version = "1.75" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] anyhow = { version = "1.0" } From 2e66d45992bff57106e4cb0bd6843e9c23f72f74 Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz <42912740+phklive@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:23:29 +0100 Subject: [PATCH 03/19] Dockerize node (#257) * Working initial docker * fmt * added script * Removed docker script + updated integration * script works * Need to install grpcurl * Docker works * Working Dockerfile * ci: turn doc warnings into errors (#259) * Removed makefile, removing start.sh file moving in Dockerfile * Added bookworm + alpine + removed gcc + added caching * Moved Dockerfile to node and added Makefile.toml * cargo make works + builds dockerfile for node * remove start script * added comment regarding PID1 * Set labels at top of Dockerfile + added comments * Added correct dependencies and explanation * Volume works persisting db files between runs * Added documentation * Moved labels + enable mounting of miden-node.toml * Added docker-run-node command + added build arguments * Add git commit as arg to docker container * Added spacing --------- Co-authored-by: Augusto Hack --- .dockerignore | 38 +++++++++++++++++++++++++++ .github/workflows/ci.yml | 3 +++ Makefile.toml | 43 ++++++++++++++++++++++++++---- README.md | 30 ++++++++++++++++++++- miden-node.toml | 21 +++++++++++++++ node/Dockerfile | 56 ++++++++++++++++++++++++++++++++++++++++ node/miden-node.toml | 4 +-- 7 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 .dockerignore create mode 100644 miden-node.toml create mode 100644 node/Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..b5dbe9424 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,38 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/go/build-context-dockerignore/ + +**/.DS_Store +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/charts +**/docker-compose* +**/compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/secrets.dev.yaml +**/values.dev.yaml +./bin +./target +/bin +/target +LICENSE +README.md +accounts +genesis.dat +miden-store.* +store.* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad8e18276..af5cc2278 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,7 @@ +# Runs the CI + name: CI + on: push: branches: diff --git a/Makefile.toml b/Makefile.toml index 3843976f7..2266170e7 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -1,8 +1,11 @@ # Cargo Makefile +# If running cargo-make in a workspace you need to add this env variable to make sure it function correctly. +# See docs: https://github.com/sagiegurari/cargo-make?tab=readme-ov-file#usage [env] CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true +# linting [tasks.format] toolchain = "nightly" command = "cargo" @@ -30,11 +33,7 @@ dependencies = [ [tasks.doc] env = { "RUSTDOCFLAGS" = "-D warnings" } command = "cargo" -args = ["doc", "--all-features", "--keep-going", "--release"] - -[tasks.test] -command = "cargo" -args = ["test", "--all-features", "--workspace", "--", "--nocapture"] +args = ["doc", "--verbose", "--all-features", "--keep-going", "--release"] [tasks.lint] dependencies = [ @@ -43,3 +42,37 @@ dependencies = [ "clippy", "docs" ] + +# testing +[tasks.test] +command = "cargo" +args = ["test", "--all-features", "--workspace", "--", "--nocapture"] + +# docker +[tasks.docker-build-node] +workspace = false +script = ''' +CREATED=$(date) +VERSION=$(cat node/Cargo.toml | grep -m 1 '^version' | cut -d '"' -f 2) +COMMIT=$(git rev-parse HEAD) + +docker build --build-arg CREATED="$CREATED" \ + --build-arg VERSION="$VERSION" \ + --build-arg COMMIT="$COMMIT" \ + -f node/Dockerfile \ + -t miden-node-image . +''' + +[tasks.docker-run-node] +workspace = false +script = ''' +docker volume create miden-db + +ABSOLUTE_PATH="$(pwd)/node/miden-node.toml" + +docker run --name miden-node \ + -p 57291:57291 \ + -v miden-db:/db \ + -v "${ABSOLUTE_PATH}:/miden-node.toml" \ + -d miden-node-image +''' diff --git a/README.md b/README.md index 7ca94f900..4dad628e1 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Before you can build and run the Miden node or any of its components, you'll nee Depending on the platform, you may need to install additional libraries. For example, on Ubuntu 22.04 the following command ensures that all required libraries are installed. ```sh -sudo apt install gcc llvm clang bindgen pkg-config libssl-dev libsqlite3-dev +sudo apt install llvm clang bindgen pkg-config libssl-dev libsqlite3-dev ``` ### Installing the node @@ -91,5 +91,33 @@ Please, refer to each component's documentation: Each directory containing the executables also contains an example configuration file. Make sure that the configuration files are mutually consistent. That is, make sure that the URLs are valid and point to the right endpoint. +### Running the node using Docker + +If you intend on running the node inside a Docker container, you will need to follow these steps: + +1. Build the docker image from source + + ```sh + cargo make docker-build-node + ``` + + This command will build the docker image for the Miden node and save it locally. + +2. Run the Docker container + + ```sh + docker run --name miden-node -p 57291:57291 -d miden-node-image + ``` + + This command will run the node as a container named `miden-node` using the `miden-node-image` and make port `57291` available (rpc endpoint). + +3. Monitor container + + ```sh + docker ps + ``` + + After running this command you should see the name of the container `miden-node` being outputed and marked as `Up`. + ## License This project is [MIT licensed](./LICENSE). diff --git a/miden-node.toml b/miden-node.toml new file mode 100644 index 000000000..04f582981 --- /dev/null +++ b/miden-node.toml @@ -0,0 +1,21 @@ +# This is an example configuration file for the Miden node. + +[block_producer] +# port defined as: sum(ord(c)**p for (p, c) in enumerate('miden-block-producer', 1)) % 2**16 +endpoint = { host = "localhost", port = 48046 } +store_url = "http://localhost:28943" +# enables or disables the verification of transaction proofs before they are accepted into the +# transaction queue. +verify_tx_proofs = true + +[rpc] +# port defined as: sum(ord(c)**p for (p, c) in enumerate('miden-rpc', 1)) % 2**16 +endpoint = { host = "0.0.0.0", port = 57291 } +block_producer_url = "http://localhost:48046" +store_url = "http://localhost:28943" + +[store] +# port defined as: sum(ord(c)**p for (p, c) in enumerate('miden-store', 1)) % 2**16 +endpoint = { host = "localhost", port = 28943 } +database_filepath = "db/miden-store.sqlite3" +genesis_filepath = "genesis.dat" diff --git a/node/Dockerfile b/node/Dockerfile new file mode 100644 index 000000000..6fc95e69c --- /dev/null +++ b/node/Dockerfile @@ -0,0 +1,56 @@ +# Miden node Dockerfile + +# Setup image builder +FROM rust:1.76-slim-bookworm AS builder + +# Install dependencies +RUN apt-get update && \ + apt-get -y upgrade && \ + apt-get install -y llvm clang bindgen pkg-config libssl-dev libsqlite3-dev && \ + rm -rf /var/lib/apt/lists/* + +# Copy source code +WORKDIR /app +COPY . . + +# Build the node crate +RUN cargo install --features testing --path node +RUN miden-node make-genesis --inputs-path node/genesis.toml + +# Run Miden node +FROM debian:bookworm-slim + +# Update machine & install required packages +# The instalation of sqlite3 is needed for correct function of the SQLite database +RUN apt-get update && \ + apt-get -y upgrade && \ + apt-get install -y --no-install-recommends \ + sqlite3 \ + && rm -rf /var/lib/apt/lists/* + +# Copy artifacts from the builder stage +COPY --from=builder /app/genesis.dat genesis.dat +COPY --from=builder /app/accounts accounts +COPY --from=builder /usr/local/cargo/bin/miden-node /usr/local/bin/miden-node + +# Set labels +LABEL org.opencontainers.image.authors=miden@polygon.io \ + org.opencontainers.image.url=https://0xpolygonmiden.github.io/ \ + org.opencontainers.image.documentation=https://github.com/0xPolygonMiden/miden-node \ + org.opencontainers.image.source=https://github.com/0xPolygonMiden/miden-node \ + org.opencontainers.image.vendor=Polygon \ + org.opencontainers.image.licenses=MIT + +ARG CREATED +ARG VERSION +ARG COMMIT +LABEL org.opencontainers.image.created=$CREATED \ + org.opencontainers.image.version=$VERSION \ + org.opencontainers.image.revision=$COMMIT + +# Expose RPC port +EXPOSE 57291 + +# Start the Miden node +# Miden node does not spawn sub-processes, so it can be used as the PID1 +CMD miden-node start --config miden-node.toml diff --git a/node/miden-node.toml b/node/miden-node.toml index f0811acd7..04f582981 100644 --- a/node/miden-node.toml +++ b/node/miden-node.toml @@ -10,12 +10,12 @@ verify_tx_proofs = true [rpc] # port defined as: sum(ord(c)**p for (p, c) in enumerate('miden-rpc', 1)) % 2**16 -endpoint = { host = "localhost", port = 57291 } +endpoint = { host = "0.0.0.0", port = 57291 } block_producer_url = "http://localhost:48046" store_url = "http://localhost:28943" [store] # port defined as: sum(ord(c)**p for (p, c) in enumerate('miden-store', 1)) % 2**16 endpoint = { host = "localhost", port = 28943 } -database_filepath = "miden-store.sqlite3" +database_filepath = "db/miden-store.sqlite3" genesis_filepath = "genesis.dat" From 95d3fff41205abc9acbcdd71c18606e5715860d0 Mon Sep 17 00:00:00 2001 From: polydez <155382956+polydez@users.noreply.github.com> Date: Thu, 14 Mar 2024 22:47:08 +0500 Subject: [PATCH 04/19] Implemented nullifier tree wrapper over `Smt` (#275) * feat: implement nullifier tree wrapper over `Smt` * refactor: move `NullifierTree` to separated file, renames ans small fixes * fix: address review comments --- store/src/db/mod.rs | 4 +- store/src/db/sql.rs | 11 ++-- store/src/db/tests.rs | 26 +++++---- store/src/errors.rs | 27 +++++++-- store/src/lib.rs | 1 + store/src/nullifier_tree.rs | 113 ++++++++++++++++++++++++++++++++++++ store/src/server/api.rs | 7 ++- store/src/state.rs | 86 +++++++-------------------- 8 files changed, 185 insertions(+), 90 deletions(-) create mode 100644 store/src/nullifier_tree.rs diff --git a/store/src/db/mod.rs b/store/src/db/mod.rs index c743a81be..e6ea9ee76 100644 --- a/store/src/db/mod.rs +++ b/store/src/db/mod.rs @@ -133,7 +133,7 @@ impl Db { /// Loads all the nullifiers from the DB. #[instrument(target = "miden-store", skip_all, ret(level = "debug"), err)] - pub async fn select_nullifiers(&self) -> Result> { + pub async fn select_nullifiers(&self) -> Result> { self.pool.get().await?.interact(sql::select_nullifiers).await.map_err(|err| { DatabaseError::InteractError(format!("Select nullifiers task failed: {err}")) })? @@ -242,7 +242,7 @@ impl Db { acquire_done: oneshot::Receiver<()>, block_header: BlockHeader, notes: Vec, - nullifiers: Vec, + nullifiers: Vec, accounts: Vec<(AccountId, RpoDigest)>, ) -> Result<()> { self.pool diff --git a/store/src/db/sql.rs b/store/src/db/sql.rs index 1b59415f7..55d2ddf65 100644 --- a/store/src/db/sql.rs +++ b/store/src/db/sql.rs @@ -3,6 +3,7 @@ use std::rc::Rc; use miden_objects::{ crypto::hash::rpo::RpoDigest, + notes::Nullifier, utils::serde::{Deserializable, Serializable}, BlockHeader, }; @@ -152,7 +153,7 @@ pub fn upsert_accounts( /// transaction. pub fn insert_nullifiers_for_block( transaction: &Transaction, - nullifiers: &[RpoDigest], + nullifiers: &[Nullifier], block_num: BlockNumber, ) -> Result { let mut stmt = transaction.prepare( @@ -172,7 +173,7 @@ pub fn insert_nullifiers_for_block( /// # Returns /// /// A vector with nullifiers and the block height at which they were created, or an error. -pub fn select_nullifiers(conn: &mut Connection) -> Result> { +pub fn select_nullifiers(conn: &mut Connection) -> Result> { let mut stmt = conn.prepare("SELECT nullifier, block_number FROM nullifiers ORDER BY block_number ASC;")?; let mut rows = stmt.query([])?; @@ -536,7 +537,7 @@ pub fn apply_block( transaction: &Transaction, block_header: &BlockHeader, notes: &[Note], - nullifiers: &[RpoDigest], + nullifiers: &[Nullifier], accounts: &[(AccountId, RpoDigest)], ) -> Result { let mut count = 0; @@ -556,8 +557,8 @@ fn deserialize(data: &[u8]) -> Result { } /// Returns the high 16 bits of the provided nullifier. -pub(crate) fn get_nullifier_prefix(nullifier: &RpoDigest) -> u32 { - (nullifier[3].as_int() >> 48) as u32 +pub(crate) fn get_nullifier_prefix(nullifier: &Nullifier) -> u32 { + (nullifier.most_significant_felt().as_int() >> 48) as u32 } /// Converts a `u64` into a [Value]. diff --git a/store/src/db/tests.rs b/store/src/db/tests.rs index 25fd21341..7abcf5da9 100644 --- a/store/src/db/tests.rs +++ b/store/src/db/tests.rs @@ -3,7 +3,7 @@ use miden_objects::{ hash::rpo::RpoDigest, merkle::{LeafIndex, MerklePath, SimpleSmt}, }, - notes::NOTE_LEAF_DEPTH, + notes::{Nullifier, NOTE_LEAF_DEPTH}, BlockHeader, Felt, FieldElement, }; use rusqlite::{vtab::array, Connection}; @@ -22,7 +22,7 @@ fn create_db() -> Connection { fn test_sql_insert_nullifiers_for_block() { let mut conn = create_db(); - let nullifiers = [num_to_rpo_digest(1 << 48)]; + let nullifiers = [num_to_nullifier(1 << 48)]; let block_num = 1; // Insert a new nullifier succeeds @@ -53,7 +53,7 @@ fn test_sql_insert_nullifiers_for_block() { // test inserting multiple nullifiers { - let nullifiers: Vec<_> = (0..10).map(num_to_rpo_digest).collect(); + let nullifiers: Vec<_> = (0..10).map(num_to_nullifier).collect(); let block_num = 1; let transaction = conn.transaction().unwrap(); let res = sql::insert_nullifiers_for_block(&transaction, &nullifiers, block_num); @@ -74,7 +74,7 @@ fn test_sql_select_nullifiers() { let block_num = 1; let mut state = vec![]; for i in 0..10 { - let nullifier = num_to_rpo_digest(i); + let nullifier = num_to_nullifier(i); state.push((nullifier, block_num)); let transaction = conn.transaction().unwrap(); @@ -157,7 +157,7 @@ fn test_sql_select_nullifiers_by_block_range() { assert!(nullifiers.is_empty()); // test single item - let nullifier1 = num_to_rpo_digest(1 << 48); + let nullifier1 = num_to_nullifier(1 << 48); let block_number1 = 1; let transaction = conn.transaction().unwrap(); @@ -174,13 +174,13 @@ fn test_sql_select_nullifiers_by_block_range() { assert_eq!( nullifiers, vec![NullifierInfo { - nullifier: nullifier1.into(), + nullifier: nullifier1, block_num: block_number1 }] ); // test two elements - let nullifier2 = num_to_rpo_digest(2 << 48); + let nullifier2 = num_to_nullifier(2 << 48); let block_number2 = 2; let transaction = conn.transaction().unwrap(); @@ -201,7 +201,7 @@ fn test_sql_select_nullifiers_by_block_range() { assert_eq!( nullifiers, vec![NullifierInfo { - nullifier: nullifier1.into(), + nullifier: nullifier1, block_num: block_number1 }] ); @@ -215,7 +215,7 @@ fn test_sql_select_nullifiers_by_block_range() { assert_eq!( nullifiers, vec![NullifierInfo { - nullifier: nullifier2.into(), + nullifier: nullifier2, block_num: block_number2 }] ); @@ -231,7 +231,7 @@ fn test_sql_select_nullifiers_by_block_range() { assert_eq!( nullifiers, vec![NullifierInfo { - nullifier: nullifier1.into(), + nullifier: nullifier1, block_num: block_number1 }] ); @@ -247,7 +247,7 @@ fn test_sql_select_nullifiers_by_block_range() { assert_eq!( nullifiers, vec![NullifierInfo { - nullifier: nullifier2.into(), + nullifier: nullifier2, block_num: block_number2 }] ); @@ -481,3 +481,7 @@ fn test_notes() { fn num_to_rpo_digest(n: u64) -> RpoDigest { RpoDigest::new([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::new(n)]) } + +fn num_to_nullifier(n: u64) -> Nullifier { + Nullifier::from(num_to_rpo_digest(n)) +} diff --git a/store/src/errors.rs b/store/src/errors.rs index 2c046e750..2856b5f96 100644 --- a/store/src/errors.rs +++ b/store/src/errors.rs @@ -6,12 +6,29 @@ use miden_objects::{ merkle::{MerkleError, MmrError}, utils::DeserializationError, }, - AccountError, BlockHeader, Digest, + notes::Nullifier, + AccountError, BlockHeader, }; use rusqlite::types::FromSqlError; use thiserror::Error; use tokio::sync::oneshot::error::RecvError; +use crate::types::BlockNumber; + +// INTERNAL ERRORS +// ================================================================================================= + +#[derive(Debug, Error)] +pub enum NullifierTreeError { + #[error("Merkle error: {0}")] + MerkleError(#[from] MerkleError), + #[error("Nullifier {nullifier} for block #{block_num} already exists in the nullifier tree")] + NullifierAlreadyExists { + nullifier: Nullifier, + block_num: BlockNumber, + }, +} + // DATABASE ERRORS // ================================================================================================= @@ -40,8 +57,8 @@ pub enum DatabaseError { pub enum StateInitializationError { #[error("Database error: {0}")] DatabaseError(#[from] DatabaseError), - #[error("Failed to create nullifiers tree: {0}")] - FailedToCreateNullifiersTree(MerkleError), + #[error("Failed to create nullifier tree: {0}")] + FailedToCreateNullifierTree(NullifierTreeError), #[error("Failed to create accounts tree: {0}")] FailedToCreateAccountsTree(MerkleError), } @@ -106,7 +123,7 @@ pub enum ApplyBlockError { #[error("Received invalid nullifier root")] NewBlockInvalidNullifierRoot, #[error("Duplicated nullifiers {0:?}")] - DuplicatedNullifiers(Vec), + DuplicatedNullifiers(Vec), #[error("Unable to create proof for note: {0}")] UnableToCreateProofForNote(MerkleError), #[error("Block applying was broken because of closed channel on database side: {0}")] @@ -117,6 +134,8 @@ pub enum ApplyBlockError { DbBlockHeaderEmpty, #[error("Failed to get MMR peaks for forest ({forest}): {error}")] FailedToGetMmrPeaksForForest { forest: usize, error: MmrError }, + #[error("Failed to update nullifier tree: {0}")] + FailedToUpdateNullifierTree(NullifierTreeError), } #[derive(Error, Debug)] diff --git a/store/src/lib.rs b/store/src/lib.rs index 19bceb9c6..a98c4764c 100644 --- a/store/src/lib.rs +++ b/store/src/lib.rs @@ -2,6 +2,7 @@ pub mod config; pub mod db; pub mod errors; pub mod genesis; +mod nullifier_tree; pub mod server; pub mod state; pub mod types; diff --git a/store/src/nullifier_tree.rs b/store/src/nullifier_tree.rs new file mode 100644 index 000000000..e4ef902b1 --- /dev/null +++ b/store/src/nullifier_tree.rs @@ -0,0 +1,113 @@ +use miden_objects::{ + crypto::{ + hash::rpo::RpoDigest, + merkle::{Smt, SmtProof}, + }, + notes::Nullifier, + Felt, FieldElement, Word, +}; + +use crate::{errors::NullifierTreeError, types::BlockNumber}; + +/// Nullifier SMT. +#[derive(Debug, Clone)] +pub struct NullifierTree(Smt); + +impl NullifierTree { + /// Construct new nullifier tree from list of items. + pub fn with_entries( + entries: impl IntoIterator + ) -> Result { + let leaves = entries.into_iter().map(|(nullifier, block_num)| { + (nullifier.inner(), Self::block_num_to_leaf_value(block_num)) + }); + + let inner = Smt::with_entries(leaves)?; + + Ok(Self(inner)) + } + + /// Get SMT root. + pub fn root(&self) -> RpoDigest { + self.0.root() + } + + /// Returns an opening of the leaf associated with the given nullifier. + pub fn open( + &self, + nullifier: &Nullifier, + ) -> SmtProof { + self.0.open(&nullifier.inner()) + } + + /// Inserts block number in which nullifier was consumed. + pub fn insert( + &mut self, + nullifier: &Nullifier, + block_num: BlockNumber, + ) -> Result<(), NullifierTreeError> { + let key = nullifier.inner(); + let prev_value = self.0.get_value(&key); + if prev_value != Smt::EMPTY_VALUE { + return Err(NullifierTreeError::NullifierAlreadyExists { + nullifier: *nullifier, + block_num: Self::leaf_value_to_block_num(prev_value), + }); + } + + self.0.insert(key, Self::block_num_to_leaf_value(block_num)); + + Ok(()) + } + + /// Returns block number stored for the given nullifier or `None` if the nullifier wasn't + /// consumed. + pub fn get_block_num( + &self, + nullifier: &Nullifier, + ) -> Option { + let value = self.0.get_value(&nullifier.inner()); + if value == Smt::EMPTY_VALUE { + return None; + } + + Some(Self::leaf_value_to_block_num(value)) + } + + /// Returns the nullifier's leaf value in the SMT by its block number. + fn block_num_to_leaf_value(block: BlockNumber) -> Word { + [Felt::from(block), Felt::ZERO, Felt::ZERO, Felt::ZERO] + } + + /// Given the leaf value of the nullifier SMT, returns the nullifier's block number. + /// + /// There are no nullifiers in the genesis block. The value zero is instead used to signal + /// absence of a value. + fn leaf_value_to_block_num(value: Word) -> BlockNumber { + value[0].as_int().try_into().expect("invalid block number found in store") + } +} + +#[cfg(test)] +mod tests { + use miden_objects::{Felt, ZERO}; + + use super::NullifierTree; + + #[test] + fn test_leaf_value_encoding() { + let block_num = 123; + let nullifier_value = NullifierTree::block_num_to_leaf_value(block_num); + + assert_eq!(nullifier_value, [Felt::from(block_num), ZERO, ZERO, ZERO]) + } + + #[test] + fn test_leaf_value_decoding() { + let block_num = 123; + let nullifier_value = [Felt::from(block_num), ZERO, ZERO, ZERO]; + let decoded_block_num = NullifierTree::leaf_value_to_block_num(nullifier_value); + + assert_eq!(decoded_block_num, block_num); + } +} diff --git a/store/src/server/api.rs b/store/src/server/api.rs index aaaad56a8..dc682bfb4 100644 --- a/store/src/server/api.rs +++ b/store/src/server/api.rs @@ -23,7 +23,7 @@ use miden_node_proto::{ }, AccountState, }; -use miden_objects::{crypto::hash::rpo::RpoDigest, BlockHeader, Felt, ZERO}; +use miden_objects::{notes::Nullifier, BlockHeader, Felt, ZERO}; use tonic::{Response, Status}; use tracing::{debug, info, instrument}; @@ -284,7 +284,7 @@ impl api_server::Api for StoreApi { .nullifiers .into_iter() .map(|nullifier| NullifierTransactionInputRecord { - nullifier: Some(nullifier.nullifier.inner().into()), + nullifier: Some(nullifier.nullifier.into()), block_num: nullifier.block_num, }) .collect(), @@ -388,9 +388,10 @@ fn invalid_argument(err: E) -> Status { } #[instrument(target = "miden-store", skip_all, err)] -fn validate_nullifiers(nullifiers: &[generated::digest::Digest]) -> Result, Status> { +fn validate_nullifiers(nullifiers: &[generated::digest::Digest]) -> Result, Status> { nullifiers .iter() + .cloned() .map(TryInto::try_into) .collect::>() .map_err(|_| invalid_argument("Digest field is not in the modulus range")) diff --git a/store/src/state.rs b/store/src/state.rs index a42b32487..b34a93026 100644 --- a/store/src/state.rs +++ b/store/src/state.rs @@ -9,10 +9,10 @@ use miden_node_utils::formatting::{format_account_id, format_array}; use miden_objects::{ crypto::{ hash::rpo::RpoDigest, - merkle::{LeafIndex, Mmr, MmrDelta, MmrPeaks, SimpleSmt, Smt, SmtProof, ValuePath}, + merkle::{LeafIndex, Mmr, MmrDelta, MmrPeaks, SimpleSmt, SmtProof, ValuePath}, }, - notes::{NoteMetadata, NOTE_LEAF_DEPTH}, - AccountError, BlockHeader, Felt, FieldElement, Word, ACCOUNT_TREE_DEPTH, EMPTY_WORD, + notes::{NoteMetadata, Nullifier, NOTE_LEAF_DEPTH}, + AccountError, BlockHeader, Word, ACCOUNT_TREE_DEPTH, }; use tokio::{ sync::{oneshot, Mutex, RwLock}, @@ -26,6 +26,7 @@ use crate::{ ApplyBlockError, DatabaseError, GetBlockInputsError, StateInitializationError, StateSyncError, }, + nullifier_tree::NullifierTree, types::{AccountId, BlockNumber}, COMPONENT, }; @@ -41,7 +42,7 @@ pub struct TransactionInputs { /// Container for state that needs to be updated atomically. struct InnerState { - nullifier_tree: Smt, + nullifier_tree: NullifierTree, chain_mmr: Mmr, account_tree: SimpleSmt, } @@ -103,7 +104,7 @@ impl State { pub async fn apply_block( &self, block_header: BlockHeader, - nullifiers: Vec, + nullifiers: Vec, accounts: Vec<(AccountId, RpoDigest)>, notes: Vec, ) -> Result<(), ApplyBlockError> { @@ -132,7 +133,7 @@ impl State { // nullifiers can be produced only once let duplicate_nullifiers: Vec<_> = nullifiers .iter() - .filter(|&n| inner.nullifier_tree.get_value(n) != EMPTY_WORD) + .filter(|&n| inner.nullifier_tree.get_block_num(n).is_some()) .cloned() .collect(); if !duplicate_nullifiers.is_empty() { @@ -164,9 +165,10 @@ impl State { // update nullifier tree let nullifier_tree = { let mut nullifier_tree = inner.nullifier_tree.clone(); - let nullifier_data = block_num_to_nullifier_value(block_header.block_num()); for nullifier in nullifiers.iter() { - nullifier_tree.insert(*nullifier, nullifier_data); + nullifier_tree + .insert(nullifier, block_header.block_num()) + .map_err(ApplyBlockError::FailedToUpdateNullifierTree)?; } if nullifier_tree.root() != block_header.nullifier_root() { @@ -296,7 +298,7 @@ impl State { #[instrument(target = "miden-store", skip_all, ret(level = "debug"))] pub async fn check_nullifiers( &self, - nullifiers: &[RpoDigest], + nullifiers: &[Nullifier], ) -> Vec { let inner = self.inner.read().await; nullifiers.iter().map(|n| inner.nullifier_tree.open(n)).collect() @@ -363,7 +365,7 @@ impl State { pub async fn get_block_inputs( &self, account_ids: &[AccountId], - nullifiers: &[RpoDigest], + nullifiers: &[Nullifier], ) -> Result< (BlockHeader, MmrPeaks, Vec, Vec), GetBlockInputsError, @@ -414,7 +416,7 @@ impl State { let proof = inner.nullifier_tree.open(nullifier); NullifierWitness { - nullifier: (*nullifier).into(), + nullifier: *nullifier, proof, } }) @@ -428,7 +430,7 @@ impl State { pub async fn get_transaction_inputs( &self, account_id: AccountId, - nullifiers: &[RpoDigest], + nullifiers: &[Nullifier], ) -> TransactionInputs { info!(target: COMPONENT, account_id = %format_account_id(account_id), nullifiers = %format_array(nullifiers)); @@ -438,15 +440,9 @@ impl State { let nullifiers = nullifiers .iter() - .cloned() - .map(|nullifier| { - let value = inner.nullifier_tree.get_value(&nullifier); - let block_num = nullifier_value_to_block_num(value); - - NullifierInfo { - nullifier: nullifier.into(), - block_num, - } + .map(|nullifier| NullifierInfo { + nullifier: *nullifier, + block_num: inner.nullifier_tree.get_block_num(nullifier).unwrap_or_default(), }) .collect(); @@ -457,7 +453,7 @@ impl State { } /// Lists all known nullifiers with their inclusion blocks, intended for testing. - pub async fn list_nullifiers(&self) -> Result, DatabaseError> { + pub async fn list_nullifiers(&self) -> Result, DatabaseError> { self.db.select_nullifiers().await } @@ -476,19 +472,6 @@ impl State { // UTILITIES // ================================================================================================ -/// Returns the nullifier's leaf value in the SMT by its block number. -fn block_num_to_nullifier_value(block: BlockNumber) -> Word { - [Felt::from(block), Felt::ZERO, Felt::ZERO, Felt::ZERO] -} - -/// Given the leaf value of the nullifier SMT, returns the nullifier's block number. -/// -/// There are no nullifiers in the genesis block. The value zero is instead used to signal absence -/// of a value. -fn nullifier_value_to_block_num(value: Word) -> BlockNumber { - value[0].as_int().try_into().expect("invalid block number found in store") -} - /// Creates a [SimpleSmt] tree from the `notes`. #[instrument(target = "miden-store", skip_all)] pub fn build_notes_tree( @@ -513,16 +496,13 @@ pub fn build_notes_tree( } #[instrument(target = "miden-store", skip_all)] -async fn load_nullifier_tree(db: &mut Db) -> Result { +async fn load_nullifier_tree(db: &mut Db) -> Result { let nullifiers = db.select_nullifiers().await?; let len = nullifiers.len(); - let leaves = nullifiers - .into_iter() - .map(|(nullifier, block)| (nullifier, block_num_to_nullifier_value(block))); let now = Instant::now(); - let nullifier_tree = Smt::with_entries(leaves) - .map_err(StateInitializationError::FailedToCreateNullifiersTree)?; + let nullifier_tree = NullifierTree::with_entries(nullifiers) + .map_err(StateInitializationError::FailedToCreateNullifierTree)?; let elapsed = now.elapsed().as_secs(); info!( @@ -556,27 +536,3 @@ async fn load_accounts( SimpleSmt::with_leaves(account_data) .map_err(StateInitializationError::FailedToCreateAccountsTree) } - -#[cfg(test)] -mod tests { - use miden_objects::{Felt, ZERO}; - - use super::{block_num_to_nullifier_value, nullifier_value_to_block_num}; - - #[test] - fn test_nullifier_data_encoding() { - let block_num = 123; - let nullifier_value = block_num_to_nullifier_value(block_num); - - assert_eq!(nullifier_value, [Felt::from(block_num), ZERO, ZERO, ZERO]) - } - - #[test] - fn test_nullifier_data_decoding() { - let block_num = 123; - let nullifier_value = [Felt::from(block_num), ZERO, ZERO, ZERO]; - let decoded_block_num = nullifier_value_to_block_num(nullifier_value); - - assert_eq!(decoded_block_num, block_num); - } -} From ce6effc742a173f08fc4964cc5a084dded20d630 Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz <42912740+phklive@users.noreply.github.com> Date: Fri, 15 Mar 2024 18:04:17 +0100 Subject: [PATCH 05/19] Separate CI into multiple files (#278) * ci: turn doc warnings into errors (#259) * Separated different CI jobs in their respective files * Add next branch in ci * Fix wrong naming in CI + use cargo make for doc * Fixed badge + removed unnecessary additional file * Change naming of ci and jobs * Fix naming * Fix comment --------- Co-authored-by: Augusto Hack --- .github/workflows/doc.yml | 25 +++++++++++++++ .github/workflows/{ci.yml => lint.yml} | 43 +++----------------------- .github/workflows/test.yml | 26 ++++++++++++++++ README.md | 2 +- miden-node.toml | 21 ------------- 5 files changed, 57 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/doc.yml rename .github/workflows/{ci.yml => lint.yml} (56%) create mode 100644 .github/workflows/test.yml delete mode 100644 miden-node.toml diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml new file mode 100644 index 000000000..a849cc96f --- /dev/null +++ b/.github/workflows/doc.yml @@ -0,0 +1,25 @@ +# Runs documentation related jobs. + +name: doc + +on: + push: + branches: [main, next] + pull_request: + types: [opened, reopened, synchronize] + +jobs: + doc: + name: doc stable on ubuntu-latest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + override: true + - name: Install cargo make + run: cargo install cargo-make + - name: cargo make - doc + run: cargo make doc diff --git a/.github/workflows/ci.yml b/.github/workflows/lint.yml similarity index 56% rename from .github/workflows/ci.yml rename to .github/workflows/lint.yml index af5cc2278..7a4c33576 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/lint.yml @@ -1,11 +1,10 @@ -# Runs the CI +# Runs linting related jobs. -name: CI +name: lint on: push: - branches: - - main + branches: [main, next] pull_request: types: [opened, reopened, synchronize] @@ -35,8 +34,8 @@ jobs: override: true - name: Install cargo make run: cargo install cargo-make - - name: cargo make - format - run: cargo make format + - name: cargo make - format-check + run: cargo make format-check clippy: name: clippy stable on ubuntu-latest @@ -53,35 +52,3 @@ jobs: run: cargo install cargo-make - name: cargo make - clippy run: cargo make clippy - - doc: - name: doc stable on ubuntu-latest - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true - - name: Install cargo make - run: cargo install cargo-make - - name: cargo make - format - run: cargo make doc - - test: - name: test stable on ubuntu-latest - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true - - name: Install cargo make - run: cargo install cargo-make - - name: cargo make - format - run: cargo make test - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..fd2778950 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,26 @@ +# Runs testing related jobs. + +name: test + +on: + push: + branches: [main, next] + pull_request: + types: [opened, reopened, synchronize] + +jobs: + unit-and-integration: + name: test stable on ubuntu-latest + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + override: true + - name: Install cargo make + run: cargo install cargo-make + - name: cargo make - test + run: cargo make test diff --git a/README.md b/README.md index 4dad628e1..405589d94 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Miden node - + This repository holds the Miden node; that is, the software which processes transactions and creates blocks for the Miden rollup. diff --git a/miden-node.toml b/miden-node.toml deleted file mode 100644 index 04f582981..000000000 --- a/miden-node.toml +++ /dev/null @@ -1,21 +0,0 @@ -# This is an example configuration file for the Miden node. - -[block_producer] -# port defined as: sum(ord(c)**p for (p, c) in enumerate('miden-block-producer', 1)) % 2**16 -endpoint = { host = "localhost", port = 48046 } -store_url = "http://localhost:28943" -# enables or disables the verification of transaction proofs before they are accepted into the -# transaction queue. -verify_tx_proofs = true - -[rpc] -# port defined as: sum(ord(c)**p for (p, c) in enumerate('miden-rpc', 1)) % 2**16 -endpoint = { host = "0.0.0.0", port = 57291 } -block_producer_url = "http://localhost:48046" -store_url = "http://localhost:28943" - -[store] -# port defined as: sum(ord(c)**p for (p, c) in enumerate('miden-store', 1)) % 2**16 -endpoint = { host = "localhost", port = 28943 } -database_filepath = "db/miden-store.sqlite3" -genesis_filepath = "genesis.dat" From c7791d04402cf03d5fe1df3d71931e4150c62f82 Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz Date: Sun, 17 Mar 2024 01:08:20 +0100 Subject: [PATCH 06/19] Boilerplate --- .github/workflows/test.yml | 18 +++++++++++++++++- Makefile.toml | 16 ++++++++++++++++ scripts/test-end-to-end.sh | 3 +++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100755 scripts/test-end-to-end.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fd2778950..3bdd88073 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ on: jobs: unit-and-integration: - name: test stable on ubuntu-latest + name: unit and integration tests stable on ubuntu-latest runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -24,3 +24,19 @@ jobs: run: cargo install cargo-make - name: cargo make - test run: cargo make test + + end-to-end: + name: end-to-end tests stable on ubuntu-latest + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + override: true + - name: Install cargo make + run: cargo install cargo-make + - name: cargo make - test-end-to-end + run: cargo make test-end-to-end diff --git a/Makefile.toml b/Makefile.toml index 2266170e7..2f98b4f56 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -48,6 +48,22 @@ dependencies = [ command = "cargo" args = ["test", "--all-features", "--workspace", "--", "--nocapture"] +[tasks.test-end-to-end] +workspace = false +script = ''' +cargo install --features testing --path node + +miden-node make-genesis --inputs-path node/genesis.toml --force + +miden-node start --config node/miden-node.toml & + +sleep 10 + +./scripts/test-end-to-end.sh + +pkill -f miden-node +''' + # docker [tasks.docker-build-node] workspace = false diff --git a/scripts/test-end-to-end.sh b/scripts/test-end-to-end.sh new file mode 100755 index 000000000..89977331f --- /dev/null +++ b/scripts/test-end-to-end.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "hello world" From b2e946a92c297e63570af8fdd1903b31f4fcbece Mon Sep 17 00:00:00 2001 From: polydez <155382956+polydez@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:23:35 +0500 Subject: [PATCH 07/19] refactor: small error refactoring (using `'static str` instead of `String`) (#281) --- block-producer/src/block_builder/prover/mod.rs | 8 ++++---- block-producer/src/errors.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/block-producer/src/block_builder/prover/mod.rs b/block-producer/src/block_builder/prover/mod.rs index 426908e8d..eb3696f53 100644 --- a/block-producer/src/block_builder/prover/mod.rs +++ b/block-producer/src/block_builder/prover/mod.rs @@ -260,22 +260,22 @@ impl BlockProver { let new_account_root = execution_output .stack_outputs() .get_stack_word(ACCOUNT_ROOT_WORD_IDX) - .ok_or(BlockProverError::InvalidRootOutput("account".to_string()))?; + .ok_or(BlockProverError::InvalidRootOutput("account"))?; let new_note_root = execution_output .stack_outputs() .get_stack_word(NOTE_ROOT_WORD_IDX) - .ok_or(BlockProverError::InvalidRootOutput("note".to_string()))?; + .ok_or(BlockProverError::InvalidRootOutput("note"))?; let new_nullifier_root = execution_output .stack_outputs() .get_stack_word(NULLIFIER_ROOT_WORD_IDX) - .ok_or(BlockProverError::InvalidRootOutput("nullifier".to_string()))?; + .ok_or(BlockProverError::InvalidRootOutput("nullifier"))?; let new_chain_mmr_root = execution_output .stack_outputs() .get_stack_word(CHAIN_MMR_ROOT_WORD_IDX) - .ok_or(BlockProverError::InvalidRootOutput("chain mmr".to_string()))?; + .ok_or(BlockProverError::InvalidRootOutput("chain mmr"))?; Ok(( new_account_root.into(), diff --git a/block-producer/src/errors.rs b/block-producer/src/errors.rs index f7635495c..a597714a1 100644 --- a/block-producer/src/errors.rs +++ b/block-producer/src/errors.rs @@ -91,7 +91,7 @@ pub enum BlockProverError { #[error("program execution failed")] ProgramExecutionFailed(ExecutionError), #[error("failed to retrieve {0} root from stack outputs")] - InvalidRootOutput(String), + InvalidRootOutput(&'static str), } // Block inputs errors From e26d69ce47b19321511b1b46c9e6ccefe98aa0fb Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz Date: Wed, 20 Mar 2024 13:39:00 +0100 Subject: [PATCH 08/19] Added end to end workflow --- .github/workflows/end.yml | 30 ++++++++++++++++++++++++++++++ .github/workflows/test.yml | 34 +++++++++++++++++++--------------- Makefile.toml | 10 ---------- 3 files changed, 49 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/end.yml diff --git a/.github/workflows/end.yml b/.github/workflows/end.yml new file mode 100644 index 000000000..3166d0583 --- /dev/null +++ b/.github/workflows/end.yml @@ -0,0 +1,30 @@ +# Runs end-to-end testing related jobs. + +name: end-to-end + +on: + push: + branches: [main, next] + pull_request: + types: [opened, reopened, synchronize] + +jobs: + end-to-end: + name: end-to-end tests stable on ubuntu-latest + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + override: true + - name: Install cargo make + run: cargo install cargo-make + - name: cargo make - docker-build-node + run: cargo make docker-build-node + - name: cargo make - docker-run-node + run: cargo make docker-run-node + - name: cargo make - test-end-to-end + run: cargo make test-end-to-end diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3bdd88073..c068ce6ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,18 +25,22 @@ jobs: - name: cargo make - test run: cargo make test - end-to-end: - name: end-to-end tests stable on ubuntu-latest - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true - - name: Install cargo make - run: cargo install cargo-make - - name: cargo make - test-end-to-end - run: cargo make test-end-to-end + # end-to-end: + # name: end-to-end tests stable on ubuntu-latest + # runs-on: ubuntu-latest + # timeout-minutes: 30 + # steps: + # - uses: actions/checkout@v4 + # - name: Install Rust + # uses: actions-rs/toolchain@v1 + # with: + # profile: minimal + # override: true + # - name: Install cargo make + # run: cargo install cargo-make + # - name: cargo make - docker-build-node + # run: cargo make docker-build-node + # - name: cargo make - docker-run-node + # run: cargo make docker-run-node + # - name: cargo make - test-end-to-end + # run: cargo make test-end-to-end diff --git a/Makefile.toml b/Makefile.toml index 2f98b4f56..5d1f5e229 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -51,17 +51,7 @@ args = ["test", "--all-features", "--workspace", "--", "--nocapture"] [tasks.test-end-to-end] workspace = false script = ''' -cargo install --features testing --path node - -miden-node make-genesis --inputs-path node/genesis.toml --force - -miden-node start --config node/miden-node.toml & - -sleep 10 - ./scripts/test-end-to-end.sh - -pkill -f miden-node ''' # docker From 20ab098a6c86db8beff5a4b6f6a655a5b86dffcd Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz Date: Wed, 20 Mar 2024 20:25:49 +0100 Subject: [PATCH 09/19] First end to end test --- .github/workflows/end.yml | 2 ++ scripts/test-end-to-end.sh | 44 +++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/.github/workflows/end.yml b/.github/workflows/end.yml index 3166d0583..550858837 100644 --- a/.github/workflows/end.yml +++ b/.github/workflows/end.yml @@ -26,5 +26,7 @@ jobs: run: cargo make docker-build-node - name: cargo make - docker-run-node run: cargo make docker-run-node + - name: cargo install - miden-client + run: cargo install miden-client --features concurrent,testing - name: cargo make - test-end-to-end run: cargo make test-end-to-end diff --git a/scripts/test-end-to-end.sh b/scripts/test-end-to-end.sh index 89977331f..8e376c4ff 100755 --- a/scripts/test-end-to-end.sh +++ b/scripts/test-end-to-end.sh @@ -1,3 +1,45 @@ #!/bin/bash -echo "hello world" +# Setup clean state +rm store.sqlite3 + +# Create miden-client.toml file +{ + echo "[rpc]" + echo "endpoint = { protocol = \"http\", host = \"localhost\", port = 57291 }" + echo "" + echo "[store]" + echo "database_filepath = \"store.sqlite3\"" +} > miden-client.toml + +# Test flow 1 + +# Create accounts +miden-client account new basic-immutable +miden-client account new fungible-faucet --token-symbol JACK --decimals 3 --max-supply 1000 + +# Capture IDs +basic_account=$(miden-client account -l | grep -oE ' 0x[a-fA-F0-9]{16} ' | head -n 1) +faucet_account=$(miden-client account -l | grep -oE ' 0x[a-fA-F0-9]{16} ' | head -n 2 | tail -n 1) + +# Sync client and create tx between faucet and account +miden-client sync +miden-client tx new mint $basic_account $faucet_account 777 +sleep 15 +miden-client sync + +# Extract note ID +note_id=$(miden-client input-notes -l | grep -oE ' 0x[a-fA-F0-9]{64} ' | head -n 1) + +# Basic account consumes faucet generated note +miden-client tx new consume-notes $basic_account $note_id + +# Check that the funds have been received successfully by the account +if miden-client account show $basic_account -v | grep -q ' 777 '; then + echo "Funds successfully received." +else + echo "Failed to receive funds from faucet" + exit 1 +fi + +echo "Test flow 1 - success ✅" From 78d4cab82a5b66a8a5c3ddbdffb77961c5dcaa64 Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz Date: Wed, 20 Mar 2024 20:37:19 +0100 Subject: [PATCH 10/19] Cleanup + remove rm store --- .github/workflows/end.yml | 32 ------------------------------ .github/workflows/test.yml | 40 ++++++++++++++++++++------------------ scripts/test-end-to-end.sh | 3 --- 3 files changed, 21 insertions(+), 54 deletions(-) delete mode 100644 .github/workflows/end.yml diff --git a/.github/workflows/end.yml b/.github/workflows/end.yml deleted file mode 100644 index 550858837..000000000 --- a/.github/workflows/end.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Runs end-to-end testing related jobs. - -name: end-to-end - -on: - push: - branches: [main, next] - pull_request: - types: [opened, reopened, synchronize] - -jobs: - end-to-end: - name: end-to-end tests stable on ubuntu-latest - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true - - name: Install cargo make - run: cargo install cargo-make - - name: cargo make - docker-build-node - run: cargo make docker-build-node - - name: cargo make - docker-run-node - run: cargo make docker-run-node - - name: cargo install - miden-client - run: cargo install miden-client --features concurrent,testing - - name: cargo make - test-end-to-end - run: cargo make test-end-to-end diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c068ce6ed..d891d8be7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,22 +25,24 @@ jobs: - name: cargo make - test run: cargo make test - # end-to-end: - # name: end-to-end tests stable on ubuntu-latest - # runs-on: ubuntu-latest - # timeout-minutes: 30 - # steps: - # - uses: actions/checkout@v4 - # - name: Install Rust - # uses: actions-rs/toolchain@v1 - # with: - # profile: minimal - # override: true - # - name: Install cargo make - # run: cargo install cargo-make - # - name: cargo make - docker-build-node - # run: cargo make docker-build-node - # - name: cargo make - docker-run-node - # run: cargo make docker-run-node - # - name: cargo make - test-end-to-end - # run: cargo make test-end-to-end + end-to-end: + name: end-to-end tests stable on ubuntu-latest + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + override: true + - name: Install cargo make + run: cargo install cargo-make + - name: cargo make - docker-build-node + run: cargo make docker-build-node + - name: cargo make - docker-run-node + run: cargo make docker-run-node + - name: cargo install - miden-client + run: cargo install miden-client --features concurrent,testing + - name: cargo make - test-end-to-end + run: cargo make test-end-to-end diff --git a/scripts/test-end-to-end.sh b/scripts/test-end-to-end.sh index 8e376c4ff..0a8be6a64 100755 --- a/scripts/test-end-to-end.sh +++ b/scripts/test-end-to-end.sh @@ -1,8 +1,5 @@ #!/bin/bash -# Setup clean state -rm store.sqlite3 - # Create miden-client.toml file { echo "[rpc]" From efbd3f4d373d565fff801d9c948d24fc1b3d241a Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz Date: Wed, 20 Mar 2024 20:39:26 +0100 Subject: [PATCH 11/19] fix gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d03086c8a..7870245be 100644 --- a/.gitignore +++ b/.gitignore @@ -17,10 +17,11 @@ target/ .vscode/ # Genesis files -/accounts +accounts/ genesis.dat # Sqlite db files *.sqlite3 *.sqlite3-shm *.sqlite3-wal +db/ From fd1f40db84e47c3e6644dbb175e39ba7e14e5dbb Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz Date: Thu, 21 Mar 2024 11:51:42 +0100 Subject: [PATCH 12/19] add comments --- scripts/test-end-to-end.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test-end-to-end.sh b/scripts/test-end-to-end.sh index 0a8be6a64..7f5a71acb 100755 --- a/scripts/test-end-to-end.sh +++ b/scripts/test-end-to-end.sh @@ -1,6 +1,7 @@ #!/bin/bash # Create miden-client.toml file +# Needed for the Miden client to work properly { echo "[rpc]" echo "endpoint = { protocol = \"http\", host = \"localhost\", port = 57291 }" From dc88ee1e6f1d7217980211e23723a46355276121 Mon Sep 17 00:00:00 2001 From: Augusto Hack Date: Thu, 21 Mar 2024 13:43:57 +0100 Subject: [PATCH 13/19] bugfix: table name is block_headers instead of block_header (#283) --- store/src/db/migrations.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/store/src/db/migrations.rs b/store/src/db/migrations.rs index d98be6422..a4faa9a7e 100644 --- a/store/src/db/migrations.rs +++ b/store/src/db/migrations.rs @@ -27,7 +27,7 @@ pub static MIGRATIONS: Lazy = Lazy::new(|| { PRIMARY KEY (block_num, note_index), CONSTRAINT notes_block_number_is_u32 CHECK (block_num >= 0 AND block_num < 4294967296), CONSTRAINT notes_note_index_is_u32 CHECK (note_index >= 0 AND note_index < 4294967296), - FOREIGN KEY (block_num) REFERENCES block_header (block_num) + FOREIGN KEY (block_num) REFERENCES block_headers (block_num) ) STRICT, WITHOUT ROWID; CREATE TABLE @@ -38,7 +38,7 @@ pub static MIGRATIONS: Lazy = Lazy::new(|| { block_num INTEGER NOT NULL, PRIMARY KEY (account_id), - FOREIGN KEY (block_num) REFERENCES block_header (block_num), + FOREIGN KEY (block_num) REFERENCES block_headers (block_num), CONSTRAINT accounts_block_num_is_u32 CHECK (block_num >= 0 AND block_num < 4294967296) ) STRICT, WITHOUT ROWID; @@ -53,7 +53,7 @@ pub static MIGRATIONS: Lazy = Lazy::new(|| { CONSTRAINT nullifiers_nullifier_is_digest CHECK (length(nullifier) = 32), CONSTRAINT nullifiers_nullifier_prefix_is_u16 CHECK (nullifier_prefix >= 0 AND nullifier_prefix < 65536), CONSTRAINT nullifiers_block_number_is_u32 CHECK (block_number >= 0 AND block_number < 4294967296), - FOREIGN KEY (block_number) REFERENCES block_header (block_num) + FOREIGN KEY (block_number) REFERENCES block_headers (block_num) ) STRICT, WITHOUT ROWID; ", )]) From 9975495d4acf408a1596a610d690af9372041192 Mon Sep 17 00:00:00 2001 From: Augusto Hack Date: Thu, 21 Mar 2024 20:31:58 +0100 Subject: [PATCH 14/19] sqlite: use bundled version to enable FK checks (#284) --- Cargo.lock | 1 + store/Cargo.toml | 2 +- store/src/db/migrations.rs | 12 ++++---- store/src/db/tests.rs | 61 +++++++++++++++++++++++++++++++------- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91dd8340b..8ca119d6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -979,6 +979,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" dependencies = [ "bindgen", + "cc", "pkg-config", "vcpkg", ] diff --git a/store/Cargo.toml b/store/Cargo.toml index a485300df..db5f8b0a3 100644 --- a/store/Cargo.toml +++ b/store/Cargo.toml @@ -30,7 +30,7 @@ miden-node-utils = { path = "../utils", version = "0.2" } miden-objects = { workspace = true } once_cell = { version = "1.18.0" } prost = { version = "0.12" } -rusqlite = { version = "0.30", features = ["array", "buildtime_bindgen"] } +rusqlite = { version = "0.30", features = ["array", "buildtime_bindgen", "bundled"] } rusqlite_migration = { version = "1.0" } serde = { version = "1.0", features = ["derive"] } thiserror = { workspace = true } diff --git a/store/src/db/migrations.rs b/store/src/db/migrations.rs index a4faa9a7e..3eec2bc0b 100644 --- a/store/src/db/migrations.rs +++ b/store/src/db/migrations.rs @@ -25,9 +25,9 @@ pub static MIGRATIONS: Lazy = Lazy::new(|| { merkle_path BLOB NOT NULL, PRIMARY KEY (block_num, note_index), + CONSTRAINT fk_block_num FOREIGN KEY (block_num) REFERENCES block_headers (block_num), CONSTRAINT notes_block_number_is_u32 CHECK (block_num >= 0 AND block_num < 4294967296), - CONSTRAINT notes_note_index_is_u32 CHECK (note_index >= 0 AND note_index < 4294967296), - FOREIGN KEY (block_num) REFERENCES block_headers (block_num) + CONSTRAINT notes_note_index_is_u32 CHECK (note_index >= 0 AND note_index < 4294967296) ) STRICT, WITHOUT ROWID; CREATE TABLE @@ -38,7 +38,7 @@ pub static MIGRATIONS: Lazy = Lazy::new(|| { block_num INTEGER NOT NULL, PRIMARY KEY (account_id), - FOREIGN KEY (block_num) REFERENCES block_headers (block_num), + CONSTRAINT fk_block_num FOREIGN KEY (block_num) REFERENCES block_headers (block_num), CONSTRAINT accounts_block_num_is_u32 CHECK (block_num >= 0 AND block_num < 4294967296) ) STRICT, WITHOUT ROWID; @@ -50,10 +50,10 @@ pub static MIGRATIONS: Lazy = Lazy::new(|| { block_number INTEGER NOT NULL, PRIMARY KEY (nullifier), + CONSTRAINT fk_block_num FOREIGN KEY (block_number) REFERENCES block_headers (block_num), CONSTRAINT nullifiers_nullifier_is_digest CHECK (length(nullifier) = 32), CONSTRAINT nullifiers_nullifier_prefix_is_u16 CHECK (nullifier_prefix >= 0 AND nullifier_prefix < 65536), - CONSTRAINT nullifiers_block_number_is_u32 CHECK (block_number >= 0 AND block_number < 4294967296), - FOREIGN KEY (block_number) REFERENCES block_headers (block_num) + CONSTRAINT nullifiers_block_number_is_u32 CHECK (block_number >= 0 AND block_number < 4294967296) ) STRICT, WITHOUT ROWID; ", )]) @@ -61,5 +61,5 @@ pub static MIGRATIONS: Lazy = Lazy::new(|| { #[test] fn migrations_test() { - assert!(MIGRATIONS.validate().is_ok()); + assert_eq!(MIGRATIONS.validate(), Ok(())); } diff --git a/store/src/db/tests.rs b/store/src/db/tests.rs index 7abcf5da9..57af54176 100644 --- a/store/src/db/tests.rs +++ b/store/src/db/tests.rs @@ -18,12 +18,36 @@ fn create_db() -> Connection { conn } +fn create_block( + conn: &mut Connection, + block_num: u32, +) { + let block_header = BlockHeader::new( + num_to_rpo_digest(1), + block_num, + num_to_rpo_digest(3), + num_to_rpo_digest(4), + num_to_rpo_digest(5), + num_to_rpo_digest(6), + num_to_rpo_digest(7), + num_to_rpo_digest(8), + 9_u8.into(), + 10_u8.into(), + ); + + let transaction = conn.transaction().unwrap(); + sql::insert_block_header(&transaction, &block_header).unwrap(); + transaction.commit().unwrap(); +} + #[test] fn test_sql_insert_nullifiers_for_block() { let mut conn = create_db(); let nullifiers = [num_to_nullifier(1 << 48)]; + let block_num = 1; + create_block(&mut conn, block_num); // Insert a new nullifier succeeds { @@ -66,12 +90,14 @@ fn test_sql_insert_nullifiers_for_block() { fn test_sql_select_nullifiers() { let mut conn = create_db(); + let block_num = 1; + create_block(&mut conn, block_num); + // test querying empty table let nullifiers = sql::select_nullifiers(&mut conn).unwrap(); assert!(nullifiers.is_empty()); // test multiple entries - let block_num = 1; let mut state = vec![]; for i in 0..10 { let nullifier = num_to_nullifier(i); @@ -90,12 +116,14 @@ fn test_sql_select_nullifiers() { fn test_sql_select_notes() { let mut conn = create_db(); + let block_num = 1; + create_block(&mut conn, block_num); + // test querying empty table let notes = sql::select_notes(&mut conn).unwrap(); assert!(notes.is_empty()); // test multiple entries - let block_num = 1; let mut state = vec![]; for i in 0..10 { let note = Note { @@ -123,12 +151,14 @@ fn test_sql_select_notes() { fn test_sql_select_accounts() { let mut conn = create_db(); + let block_num = 1; + create_block(&mut conn, block_num); + // test querying empty table let accounts = sql::select_accounts(&mut conn).unwrap(); assert!(accounts.is_empty()); // test multiple entries - let block_num = 1; let mut state = vec![]; for i in 0..10 { let account_id = i; @@ -159,6 +189,7 @@ fn test_sql_select_nullifiers_by_block_range() { // test single item let nullifier1 = num_to_nullifier(1 << 48); let block_number1 = 1; + create_block(&mut conn, block_number1); let transaction = conn.transaction().unwrap(); sql::insert_nullifiers_for_block(&transaction, &[nullifier1], block_number1).unwrap(); @@ -182,6 +213,7 @@ fn test_sql_select_nullifiers_by_block_range() { // test two elements let nullifier2 = num_to_nullifier(2 << 48); let block_number2 = 2; + create_block(&mut conn, block_number2); let transaction = conn.transaction().unwrap(); sql::insert_nullifiers_for_block(&transaction, &[nullifier2], block_number2).unwrap(); @@ -339,13 +371,15 @@ fn test_db_block_header() { fn test_db_account() { let mut conn = create_db(); + let block_num = 1; + create_block(&mut conn, block_num); + // test empty table let account_ids = vec![0, 1, 2, 3, 4, 5]; let res = sql::select_accounts_by_block_range(&mut conn, 0, u32::MAX, &account_ids).unwrap(); assert!(res.is_empty()); // test insertion - let block_num = 1; let account_id = 0; let account_hash = num_to_rpo_digest(0); @@ -382,6 +416,9 @@ fn test_db_account() { fn test_notes() { let mut conn = create_db(); + let block_num_1 = 1; + create_block(&mut conn, block_num_1); + // test empty table let res = sql::select_notes_since_block_by_tag_and_sender(&mut conn, &[], &[], 0).unwrap(); assert!(res.is_empty()); @@ -391,7 +428,6 @@ fn test_notes() { assert!(res.is_empty()); // test insertion - let block_num = 1; let note_index = 2u32; let tag = 5; let note_hash = num_to_rpo_digest(3); @@ -401,7 +437,7 @@ fn test_notes() { let merkle_path = MerklePath::new(notes_db.open(&idx).path.nodes().to_vec()); let note = Note { - block_num, + block_num: block_num_1, note_created: NoteCreated { note_index, note_id: num_to_rpo_digest(3), @@ -424,7 +460,7 @@ fn test_notes() { &mut conn, &[(tag >> 48) as u32], &[], - block_num, + block_num_1, ) .unwrap(); assert!(res.is_empty()); @@ -434,14 +470,17 @@ fn test_notes() { &mut conn, &[(tag >> 48) as u32], &[], - block_num - 1, + block_num_1 - 1, ) .unwrap(); assert_eq!(res, vec![note.clone()]); + let block_num_2 = note.block_num + 1; + create_block(&mut conn, block_num_2); + // insertion second note with same tag, but on higher block let note2 = Note { - block_num: note.block_num + 1, + block_num: block_num_2, note_created: NoteCreated { note_index: note.note_created.note_index, note_id: num_to_rpo_digest(3), @@ -460,7 +499,7 @@ fn test_notes() { &mut conn, &[(tag >> 48) as u32], &[], - block_num - 1, + block_num_1 - 1, ) .unwrap(); assert_eq!(res, vec![note.clone()]); @@ -470,7 +509,7 @@ fn test_notes() { &mut conn, &[(tag >> 48) as u32], &[], - block_num, + block_num_1, ) .unwrap(); assert_eq!(res, vec![note2.clone()]); From 98f704ecc6f90d48edbe856abf4bc076abd2ce34 Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz Date: Mon, 25 Mar 2024 13:10:42 +0100 Subject: [PATCH 15/19] cargo update --- Cargo.lock | 183 +++++++++++++++++++++++++++-------------------------- 1 file changed, 94 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ca119d6d..1934c94c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -109,9 +109,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "arrayref" @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", @@ -220,9 +220,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -260,7 +260,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -297,15 +297,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec", @@ -331,15 +331,15 @@ checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" @@ -393,9 +393,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.2" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" dependencies = [ "clap_builder", "clap_derive", @@ -415,11 +415,11 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn", @@ -576,15 +576,15 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "figment" -version = "0.10.14" +version = "0.10.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6e5bc7bd59d60d0d45a6ccab6cf0f4ce28698fb4e81e750ddf229c9b824026" +checksum = "7270677e7067213e04f323b55084586195f18308cd7546cfac9f873344ccceb6" dependencies = [ "atomic", "parking_lot", @@ -682,9 +682,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" dependencies = [ "bytes", "fnv", @@ -692,7 +692,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.5", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -730,6 +730,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -856,9 +862,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -967,7 +973,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", "redox_syscall", ] @@ -1094,14 +1100,13 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f42129adccef0debcdfcadb1a34323b765fe5895303a71bad820cc67ad2a1db7" +checksum = "a186bed6fd782e0af049164f7e3b565aabf30227ce75e3405550930eebf14627" dependencies = [ "blake3", "cc", "glob", - "libc", "winter-crypto", "winter-math", "winter-utils", @@ -1516,9 +1521,9 @@ dependencies = [ [[package]] name = "pear" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ccca0f6c17acc81df8e242ed473ec144cbf5c98037e69aa6d144780aad103c8" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" dependencies = [ "inlinable_string", "pear_codegen", @@ -1527,9 +1532,9 @@ dependencies = [ [[package]] name = "pear_codegen" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e22670e8eb757cff11d6c199ca7b987f352f0346e0be4dd23869ec72cb53c77" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", @@ -1550,7 +1555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.2.5", + "indexmap 2.2.6", ] [[package]] @@ -1599,9 +1604,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2", "syn", @@ -1618,9 +1623,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -1646,7 +1651,7 @@ checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.4.2", + "bitflags 2.5.0", "lazy_static", "num-traits", "rand", @@ -1675,7 +1680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" dependencies = [ "bytes", - "heck", + "heck 0.4.1", "itertools 0.11.0", "log", "multimap", @@ -1828,9 +1833,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -1876,7 +1881,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1908,11 +1913,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -2025,9 +2030,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smawk" @@ -2074,9 +2079,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.52" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", @@ -2124,18 +2129,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", @@ -2192,9 +2197,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -2217,14 +2222,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.7", + "toml_edit 0.22.9", ] [[package]] @@ -2242,18 +2247,18 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.7" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -2761,9 +2766,9 @@ dependencies = [ [[package]] name = "winter-air" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89fcd28faa34e8e4d1e03ee60b6ac6629cc01b344093cb3a4d45d84ef9bb3832" +checksum = "07390d3217bdd6410c1ef43f3d06510d3a424e7259b371fccbc7cd79a9c00a15" dependencies = [ "libm", "winter-crypto", @@ -2774,9 +2779,9 @@ dependencies = [ [[package]] name = "winter-crypto" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "118a3228b6b2424df48d25941a85ad4b7a69fe877d31d8b5ed57935720ebd152" +checksum = "6aea508aa819e934c837f24bb706e69d890b9be2db82da39cde887e6f0a37246" dependencies = [ "blake3", "sha3", @@ -2786,9 +2791,9 @@ dependencies = [ [[package]] name = "winter-fri" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644b37fc9093fec78c272f3f594dd205ec21dd35d5722001b1d24383693bb6c6" +checksum = "660f47c5c9f5872940ac07a724b1df426590dcffad26776e0528466f2e3095f8" dependencies = [ "winter-crypto", "winter-math", @@ -2797,18 +2802,18 @@ dependencies = [ [[package]] name = "winter-math" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0dff7a27296595fbdc653751b3718acd9518288df646e860ecb48915ff0d6c3" +checksum = "a0c91111b368b08c5a76009514e9b6d26af41fbb28604ea77a249282323b64d5" dependencies = [ "winter-utils", ] [[package]] name = "winter-prover" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ec0e854d35d5d35ae778ea2e5178f81507806071294104f3e1a807beb2554c" +checksum = "170c1ef487df609625580156ea0350c500aeabb3f429dc88cfe800c4b7893edf" dependencies = [ "tracing", "winter-air", @@ -2820,9 +2825,9 @@ dependencies = [ [[package]] name = "winter-rand-utils" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "042a5754bc7bbd2d0741f6658885c7567a4fbab52ad2efec9b797a82bfca7300" +checksum = "8b19ce50e688442052e957a69d72b8057d72ae8f03a7aea7c2538e11c76b2583" dependencies = [ "rand", "winter-utils", @@ -2830,15 +2835,15 @@ dependencies = [ [[package]] name = "winter-utils" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefd8f1108fd48c2516c316e0b42b78e7070e116d1373322bf58993c68cf036" +checksum = "ab6efccf6efa6fd0a80784f3894bc372ada67cc30d9c017fc907d4c0cdce86e7" [[package]] name = "winter-verifier" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4135f28670385a66d2c80943d7998d383660ff63f8c9f544555d9849eb621ba0" +checksum = "6f817a425ca4aa7acefb356601798d0b86b9c0e383b397af69e7e5e97f5b4008" dependencies = [ "winter-air", "winter-crypto", @@ -2849,9 +2854,9 @@ dependencies = [ [[package]] name = "winterfell" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a4f653cb8281d965ec77b87354f1a87da3bc0e15b3e40262a1e64fa93a947b3" +checksum = "be0a6acdc1fd126ba950b0e5bfaafd9f294fc157b285f35541603164054e9358" dependencies = [ "winter-prover", "winter-verifier", @@ -2859,9 +2864,9 @@ dependencies = [ [[package]] name = "yansi" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2861d76f58ec8fc95708b9b1e417f7b12fd72ad33c01fa6886707092dea0d3" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" From 121c86c2f516b92e6d731d94f3a9c23a61c3f708 Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Mon, 25 Mar 2024 14:03:57 -0300 Subject: [PATCH 16/19] chore: update CI action for rust install (#280) --- .github/workflows/doc.yml | 5 +---- .github/workflows/lint.yml | 9 +++------ .github/workflows/test.yml | 5 +---- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index a849cc96f..4fe3d9eb4 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -15,10 +15,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true + uses: dtolnay/rust-toolchain@stable - name: Install cargo make run: cargo install cargo-make - name: cargo make - doc diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7a4c33576..752e5985e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -26,12 +26,10 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install minimal Rust with rustfmt - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: nightly components: rustfmt - override: true - name: Install cargo make run: cargo install cargo-make - name: cargo make - format-check @@ -43,11 +41,10 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install minimal Rust with clippy - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - profile: minimal + toolchain: stable components: clippy - override: true - name: Install cargo make run: cargo install cargo-make - name: cargo make - clippy diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fd2778950..625bd9487 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,10 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true + uses: dtolnay/rust-toolchain@stable - name: Install cargo make run: cargo install cargo-make - name: cargo make - test From d8edb09f834673ee93d393bbf83b31758ecd3a8c Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz <42912740+phklive@users.noreply.github.com> Date: Fri, 29 Mar 2024 10:55:46 +0100 Subject: [PATCH 17/19] Add faucet enabling testing (#270) * ci: turn doc warnings into errors (#259) * Boilerplate done * pushed fix for miden-client * Fix formatting * Pulled after client fix * Fixed typos + added build_client fn * Added configs + figment * Fix client implementation in faucet + use released client * updated cargo * Fixed Miden node when importing Miden client, db errors * Format files * Added proper support of configuration file + logging * Improved config file handling * Removed superfluous file * First pass at improvements * Change naming of note * Fixed html + added metadata endpoint * Want to implement Display for NoteId * Upgraded js to use async + NoteId does not impl Display in main * cargo updated --------- Co-authored-by: Augusto Hack --- .gitignore | 1 + Cargo.lock | 1060 ++++++++++++++++++++++++++---- Cargo.toml | 1 + faucet/Cargo.toml | 27 + faucet/miden-faucet.toml | 3 + faucet/src/cli.rs | 54 ++ faucet/src/config.rs | 37 ++ faucet/src/errors.rs | 32 + faucet/src/handlers.rs | 102 +++ faucet/src/main.rs | 140 ++++ faucet/src/static/background.png | Bin 0 -> 165941 bytes faucet/src/static/favicon.ico | Bin 0 -> 1150 bytes faucet/src/static/index.css | 90 +++ faucet/src/static/index.html | 25 + faucet/src/static/index.js | 70 ++ faucet/src/utils.rs | 102 +++ 16 files changed, 1608 insertions(+), 136 deletions(-) create mode 100644 faucet/Cargo.toml create mode 100644 faucet/miden-faucet.toml create mode 100644 faucet/src/cli.rs create mode 100644 faucet/src/config.rs create mode 100644 faucet/src/errors.rs create mode 100644 faucet/src/handlers.rs create mode 100644 faucet/src/main.rs create mode 100644 faucet/src/static/background.png create mode 100644 faucet/src/static/favicon.ico create mode 100644 faucet/src/static/index.css create mode 100644 faucet/src/static/index.html create mode 100644 faucet/src/static/index.js create mode 100644 faucet/src/utils.rs diff --git a/.gitignore b/.gitignore index d03086c8a..e92749cc0 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ genesis.dat *.sqlite3 *.sqlite3-shm *.sqlite3-wal +db/ diff --git a/Cargo.lock b/Cargo.lock index 8ca119d6d..e873dd380 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,223 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.5.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-cors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more", + "futures-util", + "log", + "once_cell", + "smallvec", +] + +[[package]] +name = "actix-files" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0bdd6ff79de7c9a021f5d9ea79ce23e108d8bfc9b49b5b4a2cf6fad5a35212" +dependencies = [ + "actix-http", + "actix-service", + "actix-utils", + "actix-web", + "bitflags 2.5.0", + "bytes", + "derive_more", + "futures-core", + "http-range", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "v_htmlescape", +] + +[[package]] +name = "actix-http" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d223b13fd481fc0d1f83bb12659ae774d9e3601814c68a0bc539731698cca743" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64", + "bitflags 2.5.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.55", +] + +[[package]] +name = "actix-router" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22475596539443685426b6bdadb926ad0ecaefdfc5fb05e5e3441f15463c511" +dependencies = [ + "bytestring", + "http", + "regex", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a6556ddebb638c2358714d853257ed226ece6023ef9364f23f0c70737ea984" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.55", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -24,6 +241,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -31,13 +249,28 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.16" @@ -109,9 +342,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "arrayref" @@ -125,6 +358,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -144,18 +386,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -169,9 +411,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "axum" @@ -220,9 +462,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -260,7 +502,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -271,7 +513,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.55", ] [[package]] @@ -297,15 +539,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec", @@ -323,6 +565,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.15.4" @@ -331,15 +594,24 @@ checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "bytestring" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" +dependencies = [ + "bytes", +] [[package]] name = "cc" @@ -368,9 +640,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -393,9 +665,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.2" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -415,14 +687,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -437,12 +709,41 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "comfy-table" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" +dependencies = [ + "crossterm", + "strum", + "strum_macros", + "unicode-width", +] + [[package]] name = "constant_time_eq" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -458,6 +759,62 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.5.0", + "crossterm_winapi", + "libc", + "parking_lot", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -509,6 +866,28 @@ dependencies = [ "deadpool-runtime", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + [[package]] name = "digest" version = "0.10.7" @@ -546,6 +925,15 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -562,6 +950,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -576,15 +970,15 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "figment" -version = "0.10.14" +version = "0.10.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6e5bc7bd59d60d0d45a6ccab6cf0f4ce28698fb4e81e750ddf229c9b824026" +checksum = "7270677e7067213e04f323b55084586195f18308cd7546cfac9f873344ccceb6" dependencies = [ "atomic", "parking_lot", @@ -602,12 +996,31 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -645,6 +1058,7 @@ dependencies = [ "futures-task", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -682,9 +1096,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" dependencies = [ "bytes", "fnv", @@ -692,7 +1106,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.5", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -730,6 +1144,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -773,6 +1193,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + [[package]] name = "httparse" version = "1.8.0" @@ -844,6 +1270,16 @@ dependencies = [ "cc", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -856,9 +1292,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -896,9 +1332,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" @@ -927,6 +1363,12 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.4.0" @@ -967,7 +1409,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", "redox_syscall", ] @@ -990,6 +1432,23 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.11" @@ -1026,7 +1485,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.6.29", - "syn", + "syn 2.0.55", ] [[package]] @@ -1055,9 +1514,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "miden-air" @@ -1081,6 +1540,32 @@ dependencies = [ "tracing", ] +[[package]] +name = "miden-client" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cd9db9681b986edd6775559ebb79f90fb124af68dc9ccc69f3f2a88d61b960" +dependencies = [ + "async-trait", + "clap", + "comfy-table", + "figment", + "lazy_static", + "miden-lib", + "miden-node-proto 0.1.0", + "miden-objects", + "miden-tx", + "rand", + "rusqlite", + "rusqlite_migration", + "serde", + "serde_json", + "tokio", + "tonic", + "tracing", + "tracing-subscriber", +] + [[package]] name = "miden-core" version = "0.8.0" @@ -1094,14 +1579,14 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f42129adccef0debcdfcadb1a34323b765fe5895303a71bad820cc67ad2a1db7" +checksum = "a186bed6fd782e0af049164f7e3b565aabf30227ce75e3405550930eebf14627" dependencies = [ "blake3", "cc", "glob", - "libc", + "serde", "winter-crypto", "winter-math", "winter-utils", @@ -1129,7 +1614,7 @@ dependencies = [ "miden-node-block-producer", "miden-node-rpc", "miden-node-store", - "miden-node-utils", + "miden-node-utils 0.2.0", "miden-objects", "serde", "tokio", @@ -1147,10 +1632,10 @@ dependencies = [ "figment", "itertools 0.12.1", "miden-air", - "miden-node-proto", + "miden-node-proto 0.2.0", "miden-node-store", "miden-node-test-macro", - "miden-node-utils", + "miden-node-utils 0.2.0", "miden-objects", "miden-processor", "miden-stdlib", @@ -1166,12 +1651,51 @@ dependencies = [ "winterfell", ] +[[package]] +name = "miden-node-faucet" +version = "0.1.0" +dependencies = [ + "actix-cors", + "actix-files", + "actix-web", + "async-mutex", + "clap", + "derive_more", + "figment", + "miden-client", + "miden-lib", + "miden-node-proto 0.2.0", + "miden-node-utils 0.2.0", + "miden-objects", + "serde", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "miden-node-proto" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdcb2af46af307aaf5d80e940200c08e86d7d7491854f5a9cc33bb3ada7e757" +dependencies = [ + "hex", + "miden-node-utils 0.1.0", + "miden-objects", + "miette", + "prost", + "prost-build", + "protox", + "thiserror", + "tonic", + "tonic-build", +] + [[package]] name = "miden-node-proto" version = "0.2.0" dependencies = [ "hex", - "miden-node-utils", + "miden-node-utils 0.2.0", "miden-objects", "miette", "proptest", @@ -1193,9 +1717,9 @@ dependencies = [ "figment", "hex", "miden-node-block-producer", - "miden-node-proto", + "miden-node-proto 0.2.0", "miden-node-store", - "miden-node-utils", + "miden-node-utils 0.2.0", "miden-objects", "miden-tx", "prost", @@ -1218,8 +1742,8 @@ dependencies = [ "figment", "hex", "miden-lib", - "miden-node-proto", - "miden-node-utils", + "miden-node-proto 0.2.0", + "miden-node-utils 0.2.0", "miden-objects", "once_cell", "prost", @@ -1239,7 +1763,22 @@ name = "miden-node-test-macro" version = "0.1.0" dependencies = [ "quote", - "syn", + "syn 2.0.55", +] + +[[package]] +name = "miden-node-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd92792c7a90a2ea6e7e7e2f1c84d675b9ba84385cbf95ce04ab1f04cf46a1f" +dependencies = [ + "anyhow", + "figment", + "itertools 0.12.1", + "miden-objects", + "serde", + "tracing", + "tracing-subscriber", ] [[package]] @@ -1267,6 +1806,7 @@ dependencies = [ "miden-crypto", "miden-processor", "miden-verifier", + "serde", "winter-rand-utils", ] @@ -1356,7 +1896,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -1365,6 +1905,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1387,6 +1937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -1417,6 +1968,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.18" @@ -1455,7 +2012,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -1514,11 +2071,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "pear" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ccca0f6c17acc81df8e242ed473ec144cbf5c98037e69aa6d144780aad103c8" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" dependencies = [ "inlinable_string", "pear_codegen", @@ -1527,14 +2090,14 @@ dependencies = [ [[package]] name = "pear_codegen" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e22670e8eb757cff11d6c199ca7b987f352f0346e0be4dd23869ec72cb53c77" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -1550,7 +2113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.2.5", + "indexmap 2.2.6", ] [[package]] @@ -1570,7 +2133,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -1591,6 +2154,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1599,12 +2168,12 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.55", ] [[package]] @@ -1618,9 +2187,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -1633,7 +2202,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", "version_check", "yansi", ] @@ -1646,13 +2215,13 @@ checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.4.2", + "bitflags 2.5.0", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", "rusty-fork", "tempfile", "unarray", @@ -1675,7 +2244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" dependencies = [ "bytes", - "heck", + "heck 0.4.1", "itertools 0.11.0", "log", "multimap", @@ -1685,7 +2254,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn", + "syn 2.0.55", "tempfile", "which", ] @@ -1700,7 +2269,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -1806,6 +2375,26 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1828,14 +2417,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -1855,7 +2444,7 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -1866,9 +2455,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rusqlite" @@ -1876,7 +2465,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1906,13 +2495,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -1949,6 +2547,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + [[package]] name = "serde" version = "1.0.197" @@ -1966,14 +2570,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -1989,6 +2593,29 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha3" version = "0.10.8" @@ -2014,6 +2641,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.9" @@ -2025,9 +2661,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smawk" @@ -2051,6 +2687,25 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.55", +] + [[package]] name = "supports-color" version = "3.0.0" @@ -2074,9 +2729,20 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.52" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", @@ -2124,22 +2790,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -2152,18 +2818,66 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", @@ -2187,14 +2901,14 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -2217,14 +2931,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.7", + "toml_edit 0.22.9", ] [[package]] @@ -2242,18 +2956,18 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.7" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -2297,7 +3011,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -2338,6 +3052,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2351,7 +3066,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -2446,6 +3161,21 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -2458,18 +3188,44 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "v_htmlescape" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" + [[package]] name = "valuable" version = "0.1.0" @@ -2533,7 +3289,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.55", "wasm-bindgen-shared", ] @@ -2555,7 +3311,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2761,9 +3517,9 @@ dependencies = [ [[package]] name = "winter-air" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89fcd28faa34e8e4d1e03ee60b6ac6629cc01b344093cb3a4d45d84ef9bb3832" +checksum = "07390d3217bdd6410c1ef43f3d06510d3a424e7259b371fccbc7cd79a9c00a15" dependencies = [ "libm", "winter-crypto", @@ -2774,9 +3530,9 @@ dependencies = [ [[package]] name = "winter-crypto" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "118a3228b6b2424df48d25941a85ad4b7a69fe877d31d8b5ed57935720ebd152" +checksum = "6aea508aa819e934c837f24bb706e69d890b9be2db82da39cde887e6f0a37246" dependencies = [ "blake3", "sha3", @@ -2786,9 +3542,9 @@ dependencies = [ [[package]] name = "winter-fri" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644b37fc9093fec78c272f3f594dd205ec21dd35d5722001b1d24383693bb6c6" +checksum = "660f47c5c9f5872940ac07a724b1df426590dcffad26776e0528466f2e3095f8" dependencies = [ "winter-crypto", "winter-math", @@ -2797,18 +3553,19 @@ dependencies = [ [[package]] name = "winter-math" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0dff7a27296595fbdc653751b3718acd9518288df646e860ecb48915ff0d6c3" +checksum = "a0c91111b368b08c5a76009514e9b6d26af41fbb28604ea77a249282323b64d5" dependencies = [ + "serde", "winter-utils", ] [[package]] name = "winter-prover" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ec0e854d35d5d35ae778ea2e5178f81507806071294104f3e1a807beb2554c" +checksum = "170c1ef487df609625580156ea0350c500aeabb3f429dc88cfe800c4b7893edf" dependencies = [ "tracing", "winter-air", @@ -2820,9 +3577,9 @@ dependencies = [ [[package]] name = "winter-rand-utils" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "042a5754bc7bbd2d0741f6658885c7567a4fbab52ad2efec9b797a82bfca7300" +checksum = "8b19ce50e688442052e957a69d72b8057d72ae8f03a7aea7c2538e11c76b2583" dependencies = [ "rand", "winter-utils", @@ -2830,15 +3587,18 @@ dependencies = [ [[package]] name = "winter-utils" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefd8f1108fd48c2516c316e0b42b78e7070e116d1373322bf58993c68cf036" +checksum = "ab6efccf6efa6fd0a80784f3894bc372ada67cc30d9c017fc907d4c0cdce86e7" +dependencies = [ + "rayon", +] [[package]] name = "winter-verifier" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4135f28670385a66d2c80943d7998d383660ff63f8c9f544555d9849eb621ba0" +checksum = "6f817a425ca4aa7acefb356601798d0b86b9c0e383b397af69e7e5e97f5b4008" dependencies = [ "winter-air", "winter-crypto", @@ -2849,9 +3609,9 @@ dependencies = [ [[package]] name = "winterfell" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a4f653cb8281d965ec77b87354f1a87da3bc0e15b3e40262a1e64fa93a947b3" +checksum = "be0a6acdc1fd126ba950b0e5bfaafd9f294fc157b285f35541603164054e9358" dependencies = [ "winter-prover", "winter-verifier", @@ -2859,9 +3619,9 @@ dependencies = [ [[package]] name = "yansi" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2861d76f58ec8fc95708b9b1e417f7b12fd72ad33c01fa6886707092dea0d3" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" @@ -2880,5 +3640,33 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", +] + +[[package]] +name = "zstd" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 81310bd4a..8c7fc933a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "store", "utils", "test-macro", + "faucet" ] resolver = "2" diff --git a/faucet/Cargo.toml b/faucet/Cargo.toml new file mode 100644 index 000000000..50fc69be6 --- /dev/null +++ b/faucet/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "miden-node-faucet" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +# Makes `make-genesis` subcommand run faster. Is only suitable for testing. +testing = ["miden-client/testing"] + +[dependencies] +actix-web = "4" +actix-files = "0.6.5" +actix-cors = "0.7.0" +derive_more = "0.99.17" +figment = { version = "0.10", features = ["toml", "env"] } +miden-lib = { workspace = true } +miden-client = { version = "0.1.0", features = ["concurrent"] } +miden-node-proto = { path = "../proto", version = "0.2" } +miden-node-utils = { path = "../utils", version = "0.2" } +miden-objects = { workspace = true } +serde = { version = "1.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } +async-mutex = "1.4.0" +tracing = { workspace = true } +tracing-subscriber = { workspace = true } diff --git a/faucet/miden-faucet.toml b/faucet/miden-faucet.toml new file mode 100644 index 000000000..de62c291b --- /dev/null +++ b/faucet/miden-faucet.toml @@ -0,0 +1,3 @@ +endpoint = { protocol = "http", host = "localhost", port = 8080 } +rpc_url = "http://localhost:57291" +database_filepath = "miden-faucet.sqlite3" diff --git a/faucet/src/cli.rs b/faucet/src/cli.rs new file mode 100644 index 000000000..eeeae7252 --- /dev/null +++ b/faucet/src/cli.rs @@ -0,0 +1,54 @@ +use std::path::PathBuf; + +use clap::{Parser, Subcommand}; + +use crate::config; + +#[derive(Parser, Debug)] +#[clap(name = "Miden Faucet")] +#[clap(about = "A command line tool for the Miden faucet", long_about = None)] +pub struct Cli { + #[clap(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand, Debug)] +pub enum Commands { + /// Initialise a new Miden faucet from arguments + Init(InitArgs), + + /// Imports an existing Miden faucet from specified file + Import(ImportArgs), +} + +#[derive(Parser, Debug)] +pub struct InitArgs { + #[clap(short, long)] + pub token_symbol: String, + + #[clap(short, long)] + pub decimals: u8, + + #[clap(short, long)] + pub max_supply: u64, + + /// Amount of assets to be dispersed by the faucet on each request + #[clap(short, long)] + pub asset_amount: u64, + + #[clap(short, long, value_name = "FILE", default_value = config::CONFIG_FILENAME)] + pub config: PathBuf, +} + +#[derive(Parser, Debug)] +pub struct ImportArgs { + #[clap(short, long)] + pub faucet_path: PathBuf, + + /// Amount of assets to be dispersed by the faucet on each request + #[clap(short, long)] + pub asset_amount: u64, + + #[clap(short, long, value_name = "FILE", default_value = config::CONFIG_FILENAME)] + pub config: PathBuf, +} diff --git a/faucet/src/config.rs b/faucet/src/config.rs new file mode 100644 index 000000000..3479fc124 --- /dev/null +++ b/faucet/src/config.rs @@ -0,0 +1,37 @@ +use std::fmt::{Display, Formatter}; + +use miden_node_utils::config::Endpoint; +use serde::{Deserialize, Serialize}; + +pub const CONFIG_FILENAME: &str = "miden-faucet.toml"; + +// Faucet config +// ================================================================================================ + +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)] +pub struct FaucetConfig { + /// Endpoint of the faucet + pub endpoint: Endpoint, + /// rpc gRPC endpoint in the format `http://[:]`. + pub rpc_url: String, + /// Location to store database files + pub database_filepath: String, +} + +impl FaucetConfig { + pub fn as_url(&self) -> String { + self.endpoint.to_string() + } +} + +impl Display for FaucetConfig { + fn fmt( + &self, + f: &mut Formatter<'_>, + ) -> std::fmt::Result { + f.write_fmt(format_args!( + "{{ endpoint: \"{}\", store_url: \"{}\", block_producer_url: \"{}\" }}", + self.endpoint, self.database_filepath, self.rpc_url + )) + } +} diff --git a/faucet/src/errors.rs b/faucet/src/errors.rs new file mode 100644 index 000000000..248b38a97 --- /dev/null +++ b/faucet/src/errors.rs @@ -0,0 +1,32 @@ +use actix_web::{ + error, + http::{header::ContentType, StatusCode}, + HttpResponse, +}; +use derive_more::Display; + +#[derive(Debug, Display)] +pub enum FaucetError { + BadRequest(String), + InternalServerError(String), +} + +impl error::ResponseError for FaucetError { + fn error_response(&self) -> HttpResponse { + let message = match self { + FaucetError::BadRequest(msg) => msg, + FaucetError::InternalServerError(msg) => msg, + }; + + HttpResponse::build(self.status_code()) + .insert_header(ContentType::html()) + .body(message.to_owned()) + } + + fn status_code(&self) -> actix_web::http::StatusCode { + match *self { + FaucetError::InternalServerError(_) => StatusCode::INTERNAL_SERVER_ERROR, + FaucetError::BadRequest(_) => StatusCode::BAD_REQUEST, + } + } +} diff --git a/faucet/src/handlers.rs b/faucet/src/handlers.rs new file mode 100644 index 000000000..f97047ca0 --- /dev/null +++ b/faucet/src/handlers.rs @@ -0,0 +1,102 @@ +use actix_web::{get, http::header, post, web, HttpResponse, Result}; +use miden_client::client::transactions::TransactionTemplate; +use miden_objects::{ + accounts::AccountId, assets::FungibleAsset, notes::NoteId, utils::serde::Serializable, +}; +use serde::{Deserialize, Serialize}; +use tracing::info; + +use crate::{errors::FaucetError, FaucetState}; + +#[derive(Deserialize)] +struct FaucetRequest { + account_id: String, +} + +#[derive(Serialize)] +struct FaucetMetadataReponse { + id: String, + asset_amount: u64, +} + +#[get("/get_metadata")] +pub async fn get_metadata(state: web::Data) -> HttpResponse { + let response = FaucetMetadataReponse { + id: state.id.to_string(), + asset_amount: state.asset_amount, + }; + + HttpResponse::Ok().json(response) +} + +#[post("/get_tokens")] +pub async fn get_tokens( + req: web::Json, + state: web::Data, +) -> Result { + info!("Received a request with account_id: {}", req.account_id); + + let client = state.client.clone(); + + // Receive and hex user account id + let target_account_id = AccountId::from_hex(req.account_id.as_str()) + .map_err(|err| FaucetError::BadRequest(err.to_string()))?; + + // Instantiate asset + let asset = + FungibleAsset::new(state.id, state.asset_amount).expect("Failed to instantiate asset."); + + // Instantiate transaction template + let tx_template = TransactionTemplate::MintFungibleAsset { + asset, + target_account_id, + }; + + // Run transaction executor & execute transaction + let tx_result = client + .lock() + .await + .new_transaction(tx_template) + .map_err(|err| FaucetError::InternalServerError(err.to_string()))?; + + // Get note id + let note_id: NoteId = tx_result + .created_notes() + .first() + .ok_or_else(|| { + FaucetError::InternalServerError("Failed to access generated note.".to_string()) + })? + .id(); + + // Run transaction prover & send transaction to node + { + let mut client_guard = client.lock().await; + client_guard + .send_transaction(tx_result) + .await + .map_err(|err| FaucetError::InternalServerError(err.to_string()))?; + } + + // Get note from client store + let input_note = state + .client + .clone() + .lock() + .await + .get_input_note(note_id) + .map_err(|err| FaucetError::InternalServerError(err.to_string()))?; + + // Serialize note for transport + let bytes = input_note.to_bytes(); + + // Send generated note to user + Ok(HttpResponse::Ok() + .content_type("application/octet-stream") + .append_header(header::ContentDisposition { + disposition: actix_web::http::header::DispositionType::Attachment, + parameters: vec![actix_web::http::header::DispositionParam::Filename( + "note.mno".to_string(), + )], + }) + .body(bytes)) +} diff --git a/faucet/src/main.rs b/faucet/src/main.rs new file mode 100644 index 000000000..68293c888 --- /dev/null +++ b/faucet/src/main.rs @@ -0,0 +1,140 @@ +use std::{io, sync::Arc}; + +use actix_cors::Cors; +use actix_files::Files; +use actix_web::{ + middleware::{DefaultHeaders, Logger}, + web, App, HttpServer, +}; +use async_mutex::Mutex; +use clap::Parser; +use cli::Cli; +use config::FaucetConfig; +use handlers::{get_metadata, get_tokens}; +use miden_client::{ + client::{rpc::TonicRpcClient, Client}, + store::sqlite_store::SqliteStore, +}; +use miden_node_utils::config::load_config; +use miden_objects::accounts::AccountId; +use tracing::info; + +use crate::cli::{ImportArgs, InitArgs}; + +mod cli; +mod config; +mod errors; +mod handlers; +mod utils; + +pub type FaucetClient = Client; + +#[derive(Clone)] +pub struct FaucetState { + id: AccountId, + asset_amount: u64, + client: Arc>, +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let cli = Cli::parse(); + + miden_node_utils::logging::setup_logging().map_err(|e| { + io::Error::new(io::ErrorKind::Other, format!("Failed to load logging: {}", e)) + })?; + + let mut client: FaucetClient; + let config: FaucetConfig; + let amount: u64; + + // Create faucet account + let faucet_account = match &cli.command { + cli::Commands::Init(InitArgs { + asset_amount, + token_symbol, + decimals, + max_supply, + config: faucet_config, + }) => { + config = load_config(faucet_config.as_path()).extract().map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Failed to load configuration file: {}", e), + ) + })?; + + client = utils::build_client(config.database_filepath.clone()); + + amount = *asset_amount; + utils::create_fungible_faucet(token_symbol, decimals, max_supply, &mut client).map_err( + |e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Failed to create faucet account: {}", e), + ) + }, + ) + }, + cli::Commands::Import(ImportArgs { + asset_amount, + faucet_path, + config: faucet_config, + }) => { + config = load_config(faucet_config.as_path()).extract().map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Failed to load configuration file: {}", e), + ) + })?; + + client = utils::build_client(config.database_filepath.clone()); + + amount = *asset_amount; + utils::import_fungible_faucet(faucet_path, &mut client).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Failed to import faucet account: {}", e), + ) + }) + }, + }?; + + // Sync client + client.sync_state().await.map_err(|e| { + io::Error::new(io::ErrorKind::NotConnected, format!("Failed to sync state: {e:?}")) + })?; + + info!("✅ Faucet setup successful, account id: {}", faucet_account.id()); + + info!("🚀 Starting server on: {}", config.as_url()); + + // Instantiate faucet state + let faucet_state = FaucetState { + id: faucet_account.id(), + asset_amount: amount, + client: Arc::new(Mutex::new(client)), + }; + + HttpServer::new(move || { + let cors = Cors::default().allow_any_origin().allow_any_method(); + App::new() + .app_data(web::Data::new(faucet_state.clone())) + .wrap(cors) + .wrap(Logger::default()) + .wrap(DefaultHeaders::new().add(("Cache-Control", "no-cache"))) + .service(get_metadata) + .service(get_tokens) + .service( + Files::new("/", "faucet/src/static") + .use_etag(false) + .use_last_modified(false) + .index_file("index.html"), + ) + }) + .bind((config.endpoint.host, config.endpoint.port))? + .run() + .await?; + + Ok(()) +} diff --git a/faucet/src/static/background.png b/faucet/src/static/background.png new file mode 100644 index 0000000000000000000000000000000000000000..dbf2e4269db132405efd58c7abd635d3e6294657 GIT binary patch literal 165941 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

2Cbw{3=b0$9<*(m)s}T5dsQ`eDMT-E+k*- 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%0Ae08oe{xVQW-;WL zt2oM1N%x|^r)iT4MKkA90!cLZ2r$tfSZorcZd&N=+Xmn zT%kF4dr{?)m66rd`CJs>z0~Ar4t7a6&)Km{gNR>o#@!+B4zBf98daAXtM1QP7)mZi zI#sAGsUu%YmV3Uj3CB|jsOs+zy^sJObcG_-g{kM1)ydV+Y5e^03D?3jG|5mtyVMga z?K040t|>7g08gsS=~ZBQRhuZarv|{x)$LLbO=&gSMf-@Te}?KCcZjqq1&rs;?KF9^ za!>MKVk2G zc!%E3=yYuw=qu2By23qbd)g|5$NxAKm&p6LslF;FvtOYHs%hwtn(M_8eCVU^jFL_i zHf3-mVwmgb2tHiEnhg3#xal{{1YB~aX22eU9nf1r?r+yQ55B~^YHcJihVTBw9<;`9@xzyhJeX{>Rjcvd z!SSpzqS8iDTT2(!1(Zr;D3dKSPIZsE0{V4l;3CCP>3_7b&pcc2CxY&mqTKPj#R~is zwvS_zu5MN>+`H~7WI ztllV}f57X=4Sc0NO`oL`nZw!)as@aN%Io?ba|uZV@LNcY&1WnxFMSDNmBBZ6Cgj8{ zEf`VJw#(_(Y2)|;(}t-bJW;Q*MKp0Qxr9aDi@>H3H9-a?4ITd+ej3`f=0p52e7T3~ zQ<@iWZA}wLIgO$sT&_3&!%^cS;<)%gRC0US*e`b{_eP}!@{NN59m=TJX7_WKZvrrj zj9Y2Fj?Rme?RV4t1Pb3W6-uHvKXaO>YZtvbL#%-A&dvx3N*b{zA8wq+@+l|Ud8qHK zgd&HlCTSOP$Ed0@z}+fB$S~RutH6^1*}bpoFOwBKg)x6R{P3OdZW%8nNfyQwH~;}| zErJyZf~zSWJ44BoBiu_V;`kv^iWwFgy#h9s$nr6{x^3R8nnHmee!eG%=*eV>n!enA zP!3-?y~dyq_$N>PQ&n#nJq1FrHGE0t1C@nJ$}ImAzhyA{I8d?h-KjQ!`u(U>rw)~R zlxs`u{CTkszQ~!OQlg}!H5{&iMvRd@u-wEzNnx?D|NLxo&IqJN81o5MBhM0@V(e#! z+^&6BTmJ-{wLnZ__A7qZ5H!Ij0oIYu`LPuvK&FVx13);FzO44VzdHRF-)Hnb6etvI zs$q}*_XK6_N5I~G`$TDEY zK)uBflYK+_`_4SIPYG=3N$~%BE~V2iXw}+eNK& zB4|M8A1KvCjsrXRlDIv=6m$@Q!>sj|c_M;~6Y8R{qq*N#C;IZha4bAQ;Kbh#R#=;x zofyRog+>(%gi=J7;;liL%TUMKom0`vCYa)F!``6ys(pnQ<3zr4^@zQNth3e`_|b2GwYcAjkXbLuW?;;pA=pE}j&F&eeX|fO5I1L2843Rp6qs`c!Y;nuIIBYpf3VTogt3_WlSggf5PSIIzYxt>$_6JSr|+DC|6tebybHH%#k_mE-HH=4 z{w>M<$r{^_>SURS5KGYfIa_c2VFnhe{DvR-3H2Wr`6?=GrL`HWq4A=BJM>bJ`tH{~ zhA!-{p;ddMhH0z3uy6#tA#_~=hFQ?* zp=vI$(2gB%K*xdb4s+KlA0d^F=w?S{G8D-QO{5=k)DHg$X!S(U*omhQlh|BmJ@&Pn z`Z7b+RDcF~SC$RQD!FQE9}_}ubQe8 zhm{TzGG_#(708H7`{WzBY=o{eVUuCNSq0CbyA^yXP5dbAq>Sv!!bZm#qpbL9>+Y&c zbp=I>Ub|ka!U;o58y^%kkCg+iE^gVtjlUk3Nj)UPjg-rJyBEz1UAy)W5kdKFeQK+X zn}2@=EQ(xF>1i!bx|Te($0iv=?(jhWFn42qVBq(B3GbTQpu>D5lFTDYiiXYj7WA0w zzMb6LBzw2O4r`0n0W*^jf`kibMswpltc@W5B%>4flyWl@AsVgMMU}dUeN#Ea;`R^LGAVuwtf|sgm`O#nJ8Y_}`&o>>tqY){` z{qG{|NMg)w(74eNTF`?2Eda9MCna~Q$|(_4K!dBcOZAH9W;!hZUH$F(tL_V;+)rky zYC;L98TY3O;)Ke(N0EA=Qj+4G84I_@KH{Q(H8rkp~J-%-&DW^c!d{Op~gCa<<3 z5=N*{l)MZ$wa76qj0E|t)PB_if1WbQ^5eLd`bHS{c_VG8BnZG*I06Nw1Iiqi)nfPT@3HH?Fi zVMH%`=2ky+&pA+itgD}FHEKe%*=ag~X;}if+k@d3-sT9#0S6g%E6rE&P0ItHLjhD_=yo2QStOhuB06Vt zD`|e#04yAvKlPL-XDxZkZU8CZHtJV{aje)dgG9Omh&GAs_~*6bCtgmBy+=$f z^aC@JGkQNDgE3oX>mA>lo!>-I(kA}{ZJ-t~-I2P}H+qVRQGap%_*?hbYFvnlcGYE} zejYf(k%MBT?A}{m6a7)=IxHDY9FP)al>5r<7ex`FPBgad{n?9CH61L>Ub-(K0s{`# z|D)w>d})AyHTmL&Ts-(>a^?qmwF7xpz*b^`zyx{n+amZQO(@3sPPbDkr?4=y9 + zn)~&81pQ`Y>V`B6Z7~0Su~QMjhJ#s;TGRCCe^6<2JD&^3d{qXabiVF`AF#cNM*9-B@gETWusWX)`!0*~3y8Clu`U~=+U0!hB z0o%*tknDj*E3KZ<;#+*I{@(+iMnj>pGaW#PVmeDZMYn?Ne4Ybu*?08A*k#s}6t@DT zJ3uKTjOV;_kdYC?{oh+<9c*g4(A-ER_f9ToFCy*`=3|kIk>-4r9ns>V=lnQrGcwJp zVrqSGBg}r+ye)BwASpvK&F=82cX$!Ga5Ar=-3j0uco1oFryVHyHZCov+a6ueW}M$> zU-`UgK=OECpo*O3TnCek&Hu70+X=?ZXf-1r?HnmIL8FZ7mbz2?b-}NC)r__-)2miv;8~QLAq>81T7yo=Y;Fw zyZK@jv7^7-=9LOrf=%JXGAT33>+dj$K8oEcZu4R)&2;cPBH7Ik>0F={Sl-U$E;GaD zr`T}WYi0int82?mcS6&}ukE_Dn{+#obR92z@HxL=R#AhDwXutc+)`N`i57L$@Em5p zI2N9-yg93tr7wEnAus%dp*J^q3yxR<67?p`hN2j1Y|*Z> zP=ZUNwAWXFl}y`$OiL}&vQNeINh(BWl=(-s2tS7UO&fE4Df~;5iUz&{rS9?kv?d-O zgej|dY@Ppd$`K{HMQW5KSt$t~h+KJ^kFo1}Ff4WGh8kl&mL|oA!O}o}<36y;EC@fc{lS#Mjzs8g%4CxBMOM zRA=+|n_KO}FklUFsqXhImo3G+QM~JL-Y6!N$m11tn7rR)Ix}QM^OPtut+lhGn@C{w z6}~%VathvF&EvAl^FGKP5eU`?umUJWRfrA@PQf#B>vcQ0U>vWT)vxkezaqr82M6pR z0n+quA@JVXC-Z6L`AzJw_}*tlsEKwRR&PoK&QC^f9(;#MC8f~ba+;hQWV%&OB?wE5Ash5M8yshB6lpND?`Kc5-L!?9+>*b<81;f#p0`eEOn=etHhKGGB zF=Hf;N*1jfbpviW@`Hx>(PyGvACS1M>L2V{c^lRM|2 zKv;##K}GnX1sG1vr8lE>L8@jZsj~2;fT~n3pMIm|OmqkLeYFZ{Kx{FFVcnz=k!1Mc z-TN5u-Ux}%nL2wl#cg&#sff2-?ytL;7~coN*`>lKjuTan)8d#xny^hqOSWy!pF#=e z7LmPiFzM)@>eLBe8D8NQjDa-YTteC=V7x2jsLiJ2NeoLAMhT&&tS}h^E=(MxOx$sD zasF?5nX-lme~kdT%uXbJ3mSI3dIB>E;K7ybEP;{QBK-OQV=n-RDW%FSL?6=Y)rnrT z!oO&A$ve+_D&Y?AVXNuNqpfR5ZJC2RJ~DZigJr#wKAc*bMyP2Qx?|^Sa;dG=Rrxkn z^~4CFWw5fhJy14mG&O)3d4N$e#AC;oe2Pe&-EEwK-y-UHScH%dVza`_4?iwCGsd9h zV399M1R1NQTM~fhz0LDC{s|uqdm~K6)SzsIZOl7=UJ47wfzdGuKml>uEb9J~>Im=B zwjz51EF9f~!sE)YHwM#WUMS`j-$Sfj+8%k))|2j^*T2vRa!)8DNwxeuOEtBA6L*x9Y756wH|XDd8=UYMGN=r?PMswef-NbtYT}iLCuX2aWoU zG7T`-_EgBk$<}c1Whb<0^)O*QDF#qp)Gn(ya{Ko={A7Io-;sC}i5JwL8$iHj%}&Ua zj<32DsX|L~s=0BU2BToD>FFSnqTOi69V;<-{@A=QAOm5z*GOf7^b{Yrkg;qfjBz!j zy&ES4|3{`6;%ZLQ;(797mzE%;@`_)osQm+jCzbBW#4q z!(E*p1-WX6ts_Gn)Nune4y)< z<&S`FIH}-Uj)~V@+V%9I)nCNq(&jz=DGG5!|FDb-`jGjqrL%VV5I z74IZ@T2;-*h&253__oN6y!%dW*08gtY58Z+s0??& zhK~O?|Kway5dN6SH#H~CGvrRKi?HiaSW`6aUS8ca+z;i~H+yN||t)BHd)kH4? zUHEM2utFY`gAeZn)q5T;f2k`Jxf4-c^W1hg#}A(mPJaf#;=Ce9s5_A(cuM_gsMtTY z*2aWQxHOh)p2j;gk0^#d#*gt-)L&UDfBr+D8VW(G+ky_ zq90Nxjg8?QiM{y|VH*sPOMQnVj%k24G$x`EEnqdp4!*@Dexkei?Fi#s&#fHb%5gdY zFP4b+xmS9KVC;v%c`kBe&&4Slp`7^WPFU>PUjk*QBnlL0>G2v0uq)!n3?u9C$`;s( zg(Rn>9gT#rE_;-t^UhqyYT#dgK5F;nzt^%mRIst`{vxbKF$k2yuVq%gzP6+*+9RVRfM);H(q0 zG&*Y!GBL<^KA?7zpLx8#g9(l7+JnuQ?{yRf|7T&z`6VZSQ5>$l&ZD+eXIOfA!_`&K z@3B@21hMLqe08E18--#BL$B5m9+iStJ;zgM1S+{(l*s?hlyzoW!Kl)GTHOTNu!eC? zcpvT>%w0epYs&}Oq4&>4Q4&ibXV|~|&WP!qY4xNaR?$9i|E@5VBaQcST+d$QnbADy zfP$EVu&kFM)HxA*&DXs_6~z>H)^p7Tw*v0p4Cb1qIAa%SxtVfvhpi}~YwJaZ!exU2 z>W#2ZP)h*^nF1i@8rL(KAOnn0XD=gPvL+A$WDr{N*nVE1c?3T4KV{+Ss8b5jpGa;m2;XLe=Z4 z(8vG_-t4=i?9nST8VvCcE28Hd|Q!pU+LZJNcw=7q!u1EVXRpfOB`~2 z=9^*q6mvDlkS5HBf`@ZRGEhc8C4fqafKb8~+^kMiR3%o;nBUMp&rFrDwr^CGEOTHB zUWWZT=2fp2oK@WVWrdP4bW#cY>edw`3SS_rN1-<-3qbHp;I62dH>sdAQWVpjE*2u*{ZlEMmJD^ z8zEL{`}H!&J$od`j#mrGkBE}Nv6uE;ObGjRjiDDC?l5D0tiBc6Y-8m^a95zekbX-> zNsISY86LbK0c8SLq02!UUSu^wskDP!Uqwc=zFfhS09v^w{>=}FC4;8C>4%hd1N}X6 z(rAuk8^HCQ_}eb;{fM3i8x5$E5UeN896K4;3~}9a091|-!wS9JuMu;`OC)owv6s+@ zs0mm>%j64(&RS#LY#<})Asro$NssR6>5kf^zkPB@HQz^^l_gMN$s@B!l|yZf{p>@e zg)Vr`o){{BwVC)K3A2ePrUaA ztm_itt-93K5m&&o(F)hYo1xlE_-Hz^AVLQzdiwQ0QE9$6NmrJt%n%sJ5aYcg*Ur5X z3=e!>V6-k79(UJDXTvJ(tF7J7*WsG_fX;qev2&5CE(u6U8HNXo67nAlzdUPSeleut z2ju4;FALkDU7K-aC%D0UGlP2qU*6(d>cE{S5{AZtRF0QJs?yZ?;8e*^-%RGj1a1E^ zN8wX`CcMiuad;werN(0?Wok--2}OAT@5@#=a?UC!9HRyBaGm(nd*{y;9-2FbHI z00{yvwB1M^biOqi)nCQ1QsqF*7rV#~B6&SvtJw@1)7~R5CCMC`N)JC%m|orkD?^37 zW!{$niNWl46Z^b>xb4E~a@AL#D->RV66Xhobu?nK^MeMS{IQJDY`}O5KQKwi)pUJ~ zL+=mt9g?DR?8;&a$(8U+>t8s>x5A!KC}h`Q4Fk#0M+Hl`X+ zdATV`_pjil5^W~E*<1xpY6X3+>iH^W&->cTsD|&J%3qNHHp@dI!UP_~oyJ!s*cECQ zRG@0`<~1$K5oXM8LP&5rVf4MjC;T=;Y-4O`B?GRZh|?Q=rX>J@QOn?j2SG}%ea<(u zsLfh!B0B+o%Z&zbB%K@7Ih^vn`x_x)Pr#78fcdkk>D52$TS88M1C{9n8kWs<7VL6D zP`WkjLs`8rDbuJ7QP|tRqBcg=)!NMu;_C6^gk@*g7=;Tc3FDU`|4x09Yh6wK?ylGJ zIw&)x$TAricn8KB6%LrBF7397eYW2if|Tz? z5Ql^T%%R8HsKIjaWP)9tR5c@qRcqP5%-?bJ)!|+W3IS7J*;%Qu@ARa;HrN^cO^cT> z7E}kkdq=nCD`upgZf8xEt3KMbGbE!F*n}{EY4hdnqD6gN9o1DEc0`VomAi!JKM-c;2OyCYn7$vmYHCV6*cVb9P7bMmFslG zeEvyzlOHY{{=;}*F8E++6#u?t{@jj4Po!1lsu_spX_`!1l0%e`xL@-nS<8=qceauLkLj*3}hP?G@RwDVi6=Y zQBo&C3GKPFcj=J+oL^$RuS0+hC}1bIcKS0ZsMDCA!u?+i2*=dm&Qg2Cr)XFQbY~ep z7|6A9j}KzA*RLqWq)%4x2NVftlyuCj=29O08)M|ZYF3{d8SrsZUajDjz$@P`+x(~? z_Uxn|ua(RBHJXtB{XA~vbwO~p?q_5%c}umyhpr+810L1{k^Eh#nCX7q&{XI7Gk{fE z`VN!S5%Ex(4KV^EeSki&!~Lc@d9y0g^nT_@5knHuR`(c!Ym`!qJ zN^K}+f&9dQ`rQ?kE6oBC{x5xTgubs?7_?Yn9ac!kug5ss+)tH_ED*G8av7M#68H=) zXm@L%Ed7`arS$h$LASwQ2C@CQq093(mty@4@AfxjgN(!MtY0$zOjT1}nn$cIjJHR) zHjV)EMSxU<2_m|&S8rypo)n<+><%V3X!;CG%foM3LxMhH!rbS}Tl(I7!&Z}MKBtwH z5C3`>wY6oLX6KtR{WlI!V^2*w6Vk^Xpu$?g>JD6%Z*^P+dYA4k`wsokEY!`3Lvk;T ze7_yj;|+&#S6%CD|H${aHu~%rI>So`acP~NoIB~76{~N_M)iL#5FIlfM&id;Uq44S zbGR`%k|!YYH(L?%6UNR5`Ta1VFyAF_Vpw3!_CsOMi~m;O!@_aOXxCZz`KD9(c!I>) z;5ZimK@Oop1ud5FTH~E7X?xN-We3w}Jl2|QEgc-;{!onvS%Ncuit+wld;TOZ=nK%8 zAxcrdYYui&>^`J$BKhHPEu(RIlX2}VwDQvTBrrQGh7obHTN7;R0dE%YLF&e0;9L*I zEWl3w5DJt)i4@1uu})BQ0h~Ox-m-5!I*X>>`11FqHS56BTK#!a$>AW3fD5&s&Lr~g)f#o1|4ATo_ zoL_CvHX`?(aH~MJH7AM-kG|*pX#XUJrNcNYeY@L)Z@$51rhkd0S zws%w*=z zpe&!eGtM=g$&c=+IYmh1Z(Bv~Muvp7-u{qoNWRW4!yJMDF(JIMnyzvIOO>!6Q~8u| z#H46RskRECS-Vk)ZO=_tyZ;a{aJ>k)a)`LcNh(bEl(KxJh(>= zEv7NgHufvkE~WsB6?ErvM;z7WfBGPFGWE|oCHY+`wYT+Jwvk{e(%$uYQB`-D561&k2IJ|h&Z&CI_Z~I0t0;K$H~tO7 zq3_B>b=ln8`(9CtAlTDMwohZ^&0C#6vr~rO*O^A4IuWm5)IMBEJ;?NP7^LjT8 zv--7HttpJD+Xd2y4rCysyl9~8Us#Y~c}3M0L{l|Lys=0?AUlz8mBAymKRjQT_+HFy z*9$a0X>Xoz^--U;c^J2{top^a>>Rh=jmM~b{4Hs#@K5joA_kqUvihfIEB_wde{|z_ zCky7+Lrfx2*CArW)dZHux?mZ%mZv1pwkCfxyFg*uiH0o^lr4XBdN^+9BL63ecPu#b z0~R9b9%j0~i(}nGhGksiw%d}<+iV9-7m z)IOA965;^{bb+hCPa9AdI?wqhhx1*xiaak<9C@PlvuJx+j^?tcwCm4-gq0-mkR%L_ zBmw{2dNV6>rt1lOuNzqchdpmMv;Q2s3WNOCKbNt~&K9-F_*>{H>x-+T3TUQr_M6x^ zPUPB#s~zQA$F-gR)VvJ$9}XbNc7hgtOy47LF`6Ib_XYk-!+&lL>dZ&fSp?qz4s#s4 zx(@PojC?#*8^0%u%O$5~S}=9qEPkIhNrV046MYSGhfYX40* z>xqm0cG_bI@qHyHkEZ_xi$cEWbNtg_6y9)iMASEgOgPB_-|NBE!`>BtoY4P1jzn-k zbnTwO2jKOk|*|>CgPg6C%gokRf)deYx(uEOSHV z)F9QUVVUNOr@RXr`$=JR4q`P(P}X)CcX_*iQn@UrB?f#j!=$Q$Bog|{ZjGJHKSs%N zFFiIpO}&p)(KUfN+HieLB?h!wy@1LDc<{H?Nsl-M0c80v(%m#2ctftn5KeDsF!`z; zvOJKIE3u@2tYqC$OewJx_D0+_{^)h}{W&R&mem5~^;#T%hhbKT=w~`>pc5`9hcB zz6#y0iJ2?VLlra?<7GSV3D_GLD zb>m8hH@qIgK6yZfgQdXSH4dYPINpvzYzo#x{H&RY0{pk6j*fFD;t-K7oVMP?O%P4r zUyne(x7?K`43`bY`p%Ga@N8X-907P7Z5WU{q@9j^0aVB=rSe(m40F=M;Euzq`C+5U z*cDAzG}bf>Jc8Lo#pRO+PT6MvhgbQ(f9eo|oY^Gy(=Bc-^Ca6I1)dD-iwom4RX6_MFngqhiBb)#JysMSZNGjG=@xZJP8cgH&SYI>z{n~Ewmzu6TX?M}bN5~bB zPIWv73yP0$NMC;8v_iY*dbA^?Pux*jpQ(dXuo4GZvhzpPq+7j14IL$w<5oea1k=Gs zsM!T%g|zCR%hocf+JByCS=kt)9YXdIT>XfH9VU%>V?aDFj9Qh2x9F23-K^XONlc!) z)1Ee31V}r=^|${T632Owiw>o%aAEpYe|}lb`~u)-sK1(q^Z^Kms)BCia8JOK;IyF2 zfaC1uL{=E^9;=@tH{(NGD;-~eh}ox)Sr(NH$v>LSi)x>m&YBZJzk@_e3$4;3c2>|{hiF|w222uF5$64hb&v^sWA)F;$0 zJ}~fLiOkAoM+PI9q6cF0uTx`qc|9>+2s)dhI<|0}7%t__!*Fyk@ndCmYK99lM`ZwP z(+X~}49zThM!%Cn#j=|8zZeacSK`XYfO8!w-<7p{`9n-N3TDQo1zirT!M?%rh6S~@ z1uZviET+KbU=?=Yn=oR@ewxSu=zTfco*7#?s)2sUx0}rE5m7i!yB4vUBDIepYvsMC zSp%-Pv;Dnxt}u#{Iz;zJxm=ll7m|y4jrm+##pzCx!{3=8bI1toc)leJG!Yc|ChXpu1p#prpDHF>IQBb!Vx5Ffgi`^!bJhLZhoCL#|UZS}pfxVfFb z2DQOl<4+GpVAl-fP7WKFFU3d*ybCIq9<+{DK2fuBy3NqP(nUnS>>H8!5ErAy*LyhN zBrnyXI+_DMzqF*Pv`6WO0j{SU#7E4poDZUjYyOV`A}+Mhg@94V5MQ^e&=S$F-i(HS zvXW4GdV9zF*US;H0!#4Uu(|-X(hu;_3Xx@AnV zy+c=T0PU{=F5KStVGJE)z#X`A5J2&0jGN`pNdJWdA7ZA*_-g)UsYlNi`u*HZ@!H|% zUeJ(>s~NnEFYCJQ7oTkcQ!cz`QX%)7$cNAJ>UjuA-<8I-0sQtdNzVxuc|WS9Wr$u} z>oCXD{=)+IV@fvZtmseAYEYo?G%ReJph-@rsVn8TG}r{_Rp2yR%mR@~xNU^)gTwAY zF5A{_cURUSNq3apz2nK0tLCo8HE-kM1$9fx-=ewK-tOR-bnDVJ$i_lfS?zZYCh#>j z&feAmXim@g@kvh}O_ITq_GD&1qng;UdR1?{kIY?l(x%r#7z@DMK9xsuv;bniAAp?< zLXU%A8L`s>T<3LszM2O`?ysi)(k%w~iIdcwQOmqJe+lJJ3g&t|L~`MXo1>bH7X1$k zoA-@)OKA%2f()(KA9S3y9vTCnLT(m9n3*{`Ur!ICC8n)xwIsu>iOq zal^ks0|qbp8K!p)Xnapj7#6rhK*)(P-?vV7C;d!jl}GGQ6lp$-tGlRBUj?wB+R*k%Q8to>&9=P>}zV(Yxu z9A(Sy)x42BZaRFG)7EU!C&M~SKZP5ZL#^>Gr`oS3O zvI_8^+cj)@Cg^F@F!3c#7bK32xc<-)|6x+2?t3ssDnet5`Lwm`>eI>>8dLSH+vM(6 zJAeVmjaz$J>dxOT@GC2ryWG?QWK)m=ACj#NdV=O>bEOuJE-%cD6Y9@s?^R%SY%H6> zLe;FvHmAYvt|^>4igp-Xahg*O$ zp?eWyKmh1auO=uhB+&?AUbr~*mi)1BK9O6wT7uPaVxvO%Ky`4d-v9I;K0Ck_RfX^w zBpMRZ8@T&DacY;iZbxI-)>-4M_E+JjmVqT=P#zF{q+9m=F zRTpf{m6byq+|YhJ@mfjq&iv`_x|5JAF+zsrJEq98JZ%3f9AmU)@Xi$Mb4H?BB_PKx zBX|UCM^E7`$hyQdT6LD>)J>nEzScdcBBmn_eW9zL4{S$G>Znb1g3&sBb5xMWVL$Be zA4h&9&c?!OZ1iilJ1<{fP}pri4SJiys26GZzD0yZ#9@Syk0OPrVzj6S!S7n_6DD|JS*%npm=Y zGZRPYe?KvXcb$Us(^xgqzo>z7ttaGk#U$pt)UWCSv$~TSS;9Quv;2nOlGi^{*RVca!r$W0tr&9g z;14DlpbqhlUN!rKfwR7)Iu5yADuwB2fpavEF9HC1K*G?}NjV{AO)~?P|kEQ z^&oG`*N<}&0E3=%6oj-mO5ZU=puU5F{#)27@h2bc-=P%WW+`4zyR{@(d6;SfdfHG-SQEfk4E=IpsM(jc_X-D5y(omzykPDlm z%cMen8r0x(YhYU&W#XOAyn1Z%94su z6eg+%bza$6W6uagUxr!T_cp9RM=>GhXW^|*izcn2!HCgum-6St>og2kC0&ibCXmSq zmieD^lgRMb6Y^?eBKI)A52QL@y2YEt_i&fm#5x!bG#68xem%ElQ4^XncuY?gdS&&@ zxQ1|u?VDK#WDO;~*t?H(a46EKalI3{c$cick$vK5A65{6KgHN%V%1)w?UGNbo$pSe zT84BzK?Ott0sSjcPz~wd01I0fMl^hl9Q5-Os#33afW`8ki1~SU0jLqYb^S;H|rzP_ZTf{)`#|4`|^HDk1_3#lh!9zzy%Rq z)qYEzE%!p%T3eW0E=DiV-&k^gzBC6y%&3zH&i7G%C4fvpD{MI_6PmWcr;L853o+Im zmUeCV^r%xn3rHx6LXu^d7?chrx+;%Tkj$U1PZ$zyk1@9ujoPdFV@G1Vs8%k*krX$x zh@#!PA^a}7bk)0->b-fet@-)xfAlRGI@vlpLZZ>{OgyD>RY@FO9s*2C#A|=M@{Vt~ zS0(`JQn474s|`)?SotS`Tp#8v+@lETlV zy-3d`Q_7st*5xLbLqwSP@-Hg@=Prws(>LYwc>K`n8JEDrf$`$}h=xEB_fL};YHgFj zJrW{nM)&}%KUtj7g1C~IG8mn$c`G!gV&!{cI%+JQCg*LKMp?hkhwTn$ho(wzBW)waFTTTxfb zA9rG9>}qy(Der+AhiGT>ihDm%=9>MzwUE0DzDb$ zcgYBGz3wb)1zvbsLmm%%++ps4$xQC_7uUYbbyFXEeVF5P%2!4>TAleHZ2QB2rP_D<=8%v z7fvrJ5O7#IoCUZ<=*i*kfgO-nrMTHHp{H)})k7qeUz8@%sIhC_#ZVD0wJCm*D4Z(v z1ZR0Y*DrSKt#I7nO3cWX&1d&Z5(iUb-Yb20M0|bol26v3qEi*^(l5s;V>`J#AfPwV zYlm1FrAsVD={L?f4On}I#EeDgq!G8pSZ|4&@Q`cMG93L+V+vyW%08wx3$C3nz7I<( zvCr;gck?1<0BF!eKC=-zd>mIIgFF1Ck}++%McY(fau4E#(F#}=`i-wX@3qbN1>&(`@k`@tk*{x&`eOsnm`d>XLjKS{4jbGmX zb4=1Kx4lqY%PYDgE79qzyi(YHG|4p*{6hDkJAPa^%kaIyHd(niy1#GJEb0h$pc*@S zI2@DJaiXH-Hqsg{ava95TA2-LqYrTK-ktQs%R9d+?LQ~yb@Ve%K_>U+Rb}@&w#5kv zf%6_pDo-f~uCY^vsGjJgLj=*5uXBG4=DXq=3(l4`z5`rK;>C4ytXXz(-$B4VwxGi_ z>GNplf?yu-uy=BS_(j5yT=9)0g~2-Du(0i$JDYH$nG%X%C)d!l^?Q2Cs$Fl*XO<`T zzvE9BQx#ODo0$AiqsFqbkhn&YR@A&-s8f(+Nde!`XHVzFZ zOqeDrTuXDZ30TSq4<*D&x)lB@Wex)8d$2Cf;A*~^bBozKDj<`c*Y1r)3RiWdzT@d7 zVYfqOYsP^a@8>`5(~>@Y8KU-?k{9~|x!d0W9;F#Opyp?YlDG^gDOWkF`5(KW_4;`< zF+6zu)99aq)U$=1!8PlUF0ODYgcbkvamgx%E`)rZU_R~iI9df;x9)mg=ln?*%>o)M zD4T#9ke$WO?P;G(eC)|Q(9W(*$(;;)`qzGn=A&K2T6u_Q9DEQ2kk@j1{QDT2xAd*c zBem$SDljfdkHKv#|JIq`$m?xxcdNSBL;^%E_DxkM_j_GZ_BOGptuz{a24B?zO+G>G zlvKPp>p*t}eTbEtiUfFNHnxU~M5y{-N=>$sm^>%aOs1d*0bfa0cTQ(fG#FpQrg~!F zB0C#iIkiCjx2knzr{u`aJD{5nM4bW5J?x7|)bhva;4{7_hoV+ zJx24VQru08ii`1jtfI2fehU2mb##?cQFUt=7#Lva9=fC?q`SLYL`7#J~xdnu@v@?yTm3s!r$zvL;xcfOw(xP!_Urp34Rz1VB(P{3STE7eRsax0! z=KaCgX%5KN;~UYifM{p%{^Xfgt3V&e1Vt{5Oyuin=vn1o@WkJ^?=gmwqp2K9F06EY z3}-}CD)=C`ik)?5$VbUM%0Mzp=6Xs{*SOjYYwqrF-8 zGBg;vpd2;W29;ovzofx#$fR{lub#sfod#9=rrQgF6Az*_K6WZjH32FNUQ}b zgK;CN$MQ-@gGAK!3O>7%Wa811!Vz*Q9DE$4(%RS%P99~XA&hFpy+l2bUlo4+k~t0! zX@@H{6!;)tgJ3Y=Ck}4$7jG0lOX*Ta@d6QVE-tUL(G%T!fg8y8<^;ZE~WhFZ~9s&NqPD9FjmVP?rt zFm^vv8ilaF;_YmT2CI$Jnrr6?skL{ z*Zr&sOVl8v=!JVxRw>1M{LJTZFG4fV7sN<~mu75ZksPv%56`|mZlNz}Zy&~{7_|Z6hH#B&67)9oT7cY=rI?yfC2Fqcq^)|Ll+z8Uhw2-1aE=+)E?lAdkvFi?5a8 z3Yx=o1L+6@S`%Ha`_;%5IDd`>du(i_TL& zw~;wj-wd~_6^_jvY>kWM?HJ3<@@{Ah;XYsd2mPb~%~V79@jnMNdq=hA!NaVe`fgLz-Kb$3lPog1O1B7trbeaoa|}9sumA2 zN?%&=Tb_@`nbaSEd@n@B>uY|c%P;`Kj{P(m*)zro)TvquefY;yDK7b2g;zOpy7}su zi<~VJUBqB#F#eL}76ULsZ)HnvSg;g#rx+lRJ?-0Sx2f8?6B7J_J-A)Q^$vBpGtT~4 zQ-09q4x$=-nu{3wWP}WKwlV{;14J3r-Z7sE-(HvZ8L?!*_TGbw1l_RSd}*G>`cEyi zlpG|vV_R%GPv}ex2zVg5JaMcL0#&Zyl@l<#hPY#yhMM8?AaWvrwao#&RcA0u*Gqu zXr{C~2MQ=aa`&A4-Z}(!R1}b~?(d8&NX$3`M8CKuXGjtgXL^NqjHgL-DxZjrFUlnJ zG^k+%kj51Z&c}30$H(Tj_HykO4u;;!U}Wtx(y8>gEY#O8FsEb;`20jDKXCe<4k+t) z58H2#kR(Xpe;d|qZDVJ*=dz|FcK9Nl*HcJowDJLX`(^891S(gk+;r*N^gS|<3lG%$ zp;BHcvPDPuvl9%j5r&oK?S83 zsujp#K5RVqmgD0EU9|DeZqS?~f1`f$2H8HDQXs`xX)V&_c4JKjc&m_bKS##g2UXo- z>A)Xo5DI9N&Qtc@&^X@7X8osR@r9FHcNG6&&m#>{_Qvg^7D$zr}qQlLC=)F zYYKrf%Ji0$is;Lb7KAj6F$Te}X{L^3V?Z^_uvKf)9_;VR6&sFMJ?A;XuW9MJJ3wI>hf>`80sqJ97C6{ zOX=glT4EMf2|Ppdq#OM(2;T_2ESHNcEJ_k(vBZRgp+gn{V*W9hn{%SlU4jw?1HNkZN8bp$6VVhW}I}?T_;0#NaHpsmP zMn{mByC6%qLD+a!3+{5L~);;<7@} zlE1*eh>?rA!5%F+7q7bAf-BcT4alTcPq?pm00`|MRAIrndUB9TcY^xx$thGLX6awj z;PrN|6`DJZ7#y6@s%V~OT+d=jtSa7Mt}elXJ5CeXcbYx+ntR_!wWz{EoK8pwy? z681TlvRfvIxp*y0+AMe0)*K z##vpD+V!jg))Ye5(hxv0vhzS4HbmHl@)7Mn0%?NU?XW5XJOs1)6wuu18Ir07kl-;? zQYs;u5qZR)*Vp8XjO%;ujl?Y?f#PO-^OF{AUN}&opTr(xwxILzx`HCxC*@G+v{+2_ZSpoH-?Q?F zsrhj$)4%0P3l|p zwwfYG1;g919p~=uX@_v2Hkj7&<33(YZjDF4kCAWQ0pRSxb32^;*trbJG}vB}Q~A;$ zRPbLgHO^&ZK2G=!HkOg-q@e@Hx>v{2c|F<2&YzFyLb$;G9;en0H-cL2vZ3#;#HTqS zWdO7&!^^sl46eX#+#k~GbTQ^vQXwiq_37(@xA5FSP77+dq8R? z1(XZ{By6#iOn+0Yc#AFd=jP?e6D#Lvkn>g)5!~_fhJN@yK4IU8my>U{bOxINWg zw+hrvzn_O3S6sUD_0>Es4g4&Y{~!cdgf;-hiShgVsd~-nOvG)2Dr13yz(~06?y<>( zF6|}?ZmPf3z&>A-Y6(Dd6#s}wE-kEmK6hICls8D+-uXYDj6ZV@Q=&!IB;|X&4`GFb z`6QTH*as+H?q2&+5A(8HNO-atMmk>t@Ij%PBEFrfbnT)%j5ktCy85_wThRnli*fbO z^490vhP=JExzQwaKhBj5rbQt}a3&GxNx|f#!`K`@wd?p41wGiqO~d4OQaVR0aOJqb}}TtT$t&AsJJW0h?Ym+Y5#&yO2`_687TuDmbhCMZhO@0Nu; ze8-j`_2mkd{7B`5RIX(e(DPAPh)BFf5?ur4JMIw5PK7()1$iJa^gZEBBsYFy7z+%rydvwE9rtl7Xj6drkzQ zsw{0+e(Qt-#3m?-M|sANsuQ~i9*1jBK)F4}A8PEB#wc(4I?_ey@^y5#czwQ6U}bbn zDKS~zYt3fQtoy4k)yNH<)~O9nE&myLzg8hh0Cw3 zXs*IdI*sN;Dz%Wwx^=xZWO@nhAcm0D!P#7RrLrwFIHH5p6`K+_;|d)M=xuh_j`EU5re3aapp%Ar+5JZ z0ms#!c(*(+tIYDR)2dy%IQ!Xw(0YPIZeTg{CVi~dqxoC9@HfthD&eb1sKx@B({)dE zVSygnU~Fw}M@z4>yraKzH1AszAEld-f^nqBWimO~o^9Hq8WKafVK5vzj#uK;4`&7D zbPk3J*X~>gC8JPD;wEp4$zLfIpDRpqa8-j>Rsc!I#Jt65$%b!6L<^hR_Y*~$#`u9G zlN~_fS-*v&7FUuXPfo-Dhw>!Xz9A=VyoWZyzr)+9h;Nzcg;|rjzrg3Kx)=m;(Kjh{ zcoGeL&1Q+3|JW5oj)rkHD;yYwf5k}SMS_Rk=y6|WJ|`D#XNX?Z+B}gw`D0CcSa;|) z@~XJ02O54t+DHpjvjQCuc`Mbthmy`Xk<)*wv+RVDG&Dc>juWARIGq=L_SQEp zv}S{Hrv>T!M7pBGBXNa3g7zR3e@z&1?ykrP-_l^jZTi0;!x&{we;9>=`UL{6rF9m7 zkIIM|eOJMztRyRr;G*CUNx|au47vDBiI5%A-xonCPO}S^v`DM{=@&<9f_B9KT+3$D z>#G0h!=O~qcFNu&PaAHY8pUk_|9J_p#BU<~T6Weo>tBV)^3&Q4HxEMtzmjmK!QfyiRa?W8YF4Eo#k#k|gYsR+(+x!QFT3k{S! zH3=p*SM9T|EZn!lQgnVC;vi=7ZTCHe~XX+U-LP}8$;a!*uv)3*xgwoTXCSi0XZvH zMGT50WUqvVg35t4ffHt_G5hlGms{I$s3c>%Gkiw=KKr;qUGdW&k3vFFB6bzkI7s!C z3C6Lc0y`zFa(=l=Vkb0t;Qrm8hbv0&pF_LV8JDavrTrZH&s2Pm(VuVH{CMfZTS1dj zz@w7OgA@nAdsNm_oK`m7!~xY9D?b6Q!nUdFJ-ClofCWrJFjB!#-$ek30xO~%)GDEN zSK|nQ`i`|+j5>=^C6|m-cTcww9bbZX*Tnn+*6@0BtXPU2;(lJpnXRvbeN^@Fr};Ho zYmUU31B~SU{-YHA?ZTPTS{Bf5fi7f}-lo3p`p<e}Z=1P&k%7ST}RM?1!f!RHYsR+-V z3)R>2lo7|F=;pv7QlCU@QU3 z6&C+Wr@iaztV~AjyQd~N#oDW*tW1f9>t_XmE{3Z=Kja;Nd+C{I#5EdK?WZ$t$uky7 z`k`~cAk)0KVps@!)kv%Ak$TV&$V{Vc=&eVz$N}QHaGRR;B#PhpP|K+hxI6hg`Vj%`(!7E;mw3)j)G?;zo73zur8W*;1MH3N#jc}IL zgU%jcjq<9f`}!}E0a6u65~?7=pDe*(59>RP@jkD)JakO$aY8r;;di6otopiA60v5*j$5wxaoN^s-7A@zaL+$Zz)zC?K)RY(( z`GcGwXgwWpB0Iqiy>zI;S>qFZzTZ_n?7o zfg7qs31jNH>|BtSAo2$(4{1N_APXMKn5duzIIade4LtdOGMk~pfFrYf)vTueowHGH$h4lbRMNrxYoi16v8vA+-%KU0ohUglaj(MMXRy_Rs3 zi4%u5GW{!RvFmf>rw3hlVuAvcPs7;mNCBC_im(o^E+BLT@(`GQWHDO@1ClaHnOH9E zr<%Cgk{|^G>Z{o((JO%YJp^Ko{T##qi&nmtK&iYbOslwUlpzY9{uyP@AoNch%g>;) z?i(dDSdcJ0$8<^sqn+#PfEh=@RX=w-Y7L=#Z#9&G>7&MzK7(sDkWZH&v7+BGglqbX zQ+J3{o}DViDO*hD#m{JQdjqx4Z~zjxC_OMRnXB7SnJFQ+dHCZhFRxJI_Kl>|pHA-6 zqpV==UWKB3^f51Ho4Vy_ar$@vq0P7a(l0TKO6#x7_Fh7o3WrMw;+6fjrgiIhr1#OP z^tLL-i-SpV61Sn)N0N9zz&009L0%IJjFG;N2TI38aX9$icaWiXJ zLq9+VymgB7xXC=ng%|U_Ks~@uM6r;^MQ5QXKo)REopJY`k`fiQ1`TNc=gC=0#_-A0 znir~_M?!dYm@k(Ql9Vn}slUlW{^3IEb_-BgJ!z7>e`2HhbOt+i2KFMJ;)AHfPhy9C z7tVSSIPc9Kq=}s@2#vXk|K6cR4f4t?pQoZ$(8X58-UH6DIOdiW{*_~`Cx zTBb-kI8`y(Un%%+Sp`4-+`m;}kvfu(d@R3dYG5TDSn69|=e@Nddtv3hI#cx)*~T9PJ(pwJ{{Z*^fotJzb^--A zVBu1Hk0uM+3xU5`_ey$q0XH)1(qw{fj=sFznZSJhSvKLeDgZdiOBuAFGU*}$q6Tpw z|FE&rUZOUq>eGlK3HmrKA{qvm>NCF84w4v=yn=h5ax|^M#|Jj7Wt@86Qf_O+?7_l! z^NxfM6yn~4kd-WCFyewT%Bj*yf1n_+0ZNY#26o*EpL^vP>Y$|e4A*E{~N~?*3V!WeEA3RTqoeF zFU0uXjxAa%>VfkQf)I>^0w+Sk8Vb1L)Z$!P|z7mxo5GSx4f*U`@7;*S+)7+?u0q zsBtW_MdvVy2kRS{Jy_i7Z@RDP9w#T$+5XXyuHYo`zbY5oZ{1M@&Z`*cMCcxNt26f& zf}z-V+~0<`*{s4Wz_`WAdgFeBl`iE83r_wz&H*NlBgW)ll7MJs@&SOpWABXf#@Z)C zR*#b?av3;&6&P3Kg~LlAK0zPrqAWw}K>`Ay0nitkDYu`aRQk)1gX8Ys2g+nv?kv*d zXPo8rPIu48&x5`xKEM6~{Vy!V@)7R%aB{CGvG3%}PlZxct-|JS4~Ez&{`9Z-(M-9~ zM6|gm_zpCUH!v~N0QKS10(MKCX5FVkX!n25+Hx6U=zs8-Cpa(9|7KLZ0D)lJhNj#$ z;vfU#nfHaxlg{t-iHX?b^fezoC32({sixcH7s;rKs>@H2&5KqyX`*nte*+ zN5!+R+pfLksh3nvhE;d@h1kO+)QDq_JPVqiNx&r2dBs1i1`UN~f(Z#NPIh66^NRKE zk??JHTQUb80X>T0GaH!;6HznB_m5*Xd*^s1H0~~(Q~2OsC`ns68r5+0zb`hwY!P9G z=ffNjo?_ad3L;GY693=g0~^#-EYZ825t0MF)lzJMoQU~U7F?tz5j0sL`pJc9MbvzxFoN3b8az462vR;C&I|RL2izxS zHa!tn=R3iC6W|*SmKfi685l&m#0`HmR96pIRw}E=P@5WrJ1L;@x@j=xf5e9m)wx6W z^-I_RuK6jMuaytkYsw`zomdvib)jl|Ro?nOi1i%o@%q6ONJ$*BTW$A&gSZVgN53^z zJr(CR(T{OI9I-Xu7%Xv655b{`fbssc+WK=ipRWlQdypC~M4Pd!z!7zn5^tCg9|{r5 z{Hp*`0gQF*dw&`n<%mG?)nPM9jN1IQNO6Q0XV)8+0#`TTcLixzbdm67tTmjk9Do{R zTI&JIrW^x5==+~WjJQ||X~fhb#ey@|F!PIm+R7UsX@FrwV|7YbS(_|DaXx9xK{sYb zzY8_fiR>v>2H)KC?g)W1JO(cKJHb}xU^6mY&tV-&!YV)V+LJ(?Lmf382Igx`x3NE+ zlxQ-@0h9X%G2KJ0dO-}Q*3Hj44nXj7z_w>7{yYYNo}vi^^x3r$t8BANTFu#7fM!r{E>V zV(!1hxM)UM-nl#QAz;su=6^^sHb{w2iQuG2_&r{W1UNFeNWCZ*hD{i^`Xqxw0Dsbl0#GF{Lbvgwoao%+ShUcuRMxdKtcghV2)*Ki zE4a)UrY$FhU8!+O8C{{Mv6-6#lq*;3c96!A3HMEPqVM?tO8rv{q*mahd~&PuI(k$U zR8XVzB~~LQva}nT{&+*@fAoLPiS$0;`8&>@zm{$;yY;Y<__K`|4$RsX+5Lpkf|Y9H zrlt`w_Ouu}j`VxnkBGtW4Ikt2I;%0rKKvZbw)59?ke8J!wu!Kgqy4b-v-74Bn~<0l zB?6dBfYZeDVvK)AcWtBQDL>dn--Mw zU!=h*%^H|Ue4jYdJ-!_Ys$Mqfp8SubcFsLZ8z!$QaHxnb#JS^Taxs(rI(PooE3ka! z4NVFp(;JU)ub+Sn?~>HxM5J_OpNnQYXxkKe{39ik8|}GOEDx9e&ug$UazNQ zp+yC>8IHu`rXmXs&kRaeb#JBjIPN(9pR;F!d2|TuXEp)o^bVA`U0D-JNBBaQD&bp) za1}{VBJyhX)SH8G%H1^@y-An;Q>sdh4xw@-^$fFf-)4*M_t!GgByj{EsK30Pd@*w( zohJ~nV}?w^=f`oYEGr7Xy!yOJGogd^cUcoH*ZDVMMF>ZoGq*a@?#YJ5EYZ3--KtT|L=*&GMgq*9 zI}b-SRZU2daW(IOOgY{{$q^~T?-a%+6DU#&4+$K(%~pAhd%|w%IN-ujpdR_7Hy3ys z{MH2`;9ds$Yg0`IYKafql!3;a5f}I?SgD2EgMAfOC0kmI_R&<4`**bS=e&i9r`U6? zFp;7vcK=F*t$EUzbHazKQhW>YPA;>OnNZ*YbZ+5ow|toz6KONj{mVL;x1F@p#{5q_ zQ4ui81V3MV6EdF%xzHXYIbOBx>d)fu5s=gM6y3kWuT0f{}O5AJh{H{ppt%e?Aa zES5Ify#sIAhT1X&8X=tItPq_Cd5_p@(qq0P54sNnCNtkAX}y^w&3zaC;63V3VgkQX zP8gJ?5Dfl4`A`rh`~pdI>EoUJ#=yG<-DVWpp5Yv@I2c|KpsjWx07(#v_&o}wrDx0E zNc>hq4q|mQ{p4!Z2BD5$)1!r(U`AWWM13RJDMRp?+$zvHv@jqmbqm?%z1P=OVz(}= zcf(n6p`J1C_BFsyCgM>O0}j4})ThoKczSW9c~lgdl_rK9afS60rV4RMws{x!=(UOX z5+bK@OWmtRO%(a5?uZzlnGOR(h^TeA6bhmjHc>gw`)dzoKG}H5>{T!lI`H5D^5uUY z9<+gSzyjR@hgYj7apXM-YaZR7HaeJ!Ho`%x_z^0V)*)=(jfP)Ho0ySC8OTz6!1j8< z0bM2fNyr-&h|#Y*5`mB(fOU=Xi)H`S=ZjB5bc;I#7a`9J=3O!^RMWcLIp(l(R%t+tWT|+1lACMOyrU-DFD*_%+3Ij#(-d{8#0U6b=GQrPk2e$>y-;I=!o%t_QoG)Ntf}B+^ zWYt5X?D#~;nx?Tmg`sG?dN>(cR=wY+5y{G@T19jpR)?FAkXcD;0}h9O2gPE+_z<%k zuTfcqpt7*tyvO&#kRp7xXi%g_e~K~O#d}bj4Uy|_U!(o7Hy@xbperFV2qdXE%9OhU z1F?`QM18~*YmTdjfKV&?)BqlV5)R*bsC(1SHj}}DAm;5pj(mnZJa&0ndm}BE^2I1C zUnawAYN58Sa&vTYq611mh7qH-eD+qce$4DRC};#Pym53}`GQZ4RCf$evMG+1@n-3M zwtzPGNZuqP6i^Hq)5w?IBJ{u+1hB{6J^kTn#`0xqwg3&J1D_{@TT?*9@!Fr5x|=sT zB{BL8ftvj$wy%G4?d8@UcqNBbKt~>sfwzkfs>ozV03V<@S*&_^owjR->d$=jXC9he zegyk2%|I1A7w2KML;@G-PVqqQ$KL$%$ydJpgcdpNTP2eZDq?oObw3bri!xWJSxpvf zJPUxhICi^edMY;=7;c^_E0KOvY$A1CX4)5pl*ThV*phj3j^--GZlVQja)PkjmHHa3 zyGFChM3Ye7L&q#F$+BkR7wyXaH@U|IYW4cgQbgQ z_0}e5>_#4%63F|K;Ld(yWsg)051e1SDz)XF+s(lywt8ktXn{-Cz4fJ`_-}7?su$rc zO_Q~v*DOlOefA6EhSN$<1mwTk`ZVK71kmuh$=6m*Rbni!W*bu#^;-XO@FvrXC^i8v zdg^4{6jbgNQsz^teZViXAx#Q?-p+8#U?&f5eoClx6C9clRTLSa_($7kprl9ksH^M} zKT4#3($E-WX_fG==~G}cKjxP8Z8?KepSJn_cCXEM)%N#{MR9%|kfu=~Sld+UfMaUG`?PZSvHzarz=+=hECisoP9``qwgpG5L+B9i1@_cY;ir z;F6=qH_Jb4H}277%nK^-tcC#4lN03DDRX0sE6+Jw!${8~SOn{E{@&)rGL(glX6tQS z7@+Wm40Ocit`??RBv1oy3#JXJl^{vUM9`Z<(;}mSsd_ulVGVPMSMI>3#093qvo|)K zzDiS*oEo@{eE+La(Yp%(n$KFJ)`DgEyb9_dQo#=0+S$aci_ z-jInSb?(EIn$l!YY9m}?m|;ck-z(AzBQx=$uRyW-M)TR0=rV+nQ=o;v_{(?vz`v$I z2?Q9*@vB2-Mm>&C^10is9`U}YM=Je}#Qp3!-o->pA?fr~W!S3M-nkY8 z$-a`E6qk=d6ReA={xiL_fwGDI?$Fa$pc2bNk*y$}Sx6zI^gEq^1LkV3Kq%4OJt@LSJZ_$&BA(TCq z|G6q=Hj)KzS_r%(*2YJ|-9x~}qXEi&I+=^RR2f7;6(vm6+;r2n3 z-H#7SR}bc)Z<;Sr2{I-anka_o;vFkZM1GCG$Wt|`SB&F|OXI)eGr#^`a4`Lcg}Ny-_?H{V}CsccF~b<|)7Thb&UVaTDKkNQ{7i1nr=7f+Ljmw~_|N zD|>=Jeg8t0TpmeaPZg4oBDz4*Q{ZjeenZDHThVN!6ua>`H*u=T9L4L9(Oc zAP!8$7)VN_rNIPx;D5Vh9N*~+j-LwohZJ_LfOAgvWV!VX#w@Okh0vp<%&Z!AP;uYL z|N8`F1Bz0IOv@ipstB+~^n6yhV}53FQ8@kbb8+1|=a~jc#2;Vrb1vi`)q^;Cw32ei zt=Lc01^a5^kaW?OE~o1|0CE MAfqZ>DQW2cKSe{fS^xk5 literal 0 HcmV?d00001 diff --git a/faucet/src/static/favicon.ico b/faucet/src/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..056183ba17cb94907209bed91384cc10088e7d51 GIT binary patch literal 1150 zcma)*OGwmF6voduKCp)ws3C}on~XM5NsEv$HFch9T11+-&`Qd2oEc}-#?*>}5~EE) z5JeBFNl@seZ7L`#%`(eZW~}_6B#PbPn|sc==R1#k&tJsAFFIQIPnLb5 zB5@*;3NT@rv9pPA_CM>TsYagV9yE;D>Jmon-jp{Mm+`IDy(slz_I{Zv^vkfVE_TfB zt$lCzb%HK%gjmVbd)h@~&s{y46~eg;THKLic3%Y#N0b<^F$-d zqit2eFt5$E!Tv{V-ZbnNK_3_xwRyKr{`2hvY=5u7Bt9szOS0Tn!Xjc1o_BiLpIN`ujt(KQgYOjvl!kr)!}9!-#}gl z7k+)^kk!-tbLY|7{4@N|+x_y&?2LsQz&8cX1-M1e^Qu=8GXN6Cxx0nt{iU3p(0)D@ z9uoK!@HAfrax1d-9#2gndf&vTBIn`nJ6iGe>Kp3Y-$%|OXDGhm$R%(ugDadpLAQ5j zRvM{mJ$e^<;Y5Ce-naCdoFV8I?49_x^PZ{Q%;bx_>aYh{l}2hy=j<`inYfE@Idc-h zn+0dE*W?h5MNK;Q&KVz{8$Aw0ssAkhII)tZ%g+e&H}^9To#pH4CECww{7(V>emcN; zYTC#39(NFV3OsnL>Sk ItPoM|A1wr`X8-^I literal 0 HcmV?d00001 diff --git a/faucet/src/static/index.css b/faucet/src/static/index.css new file mode 100644 index 000000000..4c9a7d2d7 --- /dev/null +++ b/faucet/src/static/index.css @@ -0,0 +1,90 @@ +input:focus { + outline: none; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background-image: url(./background.png); + background-repeat: repeat; +} + +#error-message { + display: none; + color: red; + text-align: center; + margin-bottom: 5px; +} + +#navbar { + position: fixed; + top: 0; + left: 0; + background-color: rgb(17, 24, 39); + width: 100%; + padding: 20px; + display: flex; + flex-direction: column; + align-items: center; +} + +#title { + font-size: x-large; + text-align: center; + font-weight: bold; + color: white; + margin: 0; +} + +#center-container { + background-color: rgb(17, 24, 39); + border-radius: 10px; + display: flex; + flex-direction: column; + padding: 30px; +} + +#subtitle { + font-size: large; + text-align: center; + font-weight: bold; + color: white; + margin: 0; + margin-bottom: 20px; +} + +#account-id { + padding: 10px; + border-radius: 10px; + border: 1px solid #ccc; + margin-bottom: 30px; + width: 300px; +} + +#faucetId { + color: white; + margin-top: 4px; +} + +#button { + color: white; + border-radius: 10px; + font-weight: bold; + padding: 10px 20px; + background-color: rgb(124, 58, 237); + width: 300px; +} diff --git a/faucet/src/static/index.html b/faucet/src/static/index.html new file mode 100644 index 000000000..aee693612 --- /dev/null +++ b/faucet/src/static/index.html @@ -0,0 +1,25 @@ + + + + + + + Miden Faucet + + + + +

+
+

Request test tokens

+ + + +
+ + + + diff --git a/faucet/src/static/index.js b/faucet/src/static/index.js new file mode 100644 index 000000000..1dc7b81b2 --- /dev/null +++ b/faucet/src/static/index.js @@ -0,0 +1,70 @@ +document.addEventListener('DOMContentLoaded', function () { + const faucetIdElem = document.getElementById('faucetId'); + const button = document.getElementById('button'); + const accountIdInput = document.getElementById('account-id'); + const errorMessage = document.getElementById('error-message'); + + fetchMetadata(); + + button.addEventListener('click', handleButtonClick); + + function fetchMetadata() { + fetch('http://localhost:8080/get_metadata') + .then(response => response.json()) + .then(data => { + faucetIdElem.textContent = data.id; + button.textContent = `Send me ${data.asset_amount} tokens!`; + button.dataset.originalText = button.textContent; + }) + .catch(error => { + console.error('Error fetching metadata:', error); + faucetIdElem.textContent = 'Error loading Faucet ID.'; + button.textContent = 'Error retrieving Faucet asset amount.'; + }); + } + + async function handleButtonClick() { + let accountId = accountIdInput.value.trim(); + errorMessage.style.display = 'none'; + + if (!accountId || !/^0x[0-9a-fA-F]{16}$/i.test(accountId)) { + errorMessage.textContent = !accountId ? "Account ID is required." : "Invalid Account ID."; + errorMessage.style.display = 'block'; + return; + } + + button.textContent = 'Loading...'; + try { + const response = await fetch('http://localhost:8080/get_tokens', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ account_id: accountId }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const blob = await response.blob(); + downloadBlob(blob, 'note.mno'); + } catch (error) { + console.error('Error:', error); + errorMessage.textContent = 'Failed to receive tokens. Please try again.'; + errorMessage.style.display = 'block'; + } finally { + button.textContent = button.dataset.originalText; + } + } + + function downloadBlob(blob, filename) { + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + } +}); diff --git a/faucet/src/utils.rs b/faucet/src/utils.rs new file mode 100644 index 000000000..05a2288e3 --- /dev/null +++ b/faucet/src/utils.rs @@ -0,0 +1,102 @@ +use std::{ + fs::File, + io::{self, Read}, + path::{Path, PathBuf}, +}; + +use miden_client::{ + client::{rpc::TonicRpcClient, Client}, + config::{RpcConfig, StoreConfig}, + store::{sqlite_store::SqliteStore, AuthInfo}, +}; +use miden_lib::{accounts::faucets::create_basic_fungible_faucet, AuthScheme}; +use miden_objects::{ + accounts::{Account, AccountData}, + assets::TokenSymbol, + crypto::dsa::rpo_falcon512::KeyPair, + utils::serde::Deserializable, + Felt, +}; + +use crate::FaucetClient; + +/// Instantiates the Miden client +pub fn build_client(database_filepath: String) -> FaucetClient { + // Setup store + let store_config = StoreConfig { + database_filepath: database_filepath.clone(), + }; + let store = SqliteStore::new(store_config).expect("Failed to instantiate store."); + + // Setup the executor store + let executor_store_config = StoreConfig { + database_filepath: database_filepath.clone(), + }; + let executor_store = + SqliteStore::new(executor_store_config).expect("Failed to instantiate datastore store"); + + // Setup the tonic rpc client + let rpc_config = RpcConfig::default(); + let api = TonicRpcClient::new(&rpc_config.endpoint.to_string()); + + // Setup the client + Client::new(api, store, executor_store).expect("Failed to instantiate client.") +} + +/// Creates a Miden fungible faucet from arguments +pub fn create_fungible_faucet( + token_symbol: &str, + decimals: &u8, + max_supply: &u64, + client: &mut FaucetClient, +) -> Result { + let token_symbol = TokenSymbol::new(token_symbol).expect("Failed to parse token_symbol."); + + // Instantiate init_seed + let init_seed: [u8; 32] = [0; 32]; + + // Instantiate keypair and authscheme + let auth_seed: [u8; 40] = [0; 40]; + let keypair = KeyPair::from_seed(&auth_seed).expect("Failed to generate keypair."); + let auth_scheme = AuthScheme::RpoFalcon512 { + pub_key: keypair.public_key(), + }; + + let (account, account_seed) = create_basic_fungible_faucet( + init_seed, + token_symbol, + *decimals, + Felt::try_from(*max_supply).expect("Max_supply is outside of the possible range."), + auth_scheme, + ) + .expect("Failed to generate faucet account."); + + client + .insert_account(&account, Some(account_seed), &AuthInfo::RpoFalcon512(keypair)) + .map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "Failed to insert account into client.") + })?; + + Ok(account) +} + +/// Imports a Miden fungible faucet from a file +pub fn import_fungible_faucet( + faucet_path: &PathBuf, + client: &mut FaucetClient, +) -> Result { + let path = Path::new(faucet_path); + let mut file = File::open(path).expect("Failed to open file."); + + let mut contents = Vec::new(); + let _ = file.read_to_end(&mut contents); + + let account_data = + AccountData::read_from_bytes(&contents).expect("Failed to deserialize faucet from file."); + + client.import_account(account_data.clone()).map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "Failed to import account into client.") + })?; + + Ok(account_data.account) +} From 0b073dbc809a272b09ab9dcd7d1ca82c973b8379 Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz Date: Fri, 29 Mar 2024 10:58:17 +0100 Subject: [PATCH 18/19] cargo updated --- Cargo.lock | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1934c94c4..344b72c7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,9 +169,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "axum" @@ -368,9 +368,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -393,9 +393,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.3" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -415,9 +415,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.3" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -902,9 +902,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" @@ -1061,9 +1061,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "miden-air" @@ -1657,7 +1657,7 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", "rusty-fork", "tempfile", "unarray", @@ -1840,7 +1840,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -1860,7 +1860,7 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -1871,9 +1871,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rusqlite" @@ -1976,9 +1976,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -2159,9 +2159,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -2802,9 +2802,9 @@ dependencies = [ [[package]] name = "winter-math" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c91111b368b08c5a76009514e9b6d26af41fbb28604ea77a249282323b64d5" +checksum = "9c36d2a04b4f79f2c8c6945aab6545b7310a0cd6ae47b9210750400df6775a04" dependencies = [ "winter-utils", ] From 81131788b2d5f4c0ea14c35b4641f264483495b6 Mon Sep 17 00:00:00 2001 From: Paul-Henry Kajfasz Date: Fri, 29 Mar 2024 11:08:43 +0100 Subject: [PATCH 19/19] Improved bash scripts + changed naming in CI --- .github/workflows/test.yml | 2 +- miden-client.toml | 5 +++++ scripts/check-rust-version.sh | 2 +- scripts/test-end-to-end.sh | 5 ++++- 4 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 miden-client.toml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1728cc23a..57234f4ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ on: jobs: unit-and-integration: - name: unit and integration tests stable on ubuntu-latest + name: test stable on ubuntu-latest runs-on: ubuntu-latest timeout-minutes: 30 steps: diff --git a/miden-client.toml b/miden-client.toml new file mode 100644 index 000000000..0bebd4232 --- /dev/null +++ b/miden-client.toml @@ -0,0 +1,5 @@ +[rpc] +endpoint = { protocol = "http", host = "localhost", port = 57291 } + +[store] +database_filepath = "store.sqlite3" diff --git a/scripts/check-rust-version.sh b/scripts/check-rust-version.sh index a98dd8bf3..74507cda2 100755 --- a/scripts/check-rust-version.sh +++ b/scripts/check-rust-version.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Check rust-toolchain file TOOLCHAIN_VERSION=$(cat rust-toolchain) diff --git a/scripts/test-end-to-end.sh b/scripts/test-end-to-end.sh index 7f5a71acb..5ebfdf3d3 100755 --- a/scripts/test-end-to-end.sh +++ b/scripts/test-end-to-end.sh @@ -1,4 +1,7 @@ -#!/bin/bash +#!/usr/bin/env bash + +set -o errexit # Exit on error +set -o pipefail # The script fails if any command in a pipeline fails # Create miden-client.toml file # Needed for the Miden client to work properly